diff --git a/CMakeLists.txt b/CMakeLists.txt index f85146871c716f0f396b22d53ed6064bd6b361c5..f7aaeb52cb7b4455307d18c9daac9fc71bcf099f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,10 @@ if (POLICY CMP0072) cmake_policy (SET CMP0072 OLD) endif () +if (POLICY CMP0074) + cmake_policy (SET CMP0074 NEW) +endif () + # System package target is important for the windows builds as it allows us to package only the dlls and exes and exclude libs. Defaults to empty for other platforms. set ( SYSTEM_PACKAGE_TARGET "" ) @@ -300,106 +304,141 @@ if ( ENABLE_CPACK ) elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) include ( CPackLinuxSetup ) - # rhel requirements - set ( CPACK_RPM_PACKAGE_REQUIRES "qt4 >= 4.2,nexus >= 4.3.1,gsl,glibc,qwtplot3d-qt4,muParser,numpy,h5py >= 2.3.1,PyCifRW >= 4.2.1,tbb,librdkafka" ) - # OCE - set( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},OCE-draw,OCE-foundation,OCE-modeling,OCE-ocaf,OCE-visualization") - set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},poco-crypto,poco-data,poco-mysql,poco-sqlite,poco-odbc,poco-util,poco-xml,poco-zip,poco-net,poco-netssl,poco-foundation,PyQt4" ) - set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},sip >= 4.18" ) - set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},python-six,python-ipython >= 1.1.0,python-ipython-notebook,PyYAML" ) - # scipy - set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},scipy" ) - set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},mxml,hdf,hdf5,jsoncpp >= 0.7.0" ) + ########## rhel requirements - only used if package requested is rpm + set ( CPACK_RPM_PACKAGE_REQUIRES "nexus >= 4.3.1,gsl,glibc,muParser,numpy,h5py >= 2.3.1,PyCifRW >= 4.2.1,tbb,librdkafka," + "${CPACK_RPM_PACKAGE_REQUIRES},OCE-draw,OCE-foundation,OCE-modeling,OCE-ocaf,OCE-visualization," + "poco-crypto,poco-data,poco-mysql,poco-sqlite,poco-odbc,poco-util,poco-xml,poco-zip,poco-net,poco-netssl,poco-foundation," + "sip >= 4.18," + "python-six,python-ipython >= 1.1.0,python-ipython-notebook,PyYAML," + "python-requests," + "scipy," + "hdf,hdf5,jsoncpp >= 0.7.0" ) + if (ENABLE_MANTIDPLOT) + set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},qt4 >= 4.2,PyQt4,qwtplot3d-qt4" ) + endif() if (ENABLE_WORKBENCH) - set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},python-qt5" ) + set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},qt5-qtbase,python-qt5" ) endif() if( "${UNIX_CODENAME}" MATCHES "Santiago" ) # RHEL6 + if ( ENABLE_WORKBENCH ) + message ( FATAL_ERROR "mantidworkbench is not supported on RHEL6" ) + endif () # On RHEL6 we have to use an updated qscintilla to fix an auto complete bug - set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES} qscintilla >= 2.4.6, boost157,python-matplotlib" ) - # On RHEL6 we are using SCL packages for Qt - set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},scl-utils,mantidlibs34,mantidlibs34-runtime,mantidlibs34-qt,mantidlibs34-qt-x11,mantidlibs34-qt-webkit,mantidlibs34-qwt5-qt4" ) - else() - # Require matplotlib >= 1.5 to fix bug in latex rendering - set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES} qscintilla,qwt5-qt4,python2-matplotlib-qt4 >= 1.5.2,python2-QtAwesome,boost >= 1.53.0" ) - endif() - - # Add software collections for RHEL - if ( "${UNIX_CODENAME}" MATCHES "Santiago" ) # RHEL6 - set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES} scl-utils" ) + set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},qscintilla >= 2.4.6,boost157,python-matplotlib," + # On RHEL6 we are using SCL packages for Qt + "scl-utils,mantidlibs34,mantidlibs34-runtime,mantidlibs34-qt,mantidlibs34-qt-x11,mantidlibs34-qt-webkit,mantidlibs34-qwt5-qt4" ) + else() # assumes RHEL7 + # Require matplotlib >= 1.5 to fix bug in latex rendering - oddly matplotlib-qt4 contains qt5 backend as well + set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},python2-QtAwesome,boost >= 1.53.0,python2-matplotlib-qt4 >= 1.5.2" ) + if (ENABLE_MANTIDPLOT) + set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},qscintilla,qwt5-qt4" ) + endif() + if (ENABLE_WORKBENCH) + set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},qscintilla-qt5" ) + endif() endif() + string ( REPLACE ";" "," CPACK_RPM_PACKAGE_REQUIRES ${CPACK_RPM_PACKAGE_REQUIRES} ) # fix up the fact that it was made as an array + string ( REPLACE ",," "," CPACK_RPM_PACKAGE_REQUIRES ${CPACK_RPM_PACKAGE_REQUIRES} ) - if( "${UNIX_DIST}" MATCHES "Ubuntu" ) - # common packages - set ( DEPENDS_LIST "libboost-date-time${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}," + ########## ubuntu requirements - only used if package requested is deb + set ( CPACK_DEBIAN_PACKAGE_DEPENDS "libboost-date-time${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}," "libboost-regex${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}," "libboost-python${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}," "libboost-serialization${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}," "libboost-filesystem${Boost_MAJOR_VERSION}.${Boost_MINOR_VERSION}.${Boost_SUBMINOR_VERSION}," "libnexus0v5 (>= 4.3)," - "libgsl2," - "libqtcore4 (>= 4.2)," - "libqtgui4 (>= 4.2)," - "libqt4-opengl (>= 4.2)," - "libqt4-xml (>= 4.2)," - "libqt4-svg (>= 4.2)," - "libqt4-qt3support (>= 4.2)," - "qt4-dev-tools," - "libqwt5-qt4," - "libqwtplot3d-qt4-0v5," - "libqtwebkit4," - "python-h5py," - "python-numpy," - "python-sip," - "python-qt4," - "python-nxs (>= 4.3)," - "python-six," "libjsoncpp1 (>=0.7.0)," - "libqscintilla2-12v5," - "liboce-foundation10,liboce-modeling10," "libmuparser2v5," - "ipython-qtconsole (>= 1.1)," - "ipython-notebook," - "python-matplotlib," - "python-scipy," "libtbb2," - "librdkafka1," - "librdkafka++1," - "libpocofoundation${POCO_SOLIB_VERSION},libpocoutil${POCO_SOLIB_VERSION},libpoconet${POCO_SOLIB_VERSION},libpoconetssl${POCO_SOLIB_VERSION},libpococrypto${POCO_SOLIB_VERSION},libpocoxml${POCO_SOLIB_VERSION}," - "libhdf5-cpp-11," - "python-pycifrw (>= 4.2.1)," - "python-yaml," - "python-qtawesome") - set ( PERFTOOLS_DEB_PACKAGE "libgoogle-perftools4 (>= 1.7)" ) - if( "${UNIX_CODENAME}" STREQUAL "bionic") - list ( APPEND DEPENDS_LIST ",libgsl23,liboce-foundation11,liboce-modeling11,libqscintilla2-qt4-13,jupyter-notebook,libhdf5-cpp-100") - list (REMOVE_ITEM DEPENDS_LIST "libgsl2," "liboce-foundation10,liboce-modeling10," "libqscintilla2-12v5," "ipython-notebook," "libhdf5-cpp-11,") + "librdkafka1,librdkafka++1," + "libpocofoundation${POCO_SOLIB_VERSION},libpocoutil${POCO_SOLIB_VERSION},libpoconet${POCO_SOLIB_VERSION},libpoconetssl${POCO_SOLIB_VERSION},libpococrypto${POCO_SOLIB_VERSION},libpocoxml${POCO_SOLIB_VERSION}") + if (ENABLE_MANTIDPLOT) + set ( CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqtcore4 (>= 4.2)," + "libqtgui4 (>= 4.2)," + "libqt4-opengl (>= 4.2)," + "libqt4-xml (>= 4.2)," + "libqt4-svg (>= 4.2)," + "libqt4-qt3support (>= 4.2)," + "qt4-dev-tools," + "libqwt5-qt4," + "libqwtplot3d-qt4-0v5," + "libqtwebkit4" ) + endif() + + if( "${UNIX_CODENAME}" STREQUAL "xenial") + set ( CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libgsl2," + "liboce-foundation10,liboce-modeling10," + "libqscintilla2-12v5," + "ipython-notebook," + "libhdf5-cpp-11" ) + if (ENABLE_WORKBENCH) + set ( CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5scintilla2-12v5" ) + endif() + else() # bionic and newer + set ( CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libgsl23," + "liboce-foundation11,liboce-modeling11," + "libqscintilla2-qt4-13," + "jupyter-notebook," + "libhdf5-cpp-100" ) + if (ENABLE_WORKBENCH) + set ( CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqscintilla2-qt5-13" ) endif() - # Change DEPENDS_LIST for python 3 - if ( PYTHON_VERSION_MAJOR EQUAL 3 ) - list (REMOVE_ITEM DEPENDS_LIST "python-nxs (>= 4.3),") - string ( REPLACE "python-" "python3-" DEPENDS_LIST ${DEPENDS_LIST} ) - string ( REPLACE "python3-qt4" "python3-pyqt4" DEPENDS_LIST ${DEPENDS_LIST} ) - if (ENABLE_WORKBENCH) - set ( APPEND DEPENDS_LIST ",python3-qt5" ) - endif() - else() - if (ENABLE_WORKBENCH) - set ( APPEND DEPENDS_LIST ",pyqt5" ) - endif() - endif () - # parse list to string required for deb package - string ( REPLACE ";" "" CPACK_DEBIAN_PACKAGE_DEPENDS ${DEPENDS_LIST} ) endif() - # soft requirement of tcmalloc if selected - IF ( USE_TCMALLOC ) - message ( STATUS "Adding gperftools to the package requirements" ) - set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},gperftools-libs >= 2.0" ) - set ( CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},${PERFTOOLS_DEB_PACKAGE}" ) - ENDIF ( ) - ENDIF () + + if ( PYTHON_VERSION_MAJOR EQUAL 3 ) + set ( CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},python3-h5py," + "python3-numpy," + "python3-requests," + "python3-sip," + "python3-six," + "python3-matplotlib," + "python3-scipy," + "python3-pycifrw (>= 4.2.1)," + "python3-yaml," + "python3-qtawesome," + "ipython3-qtconsole" ) # transitional package for bionic + + if (ENABLE_MANTIDPLOT) + set ( CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},python3-pyqt4" ) + endif() + if (ENABLE_WORKBENCH) + set ( CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},python3-pyqt5" ) + endif() + else() # python 2 + set ( CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},python-h5py," + "python-numpy," + "python-requests," + "python-sip," + "python-six," + "python-matplotlib," + "python-scipy," + "python-pycifrw (>= 4.2.1)," + "python-yaml," + "python-qtawesome," + "ipython-qtconsole" ) + + if (ENABLE_MANTIDPLOT) + set ( CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},python-qt4" ) + endif() + if (ENABLE_WORKBENCH) + set ( CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},python-pyqt5" ) + endif() + endif () + # parse list to string required for deb package + string ( REPLACE ";" "," CPACK_DEBIAN_PACKAGE_DEPENDS ${CPACK_DEBIAN_PACKAGE_DEPENDS} ) # fix up the fact that it was made as an array + string ( REPLACE ",," "," CPACK_DEBIAN_PACKAGE_DEPENDS ${CPACK_DEBIAN_PACKAGE_DEPENDS} ) + endif() + + # soft requirement of tcmalloc if selected + IF ( USE_TCMALLOC ) + message ( STATUS "Adding gperftools to the package requirements" ) + set ( CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},gperftools-libs >= 2.0" ) + set ( CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libgoogle-perftools4 (>= 1.7)" ) + ENDIF ( ) + # run cpack configuration include ( CPack ) # let people know what is coming out the other end - must be after cpack generates value for rpm message ( STATUS "CPACK_PACKAGE_FILE_NAME = ${CPACK_PACKAGE_FILE_NAME}" ) -endif () \ No newline at end of file +endif () diff --git a/Framework/API/CMakeLists.txt b/Framework/API/CMakeLists.txt index b66bd694e7850b4771d1e197e0439999fbaf0941..05529388b2621945dceac437546e5087658091a4 100644 --- a/Framework/API/CMakeLists.txt +++ b/Framework/API/CMakeLists.txt @@ -491,9 +491,10 @@ if(UNITY_BUILD) enable_unity_build(API SRC_FILES SRC_UNITY_IGNORE_FILES 10) endif(UNITY_BUILD) -# Have to link to winsock library on Windows +# Have to link to winsock and bcrypt library on Windows if ( WIN32 ) set ( WINSOCK ws2_32 ) + set ( BCRYPT bcrypt ) endif () # Add a precompiled header where they are supported @@ -513,7 +514,7 @@ endif () # Add to the 'Framework' group in VS set_property ( TARGET API PROPERTY FOLDER "MantidFramework" ) -target_link_libraries ( API LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} ${JSONCPP_LIBRARIES} ${MANTIDLIBS} ${GSL_LIBRARIES} ${NEXUS_LIBRARIES} ${WINSOCK} ) +target_link_libraries ( API LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} ${JSONCPP_LIBRARIES} ${MANTIDLIBS} ${GSL_LIBRARIES} ${NEXUS_LIBRARIES} ${WINSOCK} ${BCRYPT}) # Add the unit tests directory add_subdirectory ( test ) diff --git a/Framework/API/inc/MantidAPI/AlgorithmHistory.h b/Framework/API/inc/MantidAPI/AlgorithmHistory.h index 621c11d630a50c0dbb005850e5352b1af7253a64..78b04c21c1de57618a493758e1ad7931035ea2b3 100644 --- a/Framework/API/inc/MantidAPI/AlgorithmHistory.h +++ b/Framework/API/inc/MantidAPI/AlgorithmHistory.h @@ -27,23 +27,10 @@ class IAlgorithm; class Algorithm; class AlgorithmHistory; -namespace Detail { -// Written as a template in order to get around circular issue of CompareHistory -// needing to know the implementation of AlgorithmHistory and AlgorithmHistory -// needing to know the implementation of CompareHistory. -template <class T> struct CompareHistory { - bool operator()(const boost::shared_ptr<T> &lhs, - const boost::shared_ptr<T> &rhs) const { - return (*lhs) < (*rhs); - } -}; -} // namespace Detail - // typedefs for algorithm history pointers using AlgorithmHistory_sptr = boost::shared_ptr<AlgorithmHistory>; using AlgorithmHistory_const_sptr = boost::shared_ptr<const AlgorithmHistory>; -using AlgorithmHistories = - std::set<AlgorithmHistory_sptr, Detail::CompareHistory<AlgorithmHistory>>; +using AlgorithmHistories = std::vector<AlgorithmHistory_sptr>; /** @class AlgorithmHistory AlgorithmHistory.h API/MAntidAPI/AlgorithmHistory.h @@ -69,7 +56,7 @@ public: ~AlgorithmHistory(); AlgorithmHistory &operator=(const AlgorithmHistory &); AlgorithmHistory(const AlgorithmHistory &); - AlgorithmHistory(const std::string &name, int vers, + AlgorithmHistory(const std::string &name, int vers, std::string uuid, const Types::Core::DateAndTime &start = Types::Core::DateAndTime::getCurrentTime(), const double &duration = -1.0, std::size_t uexeccount = 0); @@ -93,6 +80,8 @@ public: } /// get the execution count const std::size_t &execCount() const { return m_execCount; } + /// get the uuid + const std::string &uuid() const { return m_uuid; } /// get parameter list of algorithm in history const const Mantid::Kernel::PropertyHistories &getProperties() const { return m_properties; @@ -114,7 +103,7 @@ public: const size_t maxPropertyLength = 0) const; /// Less than operator inline bool operator<(const AlgorithmHistory &other) const { - return (execCount() < other.execCount()); + return execCount() < other.execCount(); } /// Equality operator inline bool operator==(const AlgorithmHistory &other) const { @@ -138,7 +127,7 @@ public: private: // private constructor - AlgorithmHistory() = default; + AlgorithmHistory(); // Set properties of algorithm void setProperties(const Algorithm *const alg); /// The name of the Algorithm @@ -155,6 +144,8 @@ private: std::size_t m_execCount{0}; /// set of child algorithm histories for this history record AlgorithmHistories m_childHistories; + /// UUID for this algorithm history + std::string m_uuid; }; MANTID_API_DLL std::ostream &operator<<(std::ostream &, diff --git a/Framework/API/inc/MantidAPI/FunctionFactory.h b/Framework/API/inc/MantidAPI/FunctionFactory.h index 8003e67864ad1e652cec97e4ac48ef6089cefe5c..f9b332d023da1210059b72fe8be559a44748f739 100644 --- a/Framework/API/inc/MantidAPI/FunctionFactory.h +++ b/Framework/API/inc/MantidAPI/FunctionFactory.h @@ -94,6 +94,10 @@ private: /// Add a single constraint to the created function void addConstraint(boost::shared_ptr<IFunction> fun, const Expression &expr) const; + /// Add a single constraint to the created function with non-default penalty + void addConstraint(boost::shared_ptr<IFunction> fun, + const Expression &constraint_expr, + const Expression &penalty_expr) const; /// Add ties to the created function void addTies(boost::shared_ptr<IFunction> fun, const Expression &expr) const; /// Add a tie to the created function diff --git a/Framework/API/inc/MantidAPI/IConstraint.h b/Framework/API/inc/MantidAPI/IConstraint.h index df427edf2760895812887be3bc71b97e8b7bd80b..75070c3c010bfdfb01a40f1de6ea8afd5300924e 100644 --- a/Framework/API/inc/MantidAPI/IConstraint.h +++ b/Framework/API/inc/MantidAPI/IConstraint.h @@ -61,6 +61,13 @@ public: /// Return the string that can be used in this->initialize() to recreate this /// constraint virtual std::string asString() const = 0; + + /// Return the value for default fitting penalties + static double getDefaultPenaltyFactor() { return m_defaultPenaltyFactor; } + +protected: + /// default penalty factor for constraints + static constexpr double m_defaultPenaltyFactor = 1000; }; } // namespace API diff --git a/Framework/API/inc/MantidAPI/IFunction.h b/Framework/API/inc/MantidAPI/IFunction.h index 8c3fb26391d070d47349d6f39389ddb5b76c390a..31fd0923a40fa9fa5f782c5843328fb762cf5194 100644 --- a/Framework/API/inc/MantidAPI/IFunction.h +++ b/Framework/API/inc/MantidAPI/IFunction.h @@ -477,6 +477,8 @@ public: virtual IConstraint *getConstraint(size_t i) const; /// Remove a constraint virtual void removeConstraint(const std::string &parName); + virtual void setConstraintPenaltyFactor(const std::string &parName, + const double &c); /// Write a parameter constraint to a string std::string writeConstraints() const; /// Remove all constraints. diff --git a/Framework/API/inc/MantidAPI/SpectrumDetectorMapping.h b/Framework/API/inc/MantidAPI/SpectrumDetectorMapping.h index 43dc52b32c603bf172fdd488ad8b29758e3ac5dc..26ecd29a26757e6329f16981ee00b3085773985a 100644 --- a/Framework/API/inc/MantidAPI/SpectrumDetectorMapping.h +++ b/Framework/API/inc/MantidAPI/SpectrumDetectorMapping.h @@ -14,32 +14,26 @@ #endif #include "MantidAPI/DllConfig.h" +#include "MantidAPI/MatrixWorkspace_fwd.h" #include "MantidGeometry/IDTypes.h" namespace Mantid { namespace API { -//---------------------------------------------------------------------- -// Forward Declaration -//---------------------------------------------------------------------- -class MatrixWorkspace; /** A minimal class to hold the mapping between the spectrum number and its - related - detector ID numbers for a dataset. + related detector ID numbers for a dataset. Normally, this mapping is contained within the collection of ISpectrum - objects held - by the workspace. This class can be useful when you want to pass just this - information - and not the entire workspace, or you want to store some mapping that related - to spectra - that are not yet, or no longer, contained in the workspace. + objects held by the workspace. This class can be useful when you want to + pass just this information and not the entire workspace, or you want to + store some mapping that related to spectra that are not yet, or no longer, + contained in the workspace. */ class MANTID_API_DLL SpectrumDetectorMapping { using sdmap = std::unordered_map<specnum_t, std::set<detid_t>>; public: - explicit SpectrumDetectorMapping(const MatrixWorkspace *const workspace, - bool useSpecNoIndex = true); + explicit SpectrumDetectorMapping(MatrixWorkspace_const_sptr workspace, + const bool useSpecNoIndex = true); SpectrumDetectorMapping( const std::vector<specnum_t> &spectrumNumbers, const std::vector<detid_t> &detectorIDs, diff --git a/Framework/API/inc/MantidAPI/SpectrumInfo.h b/Framework/API/inc/MantidAPI/SpectrumInfo.h index 5acea67bcbdc2d42c50df22a0162a427326e4269..510cac4a0180c050e314302988e2b1a9b5a7f156 100644 --- a/Framework/API/inc/MantidAPI/SpectrumInfo.h +++ b/Framework/API/inc/MantidAPI/SpectrumInfo.h @@ -8,6 +8,7 @@ #define MANTID_API_SPECTRUMINFO_H_ #include "MantidAPI/DllConfig.h" +#include "MantidAPI/SpectrumInfoIterator.h" #include "MantidKernel/V3D.h" #include "MantidKernel/cow_ptr.h" @@ -28,7 +29,6 @@ class Instrument; class ParameterMap; } // namespace Geometry namespace API { -class SpectrumInfoIterator; class ExperimentInfo; /** API::SpectrumInfo is an intermediate step towards a SpectrumInfo that is @@ -84,8 +84,10 @@ public: Kernel::V3D samplePosition() const; double l1() const; - SpectrumInfoIterator begin() const; - SpectrumInfoIterator end() const; + SpectrumInfoIterator<SpectrumInfo> begin(); + SpectrumInfoIterator<SpectrumInfo> end(); + const SpectrumInfoIterator<const SpectrumInfo> cbegin() const; + const SpectrumInfoIterator<const SpectrumInfo> cend() const; friend class ExperimentInfo; @@ -102,6 +104,9 @@ private: mutable std::vector<size_t> m_lastIndex; }; +using SpectrumInfoIt = SpectrumInfoIterator<SpectrumInfo>; +using SpectrumInfoConstIt = SpectrumInfoIterator<const SpectrumInfo>; + } // namespace API } // namespace Mantid diff --git a/Framework/API/inc/MantidAPI/SpectrumInfoItem.h b/Framework/API/inc/MantidAPI/SpectrumInfoItem.h index 57ba067d2830614aacaee4fec2e10bcce334a599..489cf2c59748d9e76b6849c8ee9eba20348cb81f 100644 --- a/Framework/API/inc/MantidAPI/SpectrumInfoItem.h +++ b/Framework/API/inc/MantidAPI/SpectrumInfoItem.h @@ -7,11 +7,11 @@ #ifndef MANTID_API_SPECTRUMINFOITEM_H_ #define MANTID_API_SPECTRUMINFOITEM_H_ -#include "MantidAPI/SpectrumInfo.h" +#include "MantidAPI/DllConfig.h" #include "MantidKernel/V3D.h" #include "MantidTypes/SpectrumDefinition.h" +#include <type_traits> -using Mantid::API::SpectrumInfo; using Mantid::Kernel::V3D; using Mantid::SpectrumDefinition; @@ -35,8 +35,7 @@ methods include: @author Bhuvan Bezawada, STFC @date 2018 */ - -class MANTID_API_DLL SpectrumInfoItem { +template <typename T> class SpectrumInfoItem { public: // Methods that can be called via the iterator @@ -44,6 +43,11 @@ public: bool isMasked() const { return m_spectrumInfo->isMasked(m_index); } + void setMasked(bool masked) { + static_assert(!std::is_const<T>::value, "Operation disabled on const T"); + return m_spectrumInfo->setMasked(m_index, masked); + } + double twoTheta() const { return m_spectrumInfo->twoTheta(m_index); } double signedTwoTheta() const { @@ -64,17 +68,12 @@ public: return m_spectrumInfo->position(m_index); } -private: - // Allow SpectrumInfoIterator access - friend class SpectrumInfoIterator; - - // Private constructor, can only be created by SpectrumInfoIterator - SpectrumInfoItem(const SpectrumInfo &spectrumInfo, const size_t index) + SpectrumInfoItem(T &spectrumInfo, const size_t index) : m_spectrumInfo(&spectrumInfo), m_index(index) {} // Non-owning pointer. A reference makes the class unable to define an // assignment operator that we need. - const SpectrumInfo *m_spectrumInfo; + T *m_spectrumInfo; size_t m_index; }; diff --git a/Framework/API/inc/MantidAPI/SpectrumInfoIterator.h b/Framework/API/inc/MantidAPI/SpectrumInfoIterator.h index 0c1e30e154e9245bfe11aa24b1e83f0ad18f883a..6cb8b133be0359861e5f16610ea90c18d21e1295 100644 --- a/Framework/API/inc/MantidAPI/SpectrumInfoIterator.h +++ b/Framework/API/inc/MantidAPI/SpectrumInfoIterator.h @@ -8,7 +8,6 @@ #define MANTID_API_SPECTRUMINFOITERATOR_H_ #include "MantidAPI/SpectrumInfoItem.h" - #include <boost/iterator/iterator_facade.hpp> using Mantid::API::SpectrumInfoItem; @@ -27,13 +26,14 @@ iterator. @date 2018 */ -class MANTID_API_DLL SpectrumInfoIterator - : public boost::iterator_facade<SpectrumInfoIterator, - const SpectrumInfoItem &, +template <typename T> +class SpectrumInfoIterator + : public boost::iterator_facade<SpectrumInfoIterator<T>, + SpectrumInfoItem<T> &, boost::random_access_traversal_tag> { public: - SpectrumInfoIterator(const SpectrumInfo &spectrumInfo, const size_t index) + SpectrumInfoIterator(T &spectrumInfo, const size_t index) : m_item(spectrumInfo, index) {} private: @@ -69,18 +69,18 @@ private: void setIndex(const size_t index) { m_item.m_index = index; } - uint64_t distance_to(const SpectrumInfoIterator &other) const { + uint64_t distance_to(const SpectrumInfoIterator<T> &other) const { return static_cast<uint64_t>(other.getIndex()) - static_cast<uint64_t>(getIndex()); } - bool equal(const SpectrumInfoIterator &other) const { + bool equal(const SpectrumInfoIterator<T> &other) const { return getIndex() == other.getIndex(); } - const SpectrumInfoItem &dereference() const { return m_item; } + SpectrumInfoItem<T> &dereference() const { return m_item; } - SpectrumInfoItem m_item; + mutable SpectrumInfoItem<T> m_item; }; } // namespace API diff --git a/Framework/API/src/Algorithm.cpp b/Framework/API/src/Algorithm.cpp index 91b61680e9837fee3d905dea3be21e8a54e89388..0f5f532a33e42e8f5db1c23411f7288e75feae13 100644 --- a/Framework/API/src/Algorithm.cpp +++ b/Framework/API/src/Algorithm.cpp @@ -27,7 +27,6 @@ #include "MantidParallel/Communicator.h" -#include <boost/algorithm/string/regex.hpp> #include <boost/weak_ptr.hpp> #include <MantidKernel/StringTokenizer.h> diff --git a/Framework/API/src/AlgorithmHistory.cpp b/Framework/API/src/AlgorithmHistory.cpp index 519810cbe45a846d4e17e82751f9b2f0020a11f6..d5ae137a744bf94fa4d1540020fc23c7931eed9e 100644 --- a/Framework/API/src/AlgorithmHistory.cpp +++ b/Framework/API/src/AlgorithmHistory.cpp @@ -9,6 +9,9 @@ //---------------------------------------------------------------------- #include "MantidAPI/AlgorithmHistory.h" #include "MantidAPI/Algorithm.h" +#include <boost/uuid/uuid.hpp> +#include <boost/uuid/uuid_generators.hpp> +#include <boost/uuid/uuid_io.hpp> #include <sstream> namespace Mantid { @@ -21,6 +24,11 @@ using Kernel::PropertyHistory_const_sptr; using Kernel::PropertyHistory_sptr; using Types::Core::DateAndTime; +namespace { +/// The generator for algorithm history UUIDs +static boost::uuids::random_generator uuidGen; +} // namespace + /** Constructor * @param alg :: A pointer to the algorithm for which the history should * be constructed @@ -39,6 +47,12 @@ AlgorithmHistory::AlgorithmHistory(const Algorithm *const alg, // Now go through the algorithm's properties and create the PropertyHistory // objects. setProperties(alg); + m_uuid = boost::uuids::to_string(uuidGen()); +} + +/// Default constructor +AlgorithmHistory::AlgorithmHistory() { + m_uuid = boost::uuids::to_string(uuidGen()); } /// Destructor @@ -49,18 +63,21 @@ AlgorithmHistory::~AlgorithmHistory() = default; from saved records. @param name :: The algorithm name. @param vers :: The algorithm version. + @param uuid :: The universally unique id assigned to this alghistory on + creation during assignment to history. @param start :: The start time of the algorithm execution (optional). @param duration :: The time (in seconds) that it took to run this algorithm (optional). @param uexeccount :: an unsigned int for algorithm execution order */ AlgorithmHistory::AlgorithmHistory(const std::string &name, int vers, + std::string uuid, const Types::Core::DateAndTime &start, const double &duration, std::size_t uexeccount) : m_name(name), m_version(vers), m_executionDate(start), m_executionDuration(duration), m_execCount(uexeccount), - m_childHistories() {} + m_childHistories(), m_uuid(uuid) {} /** * Set the history properties for an algorithm pointer @@ -107,7 +124,7 @@ AlgorithmHistory::AlgorithmHistory(const AlgorithmHistory &A) : m_name(A.m_name), m_version(A.m_version), m_executionDate(A.m_executionDate), m_executionDuration(A.m_executionDuration), m_properties(A.m_properties), - m_execCount(A.m_execCount) { + m_execCount(A.m_execCount), m_uuid(A.m_uuid) { m_childHistories = A.m_childHistories; } @@ -143,7 +160,7 @@ void AlgorithmHistory::addChildHistory(AlgorithmHistory_sptr childHist) { return; } - m_childHistories.insert(childHist); + m_childHistories.emplace_back(childHist); } /* @@ -214,14 +231,15 @@ AlgorithmHistory::getChildAlgorithm(const size_t index) const { */ void AlgorithmHistory::printSelf(std::ostream &os, const int indent, const size_t maxPropertyLength) const { + auto execDate = m_executionDate.toISO8601String(); + execDate.replace(execDate.find("T"), 1, " "); os << std::string(indent, ' ') << "Algorithm: " << m_name; os << std::string(indent, ' ') << " v" << m_version << '\n'; - os << std::string(indent, ' ') - << "Execution Date: " << m_executionDate.toFormattedString() << '\n'; + os << std::string(indent, ' ') << "Execution Date: " << execDate << '\n'; os << std::string(indent, ' ') << "Execution Duration: " << m_executionDuration << " seconds\n"; - + os << std::string(indent, ' ') << "UUID: " << m_uuid << '\n'; os << std::string(indent, ' ') << "Parameters:\n"; for (const auto &property : m_properties) { @@ -252,6 +270,7 @@ AlgorithmHistory &AlgorithmHistory::operator=(const AlgorithmHistory &A) { // to an ancestor auto temp = A.m_childHistories; m_childHistories = temp; + m_uuid = A.m_uuid; } return *this; } @@ -290,6 +309,5 @@ void AlgorithmHistory::saveNexus(::NeXus::File *file, int &algCount) const { } file->closeGroup(); } - } // namespace API } // namespace Mantid diff --git a/Framework/API/src/FileFinder.cpp b/Framework/API/src/FileFinder.cpp index 345adf00f064c6d7cc82c3ef40552388f1ba488a..392db921a8dce9f2659e3b90bd4613bd63f9280c 100644 --- a/Framework/API/src/FileFinder.cpp +++ b/Framework/API/src/FileFinder.cpp @@ -103,55 +103,11 @@ bool FileFinderImpl::getCaseSensitive() const { * search locations * or an empty string otherwise. */ + std::string FileFinderImpl::getFullPath(const std::string &filename, const bool ignoreDirs) const { - std::string fName = Kernel::Strings::strip(filename); - g_log.debug() << "getFullPath(" << fName << ")\n"; - // If this is already a full path, nothing to do - if (Poco::Path(fName).isAbsolute()) - return fName; - - // First try the path relative to the current directory. Can throw in some - // circumstances with extensions that have wild cards - try { - Poco::File fullPath(Poco::Path().resolve(fName)); - if (fullPath.exists() && (!ignoreDirs || !fullPath.isDirectory())) - return fullPath.path(); - } catch (std::exception &) { - } - - const std::vector<std::string> &searchPaths = - Kernel::ConfigService::Instance().getDataSearchDirs(); - for (const auto &searchPath : searchPaths) { - g_log.debug() << "Searching for " << fName << " in " << searchPath << "\n"; -// On windows globbing is note working properly with network drives -// for example a network drive containing a $ -// For this reason, and since windows is case insensitive anyway -// a special case is made for windows -#ifdef _WIN32 - if (fName.find("*") != std::string::npos) { -#endif - Poco::Path path(searchPath, fName); - std::set<std::string> files; - Kernel::Glob::glob(path, files, m_globOption); - if (!files.empty()) { - Poco::File matchPath(*files.begin()); - if (ignoreDirs && matchPath.isDirectory()) { - continue; - } - return *files.begin(); - } -#ifdef _WIN32 - } else { - Poco::Path path(searchPath, fName); - Poco::File file(path); - if (file.exists() && !(ignoreDirs && file.isDirectory())) { - return path.toString(); - } - } -#endif - } - return ""; + return Kernel::ConfigService::Instance().getFullPath(filename, ignoreDirs, + m_globOption); } /** Run numbers can be followed by an allowed string. Check if there is @@ -632,6 +588,7 @@ FileFinderImpl::findRuns(const std::string &hintstr) const { hint, ",", Mantid::Kernel::StringTokenizer::TOK_TRIM | Mantid::Kernel::StringTokenizer::TOK_IGNORE_EMPTY); + static const boost::regex digits("[0-9]+"); auto h = hints.begin(); for (; h != hints.end(); ++h) { @@ -669,7 +626,6 @@ FileFinderImpl::findRuns(const std::string &hintstr) const { runEnd.replace(runEnd.end() - range[1].size(), runEnd.end(), range[1]); // Throw if runEnd contains something else other than a digit. - boost::regex digits("[0-9]+"); if (!boost::regex_match(runEnd, digits)) throw std::invalid_argument("Malformed range of runs: Part of the run " "has a non-digit character in it."); diff --git a/Framework/API/src/FunctionFactory.cpp b/Framework/API/src/FunctionFactory.cpp index 583bd591ba2cad0b2770769406727ba2500bf4d0..e4eb20f27032c030097a4e1f9fb060574a472ba2 100644 --- a/Framework/API/src/FunctionFactory.cpp +++ b/Framework/API/src/FunctionFactory.cpp @@ -272,10 +272,28 @@ void FunctionFactoryImpl::inputError(const std::string &str) const { void FunctionFactoryImpl::addConstraints(IFunction_sptr fun, const Expression &expr) const { if (expr.name() == ",") { - for (const auto &constraint : expr) { - addConstraint(fun, constraint); + for (auto it = expr.begin(); it != expr.end(); ++it) { + // If this is a penalty term, we used it on the previous iteration + // so we move on to the next term. + auto constraint = (*it); + std::string constraint_term = constraint.terms()[0].str(); + if (constraint_term.compare("penalty") == 0) { + continue; + } + + if ((it + 1) != expr.end()) { + auto next_constraint = *(it + 1); + std::string next_term = next_constraint.terms()[0].str(); + if (next_term.compare("penalty") == 0) { + addConstraint(fun, constraint, next_constraint); + } else { + addConstraint(fun, constraint); + } + } else { + addConstraint(fun, constraint); + } } - } else { + } else { // There was a single constraint given, cannot contain a penalty addConstraint(fun, expr); } } @@ -285,10 +303,28 @@ void FunctionFactoryImpl::addConstraints(IFunction_sptr fun, * @param fun :: The function * @param expr :: The constraint expression. */ -void FunctionFactoryImpl::addConstraint(IFunction_sptr fun, +void FunctionFactoryImpl::addConstraint(boost::shared_ptr<IFunction> fun, const Expression &expr) const { auto c = std::unique_ptr<IConstraint>( ConstraintFactory::Instance().createInitialized(fun.get(), expr)); + c->setPenaltyFactor(c->getDefaultPenaltyFactor()); + fun->addConstraint(std::move(c)); +} + +/** + * Add a constraint to the function with non-default penalty + * @param fun :: The function + * @param constraint_expr :: The constraint expression. + * @param penalty_expr :: The penalty expression. + */ +void FunctionFactoryImpl::addConstraint(boost::shared_ptr<IFunction> fun, + const Expression &constraint_expr, + const Expression &penalty_expr) const { + auto c = std::unique_ptr<IConstraint>( + ConstraintFactory::Instance().createInitialized(fun.get(), + constraint_expr)); + double penalty_factor = std::stof(penalty_expr.terms()[1].str(), NULL); + c->setPenaltyFactor(penalty_factor); fun->addConstraint(std::move(c)); } diff --git a/Framework/API/src/IFunction.cpp b/Framework/API/src/IFunction.cpp index b61ad8cc5ac610d0d070ce9f56f42b7f4cb48a17..74d02c7dc04146465946f9a4ab9e30a1aaf6efbc 100644 --- a/Framework/API/src/IFunction.cpp +++ b/Framework/API/src/IFunction.cpp @@ -399,6 +399,25 @@ void IFunction::removeConstraint(const std::string &parName) { } } +/** Set a constraint penalty + * @param parName :: The name of a constraint + * @param c :: The penalty + */ +void IFunction::setConstraintPenaltyFactor(const std::string &parName, + const double &c) { + size_t iPar = parameterIndex(parName); + for (auto it = m_constraints.begin(); it != m_constraints.end(); ++it) { + if (iPar == (**it).getLocalIndex()) { + (**it).setPenaltyFactor(c); + return; + } + } + g_log.warning() + << parName + << " does not have constraint so setConstraintPenaltyFactor failed" + << "\n"; +} + /// Remove all constraints. void IFunction::clearConstraints() { m_constraints.clear(); } @@ -496,10 +515,32 @@ void IFunction::addConstraints(const std::string &str, bool isDefault) { Expression list; list.parse(str); list.toList(); - for (const auto &expr : list) { - auto c = std::unique_ptr<IConstraint>( - ConstraintFactory::Instance().createInitialized(this, expr, isDefault)); - this->addConstraint(std::move(c)); + for (auto it = list.begin(); it != list.end(); ++it) { + auto expr = (*it); + if (expr.terms()[0].str().compare("penalty") == 0) { + continue; + } + if ((it + 1) != list.end()) { + auto next_expr = *(it + 1); + if (next_expr.terms()[0].str().compare("penalty") == 0) { + auto c = std::unique_ptr<IConstraint>( + ConstraintFactory::Instance().createInitialized(this, expr, + isDefault)); + double penalty_factor = std::stof(next_expr.terms()[1].str(), NULL); + c->setPenaltyFactor(penalty_factor); + this->addConstraint(std::move(c)); + } else { + auto c = std::unique_ptr<IConstraint>( + ConstraintFactory::Instance().createInitialized(this, expr, + isDefault)); + this->addConstraint(std::move(c)); + } + } else { + auto c = std::unique_ptr<IConstraint>( + ConstraintFactory::Instance().createInitialized(this, expr, + isDefault)); + this->addConstraint(std::move(c)); + } } } diff --git a/Framework/API/src/MatrixWorkspace.cpp b/Framework/API/src/MatrixWorkspace.cpp index 3ebe85d415cb33403a0520c76c61ca8dd773e2c7..1a22e83f8c0d389ada4736da45e78eb65075c028 100644 --- a/Framework/API/src/MatrixWorkspace.cpp +++ b/Framework/API/src/MatrixWorkspace.cpp @@ -1970,13 +1970,7 @@ void MatrixWorkspace::buildDefaultSpectrumDefinitions() { std::vector<SpectrumDefinition>(m_indexInfo->size())); return; } - size_t numberOfSpectra{0}; - if (detInfo.isScanning()) { - for (size_t i = 0; i < numberOfDetectors; ++i) - numberOfSpectra += detInfo.scanCount(i); - } else { - numberOfSpectra = numberOfDetectors; - } + size_t numberOfSpectra = numberOfDetectors * detInfo.scanCount(); if (numberOfSpectra != m_indexInfo->globalSize()) throw std::invalid_argument( "MatrixWorkspace: IndexInfo does not contain spectrum definitions so " @@ -1991,7 +1985,7 @@ void MatrixWorkspace::buildDefaultSpectrumDefinitions() { size_t specIndex = 0; size_t globalSpecIndex = 0; for (size_t detIndex = 0; detIndex < detInfo.size(); ++detIndex) { - for (size_t time = 0; time < detInfo.scanCount(detIndex); ++time) { + for (size_t time = 0; time < detInfo.scanCount(); ++time) { if (m_indexInfo->isOnThisPartition( Indexing::GlobalSpectrumIndex(globalSpecIndex++))) specDefs[specIndex++].add(detIndex, time); @@ -2020,7 +2014,7 @@ void MatrixWorkspace::rebuildDetectorIDGroupings() { const size_t timeIndex = index.second; if (detIndex >= allDetIDs.size()) { errorValue = ErrorCode::InvalidDetIndex; - } else if (timeIndex >= detInfo.scanCount(detIndex)) { + } else if (timeIndex >= detInfo.scanCount()) { errorValue = ErrorCode::InvalidTimeIndex; } else { detIDs.insert(allDetIDs[detIndex]); diff --git a/Framework/API/src/ParameterTie.cpp b/Framework/API/src/ParameterTie.cpp index 1ec738b66d69458484b795fa76556f6a7d988d33..892d308053870cad1fa079217419cbbd1bfbd1de 100644 --- a/Framework/API/src/ParameterTie.cpp +++ b/Framework/API/src/ParameterTie.cpp @@ -87,7 +87,8 @@ void ParameterTie::set(const std::string &expr) { } // Create the template m_expression - boost::regex rx(R"(\b(([[:alpha:]]|_)([[:alnum:]]|_|\.)*)\b(?!(\s*\()))"); + static const boost::regex rx( + R"(\b(([[:alpha:]]|_)([[:alnum:]]|_|\.)*)\b(?!(\s*\()))"); std::string input = expr; boost::smatch res; std::string::const_iterator start = input.begin(); @@ -148,7 +149,7 @@ std::string ParameterTie::asString(const IFunction *fun) const { ; } - boost::regex rx(std::string("#(\\d+)")); + static const boost::regex rx(std::string("#(\\d+)")); boost::smatch res; std::string::const_iterator start = m_expression.begin(); std::string::const_iterator end = m_expression.end(); diff --git a/Framework/API/src/ScriptBuilder.cpp b/Framework/API/src/ScriptBuilder.cpp index 78b1b98d2278c7986dfe109be0ee4f019c3550b9..d574b27246956b925e85832a1b038873da970ae7 100644 --- a/Framework/API/src/ScriptBuilder.cpp +++ b/Framework/API/src/ScriptBuilder.cpp @@ -264,8 +264,11 @@ const std::string ScriptBuilder::buildPropertyString( if (find(nonWorkspaceTypes.begin(), nonWorkspaceTypes.end(), propHistory.type()) != nonWorkspaceTypes.end() && propHistory.direction() == Direction::Output) { - g_log.debug() << "Ignoring property " << propHistory.name() << " of type " - << propHistory.type() << '\n'; + // If algs are to be ignored (Common use case is project recovery) ignore + if (m_algsToIgnore.size() == 0) { + g_log.debug() << "Ignoring property " << propHistory.name() + << " of type " << propHistory.type() << '\n'; + } // Handle numerical properties } else if (propHistory.type() == "number") { prop = propHistory.name() + "=" + propHistory.value(); diff --git a/Framework/API/src/SpectrumDetectorMapping.cpp b/Framework/API/src/SpectrumDetectorMapping.cpp index 292ca0db3b9b572c4b7ebce822a5e915d3cd7f50..9aa13acee4919810f68d0ce3c12b55e5b1d2577c 100644 --- a/Framework/API/src/SpectrumDetectorMapping.cpp +++ b/Framework/API/src/SpectrumDetectorMapping.cpp @@ -6,6 +6,7 @@ // SPDX - License - Identifier: GPL - 3.0 + #include "MantidAPI/SpectrumDetectorMapping.h" #include "MantidAPI/MatrixWorkspace.h" +#include <exception> namespace Mantid { namespace API { @@ -14,11 +15,11 @@ namespace API { * @throws std::invalid_argument if a null workspace pointer is passed in */ SpectrumDetectorMapping::SpectrumDetectorMapping( - const MatrixWorkspace *const workspace, bool useSpecNoIndex) + MatrixWorkspace_const_sptr workspace, const bool useSpecNoIndex) : m_indexIsSpecNo(useSpecNoIndex) { if (!workspace) { throw std::invalid_argument( - "SpectrumDetectorMapping: Null workspace pointer passed"); + "SpectrumDetectorMapping: Null shared workspace pointer passed"); } for (size_t i = 0; i < workspace->getNumberHistograms(); ++i) { diff --git a/Framework/API/src/SpectrumInfo.cpp b/Framework/API/src/SpectrumInfo.cpp index 6e135bb5b3609635b6626c9df3e9d16d28284969..3aab438e6157554b389c6b878ee64d7823423ace 100644 --- a/Framework/API/src/SpectrumInfo.cpp +++ b/Framework/API/src/SpectrumInfo.cpp @@ -190,13 +190,18 @@ SpectrumInfo::checkAndGetSpectrumDefinition(const size_t index) const { } // Begin method for iterator -SpectrumInfoIterator SpectrumInfo::begin() const { - return SpectrumInfoIterator(*this, 0); +SpectrumInfoIt SpectrumInfo::begin() { return SpectrumInfoIt(*this, 0); } + +// End method for iterator +SpectrumInfoIt SpectrumInfo::end() { return SpectrumInfoIt(*this, size()); } + +const SpectrumInfoConstIt SpectrumInfo::cbegin() const { + return SpectrumInfoConstIt(*this, 0); } // End method for iterator -SpectrumInfoIterator SpectrumInfo::end() const { - return SpectrumInfoIterator(*this, size()); +const SpectrumInfoConstIt SpectrumInfo::cend() const { + return SpectrumInfoConstIt(*this, size()); } } // namespace API diff --git a/Framework/API/src/WorkspaceHistory.cpp b/Framework/API/src/WorkspaceHistory.cpp index 0b9d4a99ac4361a6af4380bc23950ca3d3db30c4..46bda66cdc66ca7b7831040eb0dbb1bb343be723 100644 --- a/Framework/API/src/WorkspaceHistory.cpp +++ b/Framework/API/src/WorkspaceHistory.cpp @@ -11,9 +11,14 @@ #include "MantidKernel/EnvironmentHistory.h" #include "MantidKernel/StringTokenizer.h" #include "MantidKernel/Strings.h" +#include "MantidTypes/Core/DateAndTime.h" #include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/split.hpp> +#include <boost/functional/hash.hpp> +#include <boost/uuid/uuid.hpp> +#include <boost/uuid/uuid_generators.hpp> +#include <boost/uuid/uuid_io.hpp> #include "Poco/DateTime.h" #include <Poco/DateTimeParser.h> @@ -26,6 +31,25 @@ namespace API { namespace { /// static logger object Kernel::Logger g_log("WorkspaceHistory"); +struct AlgorithmHistorySearch { + bool operator()(const AlgorithmHistory_sptr &lhs, + const AlgorithmHistory_sptr &rhs) { + return (*lhs) < (*rhs); + } +}; +struct AlgorithmHistoryHasher { + size_t operator()(const AlgorithmHistory_sptr &x) const { + std::size_t nameAsSeed = std::hash<std::string>{}(x->name()); + boost::hash_combine(nameAsSeed, x->executionDate().totalNanoseconds()); + return nameAsSeed; + } +}; +struct AlgorithmHistoryComparator { + bool operator()(const AlgorithmHistory_sptr &a, + const AlgorithmHistory_sptr &b) const { + return a->uuid() == b->uuid(); + } +}; } // namespace /// Default Constructor @@ -64,12 +88,25 @@ void WorkspaceHistory::addHistory(const WorkspaceHistory &otherHistory) { // Merge the histories const AlgorithmHistories &otherAlgorithms = otherHistory.getAlgorithmHistories(); - m_algorithms.insert(otherAlgorithms.begin(), otherAlgorithms.end()); + + for (const auto &algHistory : otherAlgorithms) { + this->addHistory(algHistory); + } + + std::unordered_set<AlgorithmHistory_sptr, AlgorithmHistoryHasher, + AlgorithmHistoryComparator> + set; + for (auto i : m_algorithms) + set.insert(i); + m_algorithms.assign(set.begin(), set.end()); + std::sort(m_algorithms.begin(), m_algorithms.end(), AlgorithmHistorySearch()); } /// Append an AlgorithmHistory to this WorkspaceHistory void WorkspaceHistory::addHistory(AlgorithmHistory_sptr algHistory) { - m_algorithms.insert(std::move(algHistory)); + // Assume it is always sorted as algorithm history should only be inserted in + // the correct order + m_algorithms.emplace_back(std::move(algHistory)); } /* @@ -336,14 +373,16 @@ WorkspaceHistory::parseAlgorithmHistory(const std::string &rawData) { NAME = 0, //< algorithms name EXEC_TIME = 1, //< when the algorithm was run EXEC_DUR = 2, //< execution time for the algorithm - PARAMS = 3 //< the algorithm's parameters + UUID = 3, //< the universal unique id of the algorithm + PARAMS = 4 //< the algorithm's parameters }; std::vector<std::string> info; boost::split(info, rawData, boost::is_any_of("\n")); const size_t nlines = info.size(); - if (nlines < 4) { // ignore badly formed history entries + if (nlines < 4) { // ignore badly formed history entries still at 4 so that + // legacy files can be loaded, 5 is ideal for newer files throw std::runtime_error( "Malformed history record: Incorrect record size."); } @@ -362,16 +401,24 @@ WorkspaceHistory::parseAlgorithmHistory(const std::string &rawData) { // Get the execution date/time std::string date, time; getWordsInString(info[EXEC_TIME], dummy, dummy, date, time); - Poco::DateTime start_timedate; - // This is needed by the Poco parsing function - int tzdiff(-1); Mantid::Types::Core::DateAndTime utc_start; - if (!Poco::DateTimeParser::tryParse("%Y-%b-%d %H:%M:%S", date + " " + time, - start_timedate, tzdiff)) { - g_log.warning() << "Error parsing start time in algorithm history entry." - << "\n"; - utc_start = Types::Core::DateAndTime::defaultTime(); + // If not legacy version construct normally else Parse in the legacy data + if (std::isdigit(date[6])) { + Mantid::Types::Core::DateAndTime timeConstruction(date + "T" + time); + utc_start = timeConstruction; + } else { + Poco::DateTime start_timedate; + // This is needed by the Poco parsing function + int tzdiff(-1); + if (!Poco::DateTimeParser::tryParse("%Y-%b-%d %H:%M:%S", date + " " + time, + start_timedate, tzdiff)) { + g_log.warning() << "Error parsing start time in algorithm history entry." + << "\n"; + utc_start = Types::Core::DateAndTime::defaultTime(); + } + utc_start.set_from_time_t(start_timedate.timestamp().epochTime()); } + // Get the duration getWordsInString(info[EXEC_DUR], dummy, dummy, temp, dummy); double dur = boost::lexical_cast<double>(temp); @@ -380,16 +427,28 @@ WorkspaceHistory::parseAlgorithmHistory(const std::string &rawData) { << "\n"; dur = -1.0; } - // Convert the timestamp to time_t to DateAndTime - utc_start.set_from_time_t(start_timedate.timestamp().epochTime()); + + /// To allow legacy files we must check if it is parameters and set the + /// variables accordingly. If legacy generate a new UUID for it. + std::string uuid; + size_t paramNum; + if (info[3] != "Parameters:") { + uuid = info[UUID]; + uuid.erase(uuid.find("UUID: "), 6); + paramNum = PARAMS; + } else { + uuid = boost::uuids::to_string(boost::uuids::random_generator()()); + paramNum = 3; + } + // Create the algorithm history - API::AlgorithmHistory alg_hist(algName, version, utc_start, dur, + API::AlgorithmHistory alg_hist(algName, version, uuid, utc_start, dur, Algorithm::g_execCount); // Simulate running an algorithm ++Algorithm::g_execCount; // Add property information - for (size_t index = static_cast<size_t>(PARAMS) + 1; index < nlines; + for (size_t index = static_cast<size_t>(paramNum) + 1; index < nlines; ++index) { const std::string line = info[index]; std::string::size_type colon = line.find(':'); @@ -431,6 +490,5 @@ std::ostream &operator<<(std::ostream &os, const WorkspaceHistory &WH) { WH.printSelf(os); return os; } - } // namespace API } // namespace Mantid diff --git a/Framework/API/test/AlgorithmHistoryTest.h b/Framework/API/test/AlgorithmHistoryTest.h index 08d762d8e15bcb72f6543a23567e137b84085034..69d391a3554615b6f73460a0251b45015fbb2d19 100644 --- a/Framework/API/test/AlgorithmHistoryTest.h +++ b/Framework/API/test/AlgorithmHistoryTest.h @@ -49,7 +49,13 @@ public: std::ostringstream output; output.exceptions(std::ios::failbit | std::ios::badbit); TS_ASSERT_THROWS_NOTHING(output << AH); - TS_ASSERT_EQUALS(output.str(), m_correctOutput); + + // Remove UUID line from output + std::string outputStr = output.str(); + // 44 is the expected length of the UUID string + outputStr.erase(outputStr.find("UUID: "), 43); + + TS_ASSERT_EQUALS(outputStr, m_correctOutput); // Does it equal itself TS_ASSERT_EQUALS(AH, AH); } @@ -114,7 +120,13 @@ public: std::ostringstream output; output.exceptions(std::ios::failbit | std::ios::badbit); TS_ASSERT_THROWS_NOTHING(output << algHist); - TS_ASSERT_EQUALS(output.str(), m_correctOutput); + + // Remove UUID line from output + std::string outputStr = output.str(); + // 44 is the expected length of the UUID string + outputStr.erase(outputStr.find("UUID: "), 43); + + TS_ASSERT_EQUALS(outputStr, m_correctOutput); auto children = algHist.getChildHistories(); TS_ASSERT_EQUALS(children.size(), 3); @@ -183,8 +195,10 @@ private: AlgorithmHistory createTestHistory() { m_correctOutput = "Algorithm: testalg "; m_correctOutput += "v1\n"; - m_correctOutput += "Execution Date: 2008-Feb-29 09:54:49\n"; + m_correctOutput += "Execution Date: 2008-02-29 09:54:49\n"; m_correctOutput += "Execution Duration: 14 seconds\n"; + // This line is newer than the rest and cannot be predicted + // m_correctOutput += "UUID: 207ca8f8-fee0-49ce-86c8-7842a7313c2e\n"; m_correctOutput += "Parameters:\n"; m_correctOutput += " Name: arg1_param, "; m_correctOutput += "Value: y, "; diff --git a/Framework/API/test/CMakeLists.txt b/Framework/API/test/CMakeLists.txt index 5bfdb4dd813cccfdb6304fd945092e7e8ed85ac0..180ace293300b445be06c9de8d349522e5e3b7be 100644 --- a/Framework/API/test/CMakeLists.txt +++ b/Framework/API/test/CMakeLists.txt @@ -13,7 +13,10 @@ if ( CXXTEST_FOUND ) ../../TestHelpers/src/ParallelRunner.cpp ) cxxtest_add_test ( APITest ${TEST_FILES} ${GMOCK_TEST_FILES}) - target_link_libraries( APITest LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} ${MANTIDLIBS} + if ( WIN32 ) + set ( BCRYPT bcrypt ) + endif () + target_link_libraries( APITest LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} ${MANTIDLIBS} ${BCRYPT} Types API Nexus diff --git a/Framework/API/test/HistoryItemTest.h b/Framework/API/test/HistoryItemTest.h index 0fff1f92b7ea55013589482a1022ad3c7007fa31..1d740a141e25f040774b1439fbe8833b769df1ba 100644 --- a/Framework/API/test/HistoryItemTest.h +++ b/Framework/API/test/HistoryItemTest.h @@ -9,6 +9,9 @@ #include "MantidAPI/Algorithm.h" #include "MantidAPI/HistoryView.h" +#include <boost/uuid/uuid.hpp> +#include <boost/uuid/uuid_generators.hpp> +#include <boost/uuid/uuid_io.hpp> #include <cxxtest/TestSuite.h> using namespace Mantid::API; @@ -19,7 +22,9 @@ class HistoryItemTest : public CxxTest::TestSuite { public: void test_Minimum() { // not really much to test - AlgorithmHistory algHist("AnAlg", 1); + AlgorithmHistory algHist( + "AnAlg", 1, + boost::uuids::to_string(boost::uuids::random_generator()())); HistoryItem item(boost::make_shared<AlgorithmHistory>(algHist)); item.unrolled(true); diff --git a/Framework/API/test/ImplicitFunctionFactoryTest.h b/Framework/API/test/ImplicitFunctionFactoryTest.h index 2eb5f6aa8afcdcc8693729afb2a050fefc31cc8e..a83ebe21e68b5ad87f0c3d32a1bbb3475f515e6a 100644 --- a/Framework/API/test/ImplicitFunctionFactoryTest.h +++ b/Framework/API/test/ImplicitFunctionFactoryTest.h @@ -14,7 +14,6 @@ #include "MantidGeometry/MDGeometry/MDImplicitFunction.h" #include "MantidKernel/ConfigService.h" #include "MantidKernel/WarningSuppressions.h" -#include <boost/scoped_ptr.hpp> #include <boost/shared_ptr.hpp> #include <cxxtest/TestSuite.h> #include <gmock/gmock.h> diff --git a/Framework/API/test/MatrixWorkspaceTest.h b/Framework/API/test/MatrixWorkspaceTest.h index 44cc758ce210ee40c6d6f6bfa881880f140e92c1..8600668e23773c8c0a9b252873d5d964be923ef5 100644 --- a/Framework/API/test/MatrixWorkspaceTest.h +++ b/Framework/API/test/MatrixWorkspaceTest.h @@ -1654,15 +1654,17 @@ public: auto ws2 = makeWorkspaceWithDetectors(1, 1); auto &detInfo1 = ws1->mutableDetectorInfo(); auto &detInfo2 = ws2->mutableDetectorInfo(); + auto &compInfo1 = ws1->mutableComponentInfo(); + auto &compInfo2 = ws2->mutableComponentInfo(); + detInfo1.setPosition(0, {1, 0, 0}); detInfo2.setPosition(0, {2, 0, 0}); - detInfo1.setScanInterval(0, {10, 20}); - detInfo2.setScanInterval(0, {20, 30}); + compInfo1.setScanInterval({10, 20}); + compInfo2.setScanInterval({20, 30}); // Merge auto merged = WorkspaceFactory::Instance().create(ws1, 2); - auto &detInfo = merged->mutableDetectorInfo(); - detInfo.merge(detInfo2); + merged->mutableComponentInfo().merge(ws2->componentInfo()); // Setting IndexInfo without spectrum definitions will set up a 1:1 mapping // such that each spectrum corresponds to 1 time index of a detector. diff --git a/Framework/API/test/SpectrumDetectorMappingTest.h b/Framework/API/test/SpectrumDetectorMappingTest.h index 3205430a213846bcdfe6ba6627c5f4ea327b797a..cc77820b62162587c6de407f815295f9b195c4e5 100644 --- a/Framework/API/test/SpectrumDetectorMappingTest.h +++ b/Framework/API/test/SpectrumDetectorMappingTest.h @@ -12,6 +12,7 @@ #include "MantidAPI/MatrixWorkspace.h" #include "MantidAPI/SpectrumDetectorMapping.h" #include "MantidTestHelpers/FakeObjects.h" +#include <exception> using Mantid::API::MatrixWorkspace_sptr; using Mantid::API::SpectrumDetectorMapping; @@ -25,8 +26,9 @@ public: } static void destroySuite(SpectrumDetectorMappingTest *suite) { delete suite; } - void test_workspace_constructor_null_pointer() { - TS_ASSERT_THROWS(SpectrumDetectorMapping(nullptr), std::invalid_argument); + void test_workspace_constructor() { + MatrixWorkspace_const_sptr ws; + TS_ASSERT_THROWS(SpectrumDetectorMapping map(ws), std::invalid_argument); } void test_workspace_constructor_fills_map() { @@ -36,7 +38,7 @@ public: ws->getSpectrum(0).setDetectorIDs(std::set<detid_t>()); int detids[] = {10, 20}; ws->getSpectrum(2).setDetectorIDs(std::set<detid_t>(detids, detids + 2)); - SpectrumDetectorMapping map(ws.get()); + SpectrumDetectorMapping map(ws); TS_ASSERT(map.getDetectorIDsForSpectrumNo(1).empty()); auto idsFor2 = map.getDetectorIDsForSpectrumNo(2); @@ -149,8 +151,8 @@ public: } void test_getDetectorIDsForSpectrumNo() { - MatrixWorkspace_sptr ws = boost::make_shared<WorkspaceTester>(); - SpectrumDetectorMapping map(ws.get()); + MatrixWorkspace_const_sptr ws = boost::make_shared<WorkspaceTester>(); + SpectrumDetectorMapping map(ws); // The happy path is tested in the methods above. Just test invalid entry // here. TS_ASSERT_THROWS(map.getDetectorIDsForSpectrumNo(1), std::out_of_range); diff --git a/Framework/API/test/SpectrumInfoTest.h b/Framework/API/test/SpectrumInfoTest.h index bd965373c41888847ebc2098b071efdf88c29577..a78911299f3852592c39eec1f5c3ccabc95e5a77 100644 --- a/Framework/API/test/SpectrumInfoTest.h +++ b/Framework/API/test/SpectrumInfoTest.h @@ -497,28 +497,28 @@ public: void test_iterator_begin() { // Get the SpectrumInfo object const auto &spectrumInfo = m_workspace.spectrumInfo(); - auto iter = spectrumInfo.begin(); + auto iter = spectrumInfo.cbegin(); // Check we start at the correct place - TS_ASSERT(iter != spectrumInfo.end()); + TS_ASSERT(iter != spectrumInfo.cend()); } void test_iterator_end() { // Get the SpectrumInfo object const auto &spectrumInfo = m_workspace.spectrumInfo(); - auto iter = spectrumInfo.end(); + auto iter = spectrumInfo.cend(); // Check we start at the correct place - TS_ASSERT(iter != spectrumInfo.begin()); + TS_ASSERT(iter != spectrumInfo.cbegin()); } void test_iterator_increment_and_hasUniqueDetector() { // Get the SpectrumInfo object const auto &spectrumInfo = m_workspace.spectrumInfo(); - auto iter = spectrumInfo.begin(); + auto iter = spectrumInfo.cbegin(); // Check that we start at the beginning - TS_ASSERT(iter == spectrumInfo.begin()); + TS_ASSERT(iter == spectrumInfo.cbegin()); // Increment iterator and check hasUniqueDetector for (size_t i = 0; i < m_workspace.spectrumInfo().size(); ++i) { @@ -527,16 +527,16 @@ public: } // Check we've reached the end - TS_ASSERT(iter == spectrumInfo.end()); + TS_ASSERT(iter == spectrumInfo.cend()); } void test_iterator_decrement_and_hasUniqueDetector() { // Get the SpectrumInfo object const auto &spectrumInfo = m_workspace.spectrumInfo(); - auto iter = spectrumInfo.end(); + auto iter = spectrumInfo.cend(); // Check that we start at the end - TS_ASSERT(iter == spectrumInfo.end()); + TS_ASSERT(iter == spectrumInfo.cend()); // Decrement iterator and check hasUniqueDetector for (size_t i = m_workspace.spectrumInfo().size(); i > 0; --i) { @@ -545,13 +545,13 @@ public: } // Check we've reached the beginning - TS_ASSERT(iter == spectrumInfo.begin()); + TS_ASSERT(iter == spectrumInfo.cbegin()); } void test_iterator_advance_and_hasUniqueDetector() { // Get the SpectrumInfo object const auto &spectrumInfo = m_workspace.spectrumInfo(); - auto iter = spectrumInfo.begin(); + auto iter = spectrumInfo.cbegin(); // Advance 3 places std::advance(iter, 3); @@ -563,16 +563,16 @@ public: // Go to the start std::advance(iter, -1); - TS_ASSERT(iter == spectrumInfo.begin()); + TS_ASSERT(iter == spectrumInfo.cbegin()); } void test_copy_iterator_and_hasUniqueDetector() { // Get the SpectrumInfo object const auto &spectrumInfo = m_workspace.spectrumInfo(); - auto iter = spectrumInfo.begin(); + auto iter = spectrumInfo.cbegin(); // Create a copy - auto iterCopy = SpectrumInfoIterator(iter); + auto iterCopy = SpectrumInfoConstIt(iter); // Check TS_ASSERT_EQUALS(iter->hasUniqueDetector(), true); @@ -587,6 +587,14 @@ public: TS_ASSERT_EQUALS(iterCopy->hasUniqueDetector(), true); } + void test_mutating_via_writable_iterator() { + auto &spectrumInfo = m_workspace.mutableSpectrumInfo(); + auto it = spectrumInfo.begin(); + + it->setMasked(true); + TS_ASSERT(spectrumInfo.cbegin()->isMasked() == true); + } + private: WorkspaceTester m_workspace; WorkspaceTester m_workspaceNoInstrument; diff --git a/Framework/API/test/WorkspaceHistoryIOTest.h b/Framework/API/test/WorkspaceHistoryIOTest.h index 44a3d491695b898b0f8446626ab62c143e3f96e3..ba913301de704e83d39cf215158074a1bbca44c6 100644 --- a/Framework/API/test/WorkspaceHistoryIOTest.h +++ b/Framework/API/test/WorkspaceHistoryIOTest.h @@ -15,6 +15,9 @@ #include "MantidKernel/Property.h" #include "MantidTestHelpers/NexusTestHelper.h" #include "Poco/File.h" +#include <boost/uuid/uuid.hpp> +#include <boost/uuid/uuid_generators.hpp> +#include <boost/uuid/uuid_io.hpp> #include <cxxtest/TestSuite.h> using namespace Mantid::API; @@ -211,8 +214,10 @@ public: void test_SaveNexus() { WorkspaceHistory testHistory; for (int i = 1; i < 5; i++) { - AlgorithmHistory algHist("History" + boost::lexical_cast<std::string>(i), - 1, DateAndTime::defaultTime(), -1.0, i); + AlgorithmHistory algHist( + "History" + boost::lexical_cast<std::string>(i), 1, + boost::uuids::to_string(boost::uuids::random_generator()()), + DateAndTime::defaultTime(), -1.0, i); testHistory.addHistory(boost::make_shared<AlgorithmHistory>(algHist)); } @@ -248,10 +253,14 @@ public: void test_SaveNexus_NestedHistory() { WorkspaceHistory testHistory; - AlgorithmHistory algHist("ParentHistory", 1, DateAndTime::defaultTime(), - -1.0, 0); - AlgorithmHistory childHist("ChildHistory", 1, DateAndTime::defaultTime(), - -1.0, 1); + AlgorithmHistory algHist( + "ParentHistory", 1, + boost::uuids::to_string(boost::uuids::random_generator()()), + DateAndTime::defaultTime(), -1.0, 0); + AlgorithmHistory childHist( + "ChildHistory", 1, + boost::uuids::to_string(boost::uuids::random_generator()()), + DateAndTime::defaultTime(), -1.0, 1); algHist.addChildHistory(boost::make_shared<AlgorithmHistory>(childHist)); testHistory.addHistory(boost::make_shared<AlgorithmHistory>(algHist)); diff --git a/Framework/API/test/WorkspaceHistoryTest.h b/Framework/API/test/WorkspaceHistoryTest.h index 89944bd693d915901506605db7d5bcb28aef1703..f56b2a1d37605554870510aef46e330c4f13dba9 100644 --- a/Framework/API/test/WorkspaceHistoryTest.h +++ b/Framework/API/test/WorkspaceHistoryTest.h @@ -76,7 +76,8 @@ public: TS_ASSERT_EQUALS(history.size(), 0); TS_ASSERT_EQUALS(history.empty(), true); - AlgorithmHistory alg1("FirstAlgorithm", 2); + AlgorithmHistory alg1("FirstAlgorithm", 2, + "207ca8f8-fee0-49ce-86c8-7842a7313c2e"); alg1.addProperty("FirstAlgProperty", "1", false, Mantid::Kernel::Direction::Input); @@ -139,10 +140,18 @@ public: class WorkspaceHistoryTestPerformance : public CxxTest::TestSuite { public: + WorkspaceHistoryTestPerformance() { + constructAlgHistories1(); + constructAlgHistories2(); + } + + void setUp() override { m_wsHist.clearHistory(); } + void test_Wide_History() { int depth = 3; int width = 50; - auto algHist = boost::make_shared<AlgorithmHistory>("AnAlgorithm", 1); + auto algHist = boost::make_shared<AlgorithmHistory>( + "AnAlgorithm", 1, "207ca8f8-fee0-49ce-86c8-7842a7313c2e"); build_Algorithm_History(*algHist, width, depth); m_wsHist.addHistory(std::move(algHist)); } @@ -151,24 +160,73 @@ public: int depth = 10; int width = 3; - auto algHist = boost::make_shared<AlgorithmHistory>("AnAlgorithm", 1); + auto algHist = boost::make_shared<AlgorithmHistory>( + "AnAlgorithm", 1, "207ca8f8-fee0-49ce-86c8-7842a7313c2e"); build_Algorithm_History(*algHist, width, depth); m_wsHist.addHistory(std::move(algHist)); } + void test_standard_insertion_500000_times() { + for (auto i = 0u; i < 500000; ++i) { + m_wsHist.addHistory(m_1000000Histories1[i]); + } + } + + void test_standard_insertion_1000000_times() { + for (auto i = 0u; i < 1000000; ++i) { + m_wsHist.addHistory(m_1000000Histories1[i]); + } + } + + void test_adding_1000000_to_500000_workspace_histories() { + // It's hard to test this without doing this bit + for (auto i = 0u; i < 500000; ++i) { + m_wsHist.addHistory(m_1000000Histories1[i]); + } + // The actual test + m_wsHist.addHistory(m_1000000Histories2); + } + + void test_adding_1000000_to_1000000_workspace_histories() { + // It's hard to test this without doing this bit + for (auto i = 0u; i < 1000000; ++i) { + m_wsHist.addHistory(m_1000000Histories1[i]); + } + // The actual test + m_wsHist.addHistory(m_1000000Histories2); + } + +private: void build_Algorithm_History(AlgorithmHistory &parent, int width, int depth = 0) { if (depth > 0) { for (int i = 0; i < width; ++i) { - auto algHist = boost::make_shared<AlgorithmHistory>("AnAlgorithm", 1); + auto algHist = boost::make_shared<AlgorithmHistory>( + "AnAlgorithm", 1, "207ca8f8-fee0-49ce-86c8-7842a7313c2e"); build_Algorithm_History(*algHist, width, depth - 1); parent.addChildHistory(std::move(algHist)); } } } -private: + void constructAlgHistories1() { + for (auto i = 1u; i < 1000001; ++i) { + auto algHist = boost::make_shared<AlgorithmHistory>( + "AnAlgorithm", i, "207ca8f8-fee0-49ce-86c8-7842a7313c2e"); + m_1000000Histories1.emplace_back(std::move(algHist)); + } + } + void constructAlgHistories2() { + for (auto i = 1000001u; i < 1000001; ++i) { + auto algHist = boost::make_shared<AlgorithmHistory>( + "AnAlgorithm", i, "207ca8f8-fee0-49ce-86c8-7842a7313c2e"); + m_1000000Histories2.addHistory(std::move(algHist)); + } + } + Mantid::API::WorkspaceHistory m_wsHist; + std::vector<AlgorithmHistory_sptr> m_1000000Histories1; + WorkspaceHistory m_1000000Histories2; }; #endif diff --git a/Framework/Algorithms/CMakeLists.txt b/Framework/Algorithms/CMakeLists.txt index d7c643cdd072ce4de5db4cbe7a9bfbbf3e7a9a29..00b6f25d0e4e30b49aecf9a0fec1b12a90ce3aec 100644 --- a/Framework/Algorithms/CMakeLists.txt +++ b/Framework/Algorithms/CMakeLists.txt @@ -18,12 +18,13 @@ set ( SRC_FILES src/Bin2DPowderDiffraction.cpp src/BinaryOperateMasks.cpp src/BinaryOperation.cpp + src/CalculateCarpenterSampleCorrection.cpp src/CalculateCountRate.cpp src/CalculateDIFC.cpp + src/CalculateDynamicRange.cpp src/CalculateEfficiency.cpp src/CalculateFlatBackground.cpp src/CalculateIqt.cpp - src/CalculateCarpenterSampleCorrection.cpp src/CalculatePolynomialBackground.cpp src/CalculateSlits.cpp src/CalculateTransmission.cpp @@ -177,6 +178,7 @@ set ( SRC_FILES src/MagFormFactorCorrection.cpp src/MaskBins.cpp src/MaskBinsFromTable.cpp + src/MaskBinsIf.cpp src/MaskDetectorsIf.cpp src/MaskInstrument.cpp src/MatrixWorkspaceAccess.cpp @@ -238,6 +240,7 @@ set ( SRC_FILES src/RebinToWorkspace.cpp src/Rebunch.cpp src/RecordPythonScript.cpp + src/ReflectometryBeamStatistics.cpp src/ReflectometryMomentumTransfer.cpp src/ReflectometryReductionOne2.cpp src/ReflectometryReductionOneAuto2.cpp @@ -348,12 +351,13 @@ set ( INC_FILES inc/MantidAlgorithms/BinaryOperateMasks.h inc/MantidAlgorithms/BinaryOperation.h inc/MantidAlgorithms/BoostOptionalToAlgorithmProperty.h + inc/MantidAlgorithms/CalculateCarpenterSampleCorrection.h inc/MantidAlgorithms/CalculateCountRate.h inc/MantidAlgorithms/CalculateDIFC.h + inc/MantidAlgorithms/CalculateDynamicRange.h inc/MantidAlgorithms/CalculateEfficiency.h inc/MantidAlgorithms/CalculateFlatBackground.h inc/MantidAlgorithms/CalculateIqt.h - inc/MantidAlgorithms/CalculateCarpenterSampleCorrection.h inc/MantidAlgorithms/CalculatePolynomialBackground.h inc/MantidAlgorithms/CalculateSlits.h inc/MantidAlgorithms/CalculateTransmission.h @@ -508,6 +512,7 @@ set ( INC_FILES inc/MantidAlgorithms/MagFormFactorCorrection.h inc/MantidAlgorithms/MaskBins.h inc/MantidAlgorithms/MaskBinsFromTable.h + inc/MantidAlgorithms/MaskBinsIf.h inc/MantidAlgorithms/MaskDetectorsIf.h inc/MantidAlgorithms/MaskInstrument.h inc/MantidAlgorithms/MatrixWorkspaceAccess.h @@ -572,6 +577,7 @@ set ( INC_FILES inc/MantidAlgorithms/RebinToWorkspace.h inc/MantidAlgorithms/Rebunch.h inc/MantidAlgorithms/RecordPythonScript.h + inc/MantidAlgorithms/ReflectometryBeamStatistics.h inc/MantidAlgorithms/ReflectometryMomentumTransfer.h inc/MantidAlgorithms/ReflectometryReductionOne2.h inc/MantidAlgorithms/ReflectometryReductionOneAuto2.h @@ -695,6 +701,7 @@ set ( TEST_FILES CalculateCarpenterSampleCorrectionTest.h CalculateCountRateTest.h CalculateDIFCTest.h + CalculateDynamicRangeTest.h CalculateEfficiencyTest.h CalculateFlatBackgroundTest.h CalculateIqtTest.h @@ -834,6 +841,7 @@ set ( TEST_FILES HyspecScharpfCorrectionTest.h IQTransformTest.h IdentifyNoisyDetectorsTest.h + IndirectFitDataCreationHelperTest.h IntegrateByComponentTest.h IntegrateEPPTest.h IntegrationTest.h @@ -847,6 +855,7 @@ set ( TEST_FILES MCInteractionVolumeTest.h MagFormFactorCorrectionTest.h MaskBinsFromTableTest.h + MaskBinsIfTest.h MaskBinsTest.h MaskDetectorsIfTest.h MaskInstrumentTest.h @@ -906,6 +915,7 @@ set ( TEST_FILES RebinToWorkspaceTest.h RebunchTest.h RectangularBeamProfileTest.h + ReflectometryBeamStatisticsTest.h ReflectometryMomentumTransferTest.h ReflectometryReductionOne2Test.h ReflectometryReductionOneAuto2Test.h diff --git a/Framework/Algorithms/inc/MantidAlgorithms/CalculateDynamicRange.h b/Framework/Algorithms/inc/MantidAlgorithms/CalculateDynamicRange.h new file mode 100644 index 0000000000000000000000000000000000000000..2b06f2579a288acada41416071847c10467914c4 --- /dev/null +++ b/Framework/Algorithms/inc/MantidAlgorithms/CalculateDynamicRange.h @@ -0,0 +1,35 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_ALGORITHMS_CALCULATEDYNAMICRANGE_H_ +#define MANTID_ALGORITHMS_CALCULATEDYNAMICRANGE_H_ + +#include "MantidAPI/Algorithm.h" +#include "MantidAlgorithms/DllConfig.h" + +namespace Mantid { +namespace Algorithms { + +/** CalculateDynamicRange + + Calculates the Qmin and Qmax of SANS workspace, sets to sample logs. +*/ +class MANTID_ALGORITHMS_DLL CalculateDynamicRange : public API::Algorithm { +public: + const std::string name() const override; + int version() const override; + const std::string category() const override; + const std::string summary() const override; + +private: + void init() override; + void exec() override; +}; + +} // namespace Algorithms +} // namespace Mantid + +#endif /* MANTID_ALGORITHMS_CALCULATEDYNAMICRANGE_H_ */ diff --git a/Framework/Algorithms/inc/MantidAlgorithms/EditInstrumentGeometry.h b/Framework/Algorithms/inc/MantidAlgorithms/EditInstrumentGeometry.h index 2b69c8957b0db8e2581f27bbc16ecf893af07828..a177781a49dc75c075b5fb26165721423eb2862b 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/EditInstrumentGeometry.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/EditInstrumentGeometry.h @@ -23,8 +23,8 @@ public: const std::string name() const override; /// Summary of algorithms purpose const std::string summary() const override { - return "The edit or added information will be attached to a Workspace. " - "Currently it is in an overwrite mode only."; + return "Adds a new instrument or edits an existing one; " + "currently works in an overwrite mode only."; } /// Algorithm's category for identification overriding a virtual method diff --git a/Framework/Algorithms/inc/MantidAlgorithms/MaskBinsIf.h b/Framework/Algorithms/inc/MantidAlgorithms/MaskBinsIf.h new file mode 100644 index 0000000000000000000000000000000000000000..beedad799e2b665b179671ddedcc542cd677cff3 --- /dev/null +++ b/Framework/Algorithms/inc/MantidAlgorithms/MaskBinsIf.h @@ -0,0 +1,39 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_ALGORITHMS_MASKBINSIF_H_ +#define MANTID_ALGORITHMS_MASKBINSIF_H_ + +#include "MantidAPI/Algorithm.h" +#include "MantidAlgorithms/DllConfig.h" + +namespace Mantid { +namespace Algorithms { + +/** MaskBinsIf : Masks bins based on muparser expression + */ +class MANTID_ALGORITHMS_DLL MaskBinsIf : public API::Algorithm { +public: + const std::string name() const override { return "MaskBinsIf"; } + int version() const override { return 1; } + const std::string category() const override { return "Transforms\\Masking"; } + std::map<std::string, std::string> validateInputs() override; + const std::string summary() const override { + return "Masks bins based on muparser expression"; + } + const std::vector<std::string> seeAlso() const override { + return {"MaskBins"}; + } + +private: + void init() override; + void exec() override; +}; + +} // namespace Algorithms +} // namespace Mantid + +#endif /* MANTID_ALGORITHMS_MASKBINSIF_H_ */ diff --git a/Framework/Algorithms/inc/MantidAlgorithms/MergeRuns.h b/Framework/Algorithms/inc/MantidAlgorithms/MergeRuns.h index 7985cd5b6ddfa64b9242bfd322efb59ba1150e99..257bc4f7fae14e2fd8ea716b0dc6523dfd04edad 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/MergeRuns.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/MergeRuns.h @@ -150,7 +150,6 @@ private: std::vector<SpectrumDefinition> buildScanIntervals(const std::vector<SpectrumDefinition> &addeeSpecDefs, const Geometry::DetectorInfo &addeeDetInfo, - const Geometry::DetectorInfo &outDetInfo, const Geometry::DetectorInfo &newOutDetInfo); }; diff --git a/Framework/Algorithms/inc/MantidAlgorithms/NormaliseToMonitor.h b/Framework/Algorithms/inc/MantidAlgorithms/NormaliseToMonitor.h index dc24dae83f9b988b88cc5fe7dce47493fdfc563c..eff6639e7c14588d9b61a95ee32978da238ae3c0 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/NormaliseToMonitor.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/NormaliseToMonitor.h @@ -120,7 +120,7 @@ private: double m_integrationMin = EMPTY_DBL(); /// The upper bound of the integration range double m_integrationMax = EMPTY_DBL(); - bool m_syncScanInput; + bool m_scanInput; std::vector<size_t> m_workspaceIndexes; }; diff --git a/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryBeamStatistics.h b/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryBeamStatistics.h new file mode 100644 index 0000000000000000000000000000000000000000..d51317c7a58077fc45dd7d55edcb259b773b393c --- /dev/null +++ b/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryBeamStatistics.h @@ -0,0 +1,91 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_ALGORITHMS_REFLECTOMETRYBEAMSTATISTICS_H_ +#define MANTID_ALGORITHMS_REFLECTOMETRYBEAMSTATISTICS_H_ + +#include "MantidAPI/Algorithm.h" +#include "MantidAlgorithms/DllConfig.h" +#include "MantidGeometry/Instrument_fwd.h" + +namespace Mantid { +namespace Algorithms { + +/** Calculates statistical quantities of a reflectometry workspace. + */ +class MANTID_ALGORITHMS_DLL ReflectometryBeamStatistics + : public API::Algorithm { +public: + struct LogEntry { + const static std::string BEAM_RMS_VARIATION; + const static std::string BENT_SAMPLE; + const static std::string FIRST_SLIT_ANGULAR_SPREAD; + const static std::string INCIDENT_ANGULAR_SPREAD; + const static std::string SAMPLE_WAVINESS; + const static std::string SECOND_SLIT_ANGULAR_SPREAD; + }; + static double slitSeparation(Geometry::Instrument_const_sptr instrument, + const std::string &slit1Name, + const std::string &slit2Name); + const std::string name() const override; + int version() const override; + const std::string category() const override; + const std::string summary() const override; + const std::vector<std::string> seeAlso() const override; + +private: + struct Statistics { + double beamRMSVariation{0.}; + bool bentSample{false}; + double firstSlitAngularSpread{0.}; + double incidentAngularSpread{0.}; + double sampleWaviness{0.}; + double secondSlitAngularSpread{0.}; + }; + struct Setup { + double detectorResolution{0.}; + size_t foregroundStart{0}; + size_t foregroundEnd{0}; + size_t directForegroundStart{0}; + size_t directForegroundEnd{0}; + double l2{0.}; + double directL2{0.}; + double pixelSize{0.}; + double slit1Slit2Distance{0.}; + double slit1Size{0.}; + double slit1SizeDirectBeam{0.}; + double slit2SampleDistance{0.}; + double slit2Size{0.}; + double slit2SizeDirectBeam{0.}; + }; + void init() override; + void exec() override; + std::map<std::string, std::string> validateInputs() override; + double beamRMSVariation(API::MatrixWorkspace_sptr &ws, const size_t start, + const size_t end); + static bool bentSample(const Setup &setup, const double sampleWaviness, + const double firstSlitAngularSpread); + const Setup createSetup(const API::MatrixWorkspace &ws, + const API::MatrixWorkspace &directWS); + static double detectorAngularResolution(const Setup &setup, + const double incidentFWHM); + static double firstSlitAngularSpread(const Setup &setup); + double incidentAngularSpread(const Setup &setup); + double interslitDistance(const API::MatrixWorkspace &ws); + static void rmsVariationToLogs(API::MatrixWorkspace &ws, + const double variation); + double sampleWaviness(const Setup &setup, const double beamFWHM, + const double directBeamFWHM, const double incidentFWHM); + double secondSlitAngularSpread(const Setup &setup); + double slitSize(const API::MatrixWorkspace &ws, const std::string &logEntry); + static void statisticsToLogs(API::MatrixWorkspace &ws, + const Statistics &statistics); +}; + +} // namespace Algorithms +} // namespace Mantid + +#endif /* MANTID_ALGORITHMS_REFLECTOMETRYBEAMSTATISTICS_H_ */ diff --git a/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryMomentumTransfer.h b/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryMomentumTransfer.h index 809fa207170188cd2b7c14e82253adf799c1c950..2a4cacd55db5cf68b8268a4d52195ba95803948d 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryMomentumTransfer.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryMomentumTransfer.h @@ -28,6 +28,12 @@ public: private: enum class SumType { LAMBDA, Q }; + struct Beam { + double incidentAngularSpread{0.}; + double firstSlitAngularSpread{0.}; + double secondSlitAngularSpread{0.}; + double sampleWaviness{0.}; + }; struct Setup { double chopperOpening{0.}; double chopperPairDistance{0.}; @@ -36,10 +42,9 @@ private: double detectorResolution{0.}; size_t foregroundStart{0}; size_t foregroundEnd{0}; - size_t directForegroundStart{0}; - size_t directForegroundEnd{0}; + double l1{0.}; + double l2{0.}; double pixelSize{0.}; - bool polarized{false}; double slit1Slit2Distance{0.}; double slit1Size{0.}; double slit1SizeDirectBeam{0.}; @@ -49,37 +54,22 @@ private: SumType sumType{SumType::LAMBDA}; double tofChannelWidth{0.}; }; + void init() override; + std::map<std::string, std::string> validateInputs() override; void exec() override; - double angularResolutionSquared(API::MatrixWorkspace_sptr &ws, - const API::MatrixWorkspace &directWS, - const size_t wsIndex, const Setup &setup, - const double beamFWHM, - const double directBeamFWHM, - const double incidentFWHM, - const double slit1FWHM); - double beamRMSVariation(API::MatrixWorkspace_sptr &ws, const size_t start, - const size_t end); + static void addResolutionDX(const API::MatrixWorkspace &inWS, + API::MatrixWorkspace &outWS, const Setup &setup, + const Beam &beam); + static double angularResolutionSquared(const API::MatrixWorkspace &ws, + const Setup &setup, const Beam &beam); void convertToMomentumTransfer(API::MatrixWorkspace_sptr &ws); - double detectorAngularResolution(const API::MatrixWorkspace &ws, - const size_t wsIndex, const Setup &setup, - const double incidentFWHM); - const Setup createSetup(const API::MatrixWorkspace &ws, - const API::MatrixWorkspace &directWS); - double incidentAngularSpread(const Setup &setup); + static const Beam createBeamStatistics(const API::MatrixWorkspace &ws); + const Setup createSetup(const API::MatrixWorkspace &ws); double interslitDistance(const API::MatrixWorkspace &ws); - double sampleWaviness(API::MatrixWorkspace_sptr &ws, - const API::MatrixWorkspace &directWS, - const size_t wsIndex, const Setup &setup, - const double beamFWHM, const double directBeamFWHM, - const double incidentFWHM); - double slit1AngularSpread(const Setup &setup); - double slit2AngularSpread(const API::MatrixWorkspace &ws, - const size_t wsIndex, const Setup &setup); double slitSize(const API::MatrixWorkspace &ws, const std::string &logEntry); - double wavelengthResolutionSquared(const API::MatrixWorkspace &ws, - const size_t wsIndex, const Setup &setup, - const double wavelength); + static double wavelengthResolutionSquared(const Setup &setup, + const double wavelength); }; } // namespace Algorithms diff --git a/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOne2.h b/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOne2.h index 1a4ce39b2a3b295c9a5a8ad349ff37dde60504b1..12959e262a19559a1ea5ddf070996d9f4b4ddac6 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOne2.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOne2.h @@ -130,8 +130,7 @@ private: const bool outerCorners = true); // Check whether two spectrum maps match void verifySpectrumMaps(API::MatrixWorkspace_const_sptr ws1, - API::MatrixWorkspace_const_sptr ws2, - const bool severe); + API::MatrixWorkspace_const_sptr ws2); // Find and cache constants void findDetectorGroups(); diff --git a/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOneAuto2.h b/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOneAuto2.h index b56d7612a58da101c17d364a8e4a48cdb45b1c5c..7b59d63b16054bfb3592c4d3021341e0249d4fba 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOneAuto2.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOneAuto2.h @@ -41,16 +41,13 @@ private: void setDefaultOutputWorkspaceNames(); /// Get the name of the detectors of interest based on processing instructions std::vector<std::string> - getDetectorNames(const std::string &instructions, - Mantid::API::MatrixWorkspace_sptr inputWS); + getDetectorNames(Mantid::API::MatrixWorkspace_sptr inputWS); /// Correct detector positions vertically Mantid::API::MatrixWorkspace_sptr - correctDetectorPositions(const std::string &instructions, - Mantid::API::MatrixWorkspace_sptr inputWS, + correctDetectorPositions(Mantid::API::MatrixWorkspace_sptr inputWS, const double twoTheta); /// Calculate theta - double calculateTheta(const std::string &instructions, - Mantid::API::MatrixWorkspace_sptr inputWS); + double calculateTheta(Mantid::API::MatrixWorkspace_sptr inputWS); /// Rebin and scale a workspace in Q Mantid::API::MatrixWorkspace_sptr rebinAndScale(Mantid::API::MatrixWorkspace_sptr inputWS, double theta, diff --git a/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryWorkflowBase2.h b/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryWorkflowBase2.h index b387a69f80e57d5ee78964be9b2e7b18c9244ffa..db477696fabddb1ee0c6027bb9537aee657517c1 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryWorkflowBase2.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryWorkflowBase2.h @@ -11,6 +11,9 @@ #include "MantidAPI/MatrixWorkspace_fwd.h" #include "MantidGeometry/Instrument_fwd.h" +using namespace Mantid::API; +using namespace Mantid::Kernel; +using namespace Mantid::Geometry; namespace Mantid { namespace Algorithms { @@ -69,10 +72,9 @@ protected: populateMonitorProperties(Mantid::API::IAlgorithm_sptr alg, Mantid::Geometry::Instrument_const_sptr instrument); /// Populate processing instructions - std::string populateProcessingInstructions( - Mantid::API::IAlgorithm_sptr alg, - Mantid::Geometry::Instrument_const_sptr instrument, - Mantid::API::MatrixWorkspace_sptr inputWS) const; + std::string + findProcessingInstructions(Mantid::Geometry::Instrument_const_sptr instrument, + Mantid::API::MatrixWorkspace_sptr inputWS) const; /// Populate transmission properties bool populateTransmissionProperties(Mantid::API::IAlgorithm_sptr alg) const; /// Find theta from a named log value @@ -80,6 +82,28 @@ protected: const std::string &logName); // Retrieve the run number from the logs of the input workspace. std::string getRunNumber(Mantid::API::MatrixWorkspace const &ws) const; + + void convertProcessingInstructions(Instrument_const_sptr instrument, + MatrixWorkspace_sptr inputWS); + void convertProcessingInstructions(MatrixWorkspace_sptr inputWS); + std::string m_processingInstructionsWorkspaceIndex; + std::string m_processingInstructions; + +protected: + std::string + convertToSpectrumNumber(const std::string &workspaceIndex, + Mantid::API::MatrixWorkspace_const_sptr ws) const; + + std::string convertProcessingInstructionsToWorkspaceIndices( + const std::string &instructions, + Mantid::API::MatrixWorkspace_const_sptr ws) const; + + std::string convertToWorkspaceIndex(const std::string &spectrumNumber, + MatrixWorkspace_const_sptr ws) const; + + std::string convertProcessingInstructionsToSpectrumNumbers( + const std::string &instructions, + Mantid::API::MatrixWorkspace_const_sptr ws) const; }; } // namespace Algorithms } // namespace Mantid diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SpecularReflectionPositionCorrect2.h b/Framework/Algorithms/inc/MantidAlgorithms/SpecularReflectionPositionCorrect2.h index e2e1c34902c9b28efc390a8f0c9a3fe8ee179cbf..865d22051185e7fbbe2cec54ff9a06ae68cb6e8a 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/SpecularReflectionPositionCorrect2.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/SpecularReflectionPositionCorrect2.h @@ -9,13 +9,23 @@ #include "MantidAPI/Algorithm.h" +#include "MantidGeometry/IDTypes.h" + namespace Mantid { +namespace Geometry { +class Instrument; +class ReferenceFrame; +} // namespace Geometry +namespace Kernel { +class V3D; +} namespace Algorithms { -/** SpecularReflectionPositionCorrect : Algorithm to perform vertical position +/** SpecularReflectionPositionCorrect : Algorithm to perform position corrections based on the specular reflection condition. Version 2. */ -class DLLExport SpecularReflectionPositionCorrect2 : public API::Algorithm { +class DLLExport SpecularReflectionPositionCorrect2 final + : public API::Algorithm { public: /// Name of this algorithm const std::string name() const override; @@ -24,14 +34,35 @@ public: /// Version int version() const override; const std::vector<std::string> seeAlso() const override { - return {"SpecularReflectionCalculateTheta"}; + return {"SpecularReflectionCalculateTheta, FindReflectometryLines"}; } /// Category const std::string category() const override; private: void init() override; + std::map<std::string, std::string> validateInputs() override; void exec() override; + void correctDetectorPosition(API::MatrixWorkspace_sptr &outWS, + const std::string &detectorName, + const detid_t detectorID, + const double twoThetaInRad, + const std::string &correctionType, + const Geometry::ReferenceFrame &referenceFrame, + const Kernel::V3D &samplePosition, + const Kernel::V3D &sampleToDetector, + const double beamOffsetOld); + static Kernel::V3D declareDetectorPosition(const Geometry::Instrument &inst, + const std::string &detectorName, + const detid_t detectorID); + Kernel::V3D declareSamplePosition(const API::MatrixWorkspace &ws); + double twoThetaFromProperties(const API::MatrixWorkspace &inWS, + const double l2); + double twoThetaFromDirectLine(const std::string &detectorName, + const detid_t detectorID, + const Kernel::V3D &samplePosition, + const double l2, const Kernel::V3D &alongDir, + const double beamOffset); }; } // namespace Algorithms diff --git a/Framework/Algorithms/inc/MantidAlgorithms/Stitch1DMany.h b/Framework/Algorithms/inc/MantidAlgorithms/Stitch1DMany.h index a760682562595430dabcabcb147550286d0cb255..2af82cb9170eed4888b904c0075215ed6169c8d5 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/Stitch1DMany.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/Stitch1DMany.h @@ -43,7 +43,8 @@ public: /// Performs the Stitch1DMany algorithm at a specific period void doStitch1DMany(const size_t period, const bool useManualScaleFactors, std::string &outName, - std::vector<double> &outScaleFactors); + std::vector<double> &outScaleFactors, + const bool storeInADS = true); private: /// Overwrites Algorithm method. diff --git a/Framework/Algorithms/src/AddSampleLog.cpp b/Framework/Algorithms/src/AddSampleLog.cpp index a6d5f6e8db73bfb3cb7282781c8a054fedf03fc2..8e338df8a7a4ae099c5eeccebd52fcacf3d3a0fb 100644 --- a/Framework/Algorithms/src/AddSampleLog.cpp +++ b/Framework/Algorithms/src/AddSampleLog.cpp @@ -191,8 +191,8 @@ void AddSampleLog::addSingleValueProperty(Run &theRun, // dblVal = 0.; } theRun.addLogData(new PropertyWithValue<double>(propName, dblVal)); - g_log.warning() << "added property " << propName << " with value " << dblVal - << "\n"; + g_log.information() << "added property " << propName << " with value " + << dblVal << "\n"; } // add unit diff --git a/Framework/Algorithms/src/CalculateDynamicRange.cpp b/Framework/Algorithms/src/CalculateDynamicRange.cpp new file mode 100644 index 0000000000000000000000000000000000000000..969eabb1c582e50d01c15125749641eb044f83d1 --- /dev/null +++ b/Framework/Algorithms/src/CalculateDynamicRange.cpp @@ -0,0 +1,99 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidAlgorithms/CalculateDynamicRange.h" +#include "MantidAPI/Run.h" +#include "MantidAPI/SpectrumInfo.h" +#include "MantidAPI/WorkspaceUnitValidator.h" + +namespace { +/** + * @param lambda : wavelength in Angstroms + * @param twoTheta : twoTheta in degreess + * @return Q : momentum transfer [Aˆ-1] + */ +double calculateQ(const double lambda, const double twoTheta) { + return (4 * M_PI * std::sin(twoTheta * (M_PI / 180) / 2)) / (lambda); +} +} // namespace + +namespace Mantid { +namespace Algorithms { + +using Mantid::API::MatrixWorkspace; +using Mantid::API::Run; +using Mantid::API::SpectrumInfo; +using Mantid::API::WorkspaceProperty; +using Mantid::API::WorkspaceUnitValidator; +using Mantid::Kernel::Direction; + +// Register the algorithm into the AlgorithmFactory +DECLARE_ALGORITHM(CalculateDynamicRange) + +//---------------------------------------------------------------------------------------------- + +/// Algorithms name for identification. @see Algorithm::name +const std::string CalculateDynamicRange::name() const { + return "CalculateDynamicRange"; +} + +/// Algorithm's version for identification. @see Algorithm::version +int CalculateDynamicRange::version() const { return 1; } + +/// Algorithm's category for identification. @see Algorithm::category +const std::string CalculateDynamicRange::category() const { + return "Utility\\Workspaces"; +} + +/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary +const std::string CalculateDynamicRange::summary() const { + return "Calculates and sets Qmin and Qmax of a SANS workspace"; +} + +//---------------------------------------------------------------------------------------------- +/** Initialize the algorithm's properties. + */ +void CalculateDynamicRange::init() { + auto unitValidator = boost::make_shared<WorkspaceUnitValidator>("Wavelength"); + declareProperty(Kernel::make_unique<WorkspaceProperty<MatrixWorkspace>>( + "Workspace", "", Direction::InOut, unitValidator), + "An input workspace."); +} + +//---------------------------------------------------------------------------------------------- +/** Execute the algorithm. + */ +void CalculateDynamicRange::exec() { + API::MatrixWorkspace_sptr workspace = getProperty("Workspace"); + double min = std::numeric_limits<double>::max(), + max = std::numeric_limits<double>::min(); + const int64_t nHist = static_cast<int64_t>(workspace->getNumberHistograms()); + const auto &spectrumInfo = workspace->spectrumInfo(); + PARALLEL_FOR_NO_WSP_CHECK() + for (int64_t i = 0; i < nHist; ++i) { + const size_t index = static_cast<size_t>(i); + if (!spectrumInfo.isMonitor(index) && !spectrumInfo.isMasked(index)) { + const auto &lambdaBinning = workspace->x(index); + const Kernel::V3D detPos = spectrumInfo.position(index); + double r, theta, phi; + detPos.getSpherical(r, theta, phi); + const double v1 = calculateQ(lambdaBinning.front(), theta); + const double v2 = calculateQ(lambdaBinning.back(), theta); + PARALLEL_CRITICAL(CalculateDynamicRange) { + min = std::min(min, std::min(v1, v2)); + max = std::max(max, std::max(v1, v2)); + } + } + } + g_log.information("Calculated QMin = " + std::to_string(min)); + g_log.information("Calculated QMax = " + std::to_string(max)); + auto &run = workspace->mutableRun(); + run.addProperty<double>("qmin", min, true); + run.addProperty<double>("qmax", max, true); +} + +} // namespace Algorithms +} // namespace Mantid diff --git a/Framework/Algorithms/src/CopyDetectorMapping.cpp b/Framework/Algorithms/src/CopyDetectorMapping.cpp index 0ed43f757822ab0f672c6c495dd6b4787814bb05..9658d5faff93a7f6b36a115ae1ff0cbf0f7bdb74 100644 --- a/Framework/Algorithms/src/CopyDetectorMapping.cpp +++ b/Framework/Algorithms/src/CopyDetectorMapping.cpp @@ -40,7 +40,7 @@ void CopyDetectorMapping::exec() { bool indexBySpecNumber = getProperty("IndexBySpectrumNumber"); // Copy detector mapping - SpectrumDetectorMapping detMap(wsToMatch.get(), indexBySpecNumber); + SpectrumDetectorMapping detMap(wsToMatch, indexBySpecNumber); wsToRemap->updateSpectraUsing(detMap); setProperty("WorkspaceToRemap", wsToRemap); diff --git a/Framework/Algorithms/src/CreateTransmissionWorkspace2.cpp b/Framework/Algorithms/src/CreateTransmissionWorkspace2.cpp index 20ee1aeb340ab4a13cc6a2aef10b6015ae315e7a..4195607677d2657749daca59e7139003a7c9f886 100644 --- a/Framework/Algorithms/src/CreateTransmissionWorkspace2.cpp +++ b/Framework/Algorithms/src/CreateTransmissionWorkspace2.cpp @@ -11,8 +11,9 @@ #include "MantidAPI/WorkspaceUnitValidator.h" #include "MantidKernel/MandatoryValidator.h" -using namespace Mantid::Kernel; using namespace Mantid::API; +using namespace Mantid::Kernel; +using namespace Mantid::Geometry; namespace Mantid { namespace Algorithms { @@ -69,7 +70,7 @@ void CreateTransmissionWorkspace2::init() { "ProcessingInstructions", "", boost::make_shared<MandatoryValidator<std::string>>(), Direction::Input), - "Grouping pattern on workspace indexes to yield only " + "Grouping pattern on spectrum numbers to yield only " "the detectors of interest. See GroupDetectors for details."); declareProperty(make_unique<PropertyWithValue<double>>( @@ -123,6 +124,8 @@ void CreateTransmissionWorkspace2::exec() { MatrixWorkspace_sptr outWS; MatrixWorkspace_sptr firstTransWS = getProperty("FirstTransmissionRun"); + convertProcessingInstructions(firstTransWS); + firstTransWS = normalizeDetectorsByMonitors(firstTransWS); firstTransWS = cropWavelength(firstTransWS); @@ -130,6 +133,8 @@ void CreateTransmissionWorkspace2::exec() { if (secondTransWS) { storeTransitionRun(1, firstTransWS); + convertProcessingInstructions(secondTransWS); + secondTransWS = normalizeDetectorsByMonitors(secondTransWS); secondTransWS = cropWavelength(secondTransWS); storeTransitionRun(2, secondTransWS); diff --git a/Framework/Algorithms/src/CreateTransmissionWorkspaceAuto2.cpp b/Framework/Algorithms/src/CreateTransmissionWorkspaceAuto2.cpp index 2fe876914bf5d857b22402ecd2d87f8674c3df96..c5260582705f37b05899859998a2fac208fdcd8e 100644 --- a/Framework/Algorithms/src/CreateTransmissionWorkspaceAuto2.cpp +++ b/Framework/Algorithms/src/CreateTransmissionWorkspaceAuto2.cpp @@ -7,6 +7,7 @@ #include "MantidAlgorithms/CreateTransmissionWorkspaceAuto2.h" #include "MantidAPI/WorkspaceUnitValidator.h" #include "MantidAlgorithms/BoostOptionalToAlgorithmProperty.h" +#include "MantidAlgorithms/ReflectometryWorkflowBase2.h" #include "MantidKernel/ListValidator.h" using namespace Mantid::Kernel; @@ -53,7 +54,7 @@ void CreateTransmissionWorkspaceAuto2::init() { // Processing instructions declareProperty(make_unique<PropertyWithValue<std::string>>( "ProcessingInstructions", "", Direction::Input), - "Grouping pattern of workspace indices to yield only the" + "Grouping pattern of spectrum numbers to yield only the" " detectors of interest. See GroupDetectors for syntax."); // Wavelength range @@ -102,7 +103,8 @@ void CreateTransmissionWorkspaceAuto2::exec() { populateMonitorProperties(alg, instrument); // Processing instructions - populateProcessingInstructions(alg, instrument, firstWS); + convertProcessingInstructions(instrument, firstWS); + alg->setProperty("ProcessingInstructions", m_processingInstructions); alg->execute(); MatrixWorkspace_sptr outWS = alg->getProperty("OutputWorkspace"); diff --git a/Framework/Algorithms/src/MaskBinsIf.cpp b/Framework/Algorithms/src/MaskBinsIf.cpp new file mode 100644 index 0000000000000000000000000000000000000000..898895c2913c2a28d43b74c093ebe6b06c140b2c --- /dev/null +++ b/Framework/Algorithms/src/MaskBinsIf.cpp @@ -0,0 +1,129 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidAlgorithms/MaskBinsIf.h" +#include "MantidAPI/MatrixWorkspace.h" +#include "MantidAPI/NumericAxis.h" +#include "MantidAPI/Progress.h" +#include "MantidAPI/SpectraAxis.h" +#include "MantidHistogramData/HistogramIterator.h" +#include "MantidKernel/MultiThreaded.h" + +#include <muParser.h> + +namespace { + +/** + * @brief Makes a new instance of the muparser + * @param y : count + * @param e : error + * @param x : bin center + * @param dx : bin center error + * @param s : spectrum axis value + * @param criterion : expression + * @return muparser + */ +mu::Parser makeParser(double &y, double &e, double &x, double &dx, double &s, + const std::string &criterion) { + mu::Parser muParser; + muParser.DefineVar("y", &y); + muParser.DefineVar("e", &e); + muParser.DefineVar("x", &x); + muParser.DefineVar("dx", &dx); + muParser.DefineVar("s", &s); + muParser.SetExpr(criterion); + return muParser; +} +} // namespace + +namespace Mantid { +namespace Algorithms { + +using namespace API; +using namespace Kernel; + +// Register the algorithm into the AlgorithmFactory +DECLARE_ALGORITHM(MaskBinsIf) + +//---------------------------------------------------------------------------------------------- +/** Initialize the algorithm's properties. + */ +void MaskBinsIf::init() { + declareProperty(Kernel::make_unique<WorkspaceProperty<MatrixWorkspace>>( + "InputWorkspace", "", Direction::Input), + "An input workspace."); + declareProperty("Criterion", "", + "Masking criterion as a muparser expression; y: bin count, " + "e: bin error, x: bin center, dx: bin center error, s: " + "spectrum axis value."); + declareProperty(Kernel::make_unique<WorkspaceProperty<MatrixWorkspace>>( + "OutputWorkspace", "", Direction::Output), + "An output workspace."); +} + +//---------------------------------------------------------------------------------------------- +/** Validate the inputs. + */ +std::map<std::string, std::string> MaskBinsIf::validateInputs() { + std::map<std::string, std::string> issues; + double y = 0., e = 0., x = 0., dx = 0., s = 0.; + mu::Parser parser = makeParser(y, e, x, dx, s, getPropertyValue("Criterion")); + try { + parser.Eval(); + } catch (mu::Parser::exception_type &e) { + issues["Criterion"] = "Invalid expression given: " + e.GetMsg(); + } + return issues; +} + +//---------------------------------------------------------------------------------------------- +/** Execute the algorithm. + */ +void MaskBinsIf::exec() { + const std::string criterion = getPropertyValue("Criterion"); + MatrixWorkspace_const_sptr inputWorkspace = getProperty("InputWorkspace"); + MatrixWorkspace_sptr outputWorkspace = getProperty("OutputWorkspace"); + if (inputWorkspace != outputWorkspace) { + outputWorkspace = inputWorkspace->clone(); + } + const auto spectrumAxis = outputWorkspace->getAxis(1); + const auto numeric = dynamic_cast<NumericAxis *>(spectrumAxis); + const auto spectrum = dynamic_cast<SpectraAxis *>(spectrumAxis); + const bool spectrumOrNumeric = numeric || spectrum; + if (!spectrumOrNumeric) { + throw std::runtime_error( + "Vertical axis must be NumericAxis or SpectraAxis"); + } + const int64_t numberHistograms = + static_cast<int64_t>(outputWorkspace->getNumberHistograms()); + auto progress = make_unique<Progress>(this, 0., 1., numberHistograms); + PARALLEL_FOR_IF(Mantid::Kernel::threadSafe(*outputWorkspace)) + for (int64_t index = 0; index < numberHistograms; ++index) { + PARALLEL_START_INTERUPT_REGION + double y, e, x, dx; + double s = spectrumOrNumeric ? spectrumAxis->getValue(index) : 0.; + mu::Parser parser = makeParser(y, e, x, dx, s, criterion); + const auto &spectrum = outputWorkspace->histogram(index); + const bool hasDx = outputWorkspace->hasDx(index); + for (auto it = spectrum.begin(); it != spectrum.end(); ++it) { + const auto bin = std::distance(spectrum.begin(), it); + y = it->counts(); + x = it->center(); + e = it->countStandardDeviation(); + dx = hasDx ? it->centerError() : 0.; + if (parser.Eval() != 0.) { + outputWorkspace->flagMasked(index, bin); + } + } + progress->report(); + PARALLEL_END_INTERUPT_REGION + } + PARALLEL_CHECK_INTERUPT_REGION + setProperty("OutputWorkspace", outputWorkspace); +} + +} // namespace Algorithms +} // namespace Mantid diff --git a/Framework/Algorithms/src/MergeRuns.cpp b/Framework/Algorithms/src/MergeRuns.cpp index 40c5cce16fbfe8d10c040af233688fb4f94aa851..497459e2c43d9ad9fa2153444116d2d870e7f9fa 100644 --- a/Framework/Algorithms/src/MergeRuns.cpp +++ b/Framework/Algorithms/src/MergeRuns.cpp @@ -16,6 +16,7 @@ #include "MantidDataObjects/Workspace2D.h" #include "MantidDataObjects/WorkspaceCreation.h" #include "MantidGeometry/Instrument.h" +#include "MantidGeometry/Instrument/ComponentInfo.h" #include "MantidGeometry/Instrument/DetectorInfo.h" #include "MantidIndexing/IndexInfo.h" #include "MantidKernel/ArrayProperty.h" @@ -124,16 +125,19 @@ MergeRuns::buildScanningOutputWorkspace(const MatrixWorkspace_sptr &outWS, MatrixWorkspace_sptr newOutWS = DataObjects::create<MatrixWorkspace>( *outWS, numOutputSpectra, outWS->histogram(0).binEdges()); - newOutWS->mutableDetectorInfo().merge(addeeWS->detectorInfo()); + newOutWS->mutableComponentInfo().merge(addeeWS->componentInfo()); - if (newOutWS->detectorInfo().scanSize() == outWS->detectorInfo().scanSize()) { + if (newOutWS->detectorInfo().size() * newOutWS->detectorInfo().scanCount() == + outWS->detectorInfo().size() * outWS->detectorInfo().scanCount()) { // In this case the detector info objects were identical. We just add the // workspaces as we normally would for MergeRuns. g_log.information() << "Workspaces had identical detector scan information and were " "merged."; return outWS + addeeWS; - } else if (newOutWS->detectorInfo().scanSize() != numOutputSpectra) { + } else if (newOutWS->detectorInfo().size() * + newOutWS->detectorInfo().scanCount() != + numOutputSpectra) { throw std::runtime_error("Unexpected DetectorInfo size. Merging workspaces " "with some, but not all overlapping scan " "intervals is not currently supported."); @@ -146,9 +150,8 @@ MergeRuns::buildScanningOutputWorkspace(const MatrixWorkspace_sptr &outWS, auto outSpecDefs = *(outWS->indexInfo().spectrumDefinitions()); const auto &addeeSpecDefs = *(addeeWS->indexInfo().spectrumDefinitions()); - const auto newAddeeSpecDefs = - buildScanIntervals(addeeSpecDefs, addeeWS->detectorInfo(), - outWS->detectorInfo(), newOutWS->detectorInfo()); + const auto newAddeeSpecDefs = buildScanIntervals( + addeeSpecDefs, addeeWS->detectorInfo(), newOutWS->detectorInfo()); outSpecDefs.insert(outSpecDefs.end(), newAddeeSpecDefs.begin(), newAddeeSpecDefs.end()); @@ -714,27 +717,23 @@ void MergeRuns::fillHistory() { *the scan times for the addee workspace and output workspace are the same this *builds the same indexing as the workspace had before. Otherwise, the correct *time indexes are set here. + * + *This function translates time indices from the addee to the new workspace. */ std::vector<SpectrumDefinition> MergeRuns::buildScanIntervals( const std::vector<SpectrumDefinition> &addeeSpecDefs, - const DetectorInfo &addeeDetInfo, const DetectorInfo &outDetInfo, - const DetectorInfo &newOutDetInfo) { + const DetectorInfo &addeeDetInfo, const DetectorInfo &newOutDetInfo) { std::vector<SpectrumDefinition> newAddeeSpecDefs(addeeSpecDefs.size()); PARALLEL_FOR_NO_WSP_CHECK() for (int64_t i = 0; i < int64_t(addeeSpecDefs.size()); ++i) { for (auto &index : addeeSpecDefs[i]) { SpectrumDefinition newSpecDef; - const auto &addeeScanInterval = addeeDetInfo.scanInterval(index); - if (addeeScanInterval == outDetInfo.scanInterval(index)) { - newSpecDef.add(index.first, index.second); - } else { - // Find the correct time index for this entry - for (size_t i = 0; i < newOutDetInfo.scanCount(index.first); i++) { - if (addeeScanInterval == - newOutDetInfo.scanInterval({index.first, i})) { - newSpecDef.add(index.first, i); - } + for (size_t time_index = 0; time_index < newOutDetInfo.scanCount(); + time_index++) { + if (addeeDetInfo.scanIntervals()[index.second] == + newOutDetInfo.scanIntervals()[time_index]) { + newSpecDef.add(index.first, time_index); } } newAddeeSpecDefs[i] = newSpecDef; diff --git a/Framework/Algorithms/src/NormaliseToMonitor.cpp b/Framework/Algorithms/src/NormaliseToMonitor.cpp index 6e376c1a9068fcb9b0548ff262e05315abd32dd8..5b34e20a99c4c29949aaec6542a8207fdce57860 100644 --- a/Framework/Algorithms/src/NormaliseToMonitor.cpp +++ b/Framework/Algorithms/src/NormaliseToMonitor.cpp @@ -384,10 +384,10 @@ void NormaliseToMonitor::checkProperties( Property *monID = getProperty("MonitorID"); // Is the monitor spectrum within the main input workspace const bool inWS = !monSpec->isDefault(); - m_syncScanInput = inputWorkspace->detectorInfo().isSyncScan(); + m_scanInput = inputWorkspace->detectorInfo().isScanning(); // Or is it in a separate workspace bool sepWS{monWS}; - if (m_syncScanInput && sepWS) + if (m_scanInput && sepWS) throw std::runtime_error("Can not currently use a separate monitor " "workspace with a detector scan input workspace."); // or monitor ID @@ -434,7 +434,7 @@ void NormaliseToMonitor::checkProperties( "monitor - the instrument is not fully specified.\n " "Continuing with normalization regardless."); g_log.warning() << "Error was: " << e.what() << "\n"; - if (m_syncScanInput) + if (m_scanInput) throw std::runtime_error("Can not continue, spectrum can not be obtained " "for monitor workspace, but the input workspace " "has a detector scan."); @@ -470,15 +470,15 @@ MatrixWorkspace_sptr NormaliseToMonitor::getInWSMonitorSpectrum( throw std::runtime_error( "Can not find spectra, corresponding to the requested monitor ID"); } - if (indexList.size() > 1 && !m_syncScanInput) { + if (indexList.size() > 1 && !m_scanInput) { throw std::runtime_error("More then one spectrum corresponds to the " "requested monitor ID. This is unexpected in a " "non-scanning workspace."); } m_workspaceIndexes = indexList; } else { // monitor spectrum is specified. - if (m_syncScanInput) - throw std::runtime_error("For a sync-scan input workspace the monitor ID " + if (m_scanInput) + throw std::runtime_error("For a scanning input workspace the monitor ID " "must be provided. Normalisation can not be " "performed to a spectrum."); const SpectraAxis *axis = @@ -635,7 +635,7 @@ void NormaliseToMonitor::performHistogramDivision( prog.report("Performing normalisation"); size_t timeIndex = 0; - if (m_syncScanInput) + if (m_scanInput) timeIndex = specInfo.spectrumDefinition(workspaceIndex)[0].second; const auto newYFactor = @@ -700,7 +700,7 @@ void NormaliseToMonitor::normaliseBinByBin( auto monY = m_monitor->counts(workspaceIndex); auto monE = m_monitor->countStandardDeviations(workspaceIndex); size_t timeIndex = 0; - if (m_syncScanInput) + if (m_scanInput) timeIndex = monitorSpecInfo.spectrumDefinition(workspaceIndex)[0].second; // Calculate the overall normalization just the once if bins are all // matching diff --git a/Framework/Algorithms/src/PerformIndexOperations.cpp b/Framework/Algorithms/src/PerformIndexOperations.cpp index 04aa0118f7134417d3e2cf66557318d6b4af356f..c8bbbba62f9901aeb31da595b2a038573ab57894 100644 --- a/Framework/Algorithms/src/PerformIndexOperations.cpp +++ b/Framework/Algorithms/src/PerformIndexOperations.cpp @@ -10,7 +10,6 @@ #include "MantidKernel/Strings.h" #include <boost/algorithm/string.hpp> #include <boost/regex.hpp> -#include <boost/scoped_ptr.hpp> using namespace Mantid::Kernel; using namespace Mantid::API; @@ -178,7 +177,8 @@ class AdditionParserRange : public CommandParserBase<AdditionCommand> { public: private: boost::regex getRegex() const override { - return boost::regex(R"(^\s*[0-9]+\s*\-\s*[0-9]+\s*$)"); + static const boost::regex r(R"(^\s*[0-9]+\s*\-\s*[0-9]+\s*$)"); + return r; } std::string getSeparator() const override { return "-"; } }; @@ -190,7 +190,7 @@ class AdditionParser : public CommandParser { public: Command *interpret(const std::string &instruction) const override { Command *command = nullptr; - boost::regex ex(R"(^\s*[0-9]+\s*\+\s*[0-9]+\s*$)"); + static const boost::regex ex(R"(^\s*[0-9]+\s*\+\s*[0-9]+\s*$)"); if (boost::regex_match(instruction, ex)) { std::vector<std::string> arguments; boost::split(arguments, instruction, boost::is_any_of("+")); @@ -216,7 +216,8 @@ class CropParserRange : public CommandParserBase<CropCommand> { public: private: boost::regex getRegex() const override { - return boost::regex(R"(^\s*[0-9]+\s*:\s*[0-9]+\s*$)"); + static const boost::regex r(R"(^\s*[0-9]+\s*:\s*[0-9]+\s*$)"); + return r; } std::string getSeparator() const override { return ":"; } }; @@ -228,7 +229,7 @@ class CropParserIndex : public CommandParser { public: Command *interpret(const std::string &instruction) const override { Command *command = nullptr; - boost::regex ex("^\\s*[0-9]+\\s*$"); + static const boost::regex ex("^\\s*[0-9]+\\s*$"); if (boost::regex_match(instruction, ex)) { int index = -1; Mantid::Kernel::Strings::convert<int>(instruction, index); diff --git a/Framework/Algorithms/src/ReflectometryBeamStatistics.cpp b/Framework/Algorithms/src/ReflectometryBeamStatistics.cpp new file mode 100644 index 0000000000000000000000000000000000000000..409617982925b0b6e52f0541967c3258a0b64adc --- /dev/null +++ b/Framework/Algorithms/src/ReflectometryBeamStatistics.cpp @@ -0,0 +1,469 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidAlgorithms/ReflectometryBeamStatistics.h" + +#include "MantidAPI/MatrixWorkspace.h" +#include "MantidAPI/Run.h" +#include "MantidAPI/SpectrumInfo.h" +#include "MantidGeometry/Crystal/AngleUnits.h" +#include "MantidGeometry/Instrument.h" +#include "MantidKernel/ArrayLengthValidator.h" +#include "MantidKernel/BoundedValidator.h" +#include "MantidKernel/CompositeValidator.h" +#include "MantidKernel/MandatoryValidator.h" + +#include <boost/math/special_functions/pow.hpp> +#include <boost/optional.hpp> + +namespace { +namespace Prop { +const std::string DETECTOR_RESOLUTION{"DetectorResolution"}; +const std::string DIRECT_FOREGROUND{"DirectForeground"}; +const std::string DIRECT_WS{"DirectBeamWorkspace"}; +const std::string FIRST_SLIT_NAME{"FirstSlitName"}; +const std::string FIRST_SLIT_SIZE_LOG{"FirstSlitSizeSampleLog"}; +const std::string PIXEL_SIZE{"PixelSize"}; +const std::string REFLECTED_FOREGROUND{"ReflectedForeground"}; +const std::string REFLECTED_WS{"ReflectedBeamWorkspace"}; +const std::string SECOND_SLIT_NAME{"SecondSlitName"}; +const std::string SECOND_SLIT_SIZE_LOG{"SecondSlitSizeSampleLog"}; +} // namespace Prop +/// A conversion factor from e.g. slit opening to FWHM of Gaussian equivalent. +constexpr double FWHM_GAUSSIAN_EQUIVALENT{0.68}; +} // namespace + +namespace Mantid { +namespace Algorithms { + +// Register the algorithm into the AlgorithmFactory +DECLARE_ALGORITHM(ReflectometryBeamStatistics) + +const std::string ReflectometryBeamStatistics::LogEntry::BEAM_RMS_VARIATION{ + "beam_stats.beam_rms_variation"}; +const std::string ReflectometryBeamStatistics::LogEntry::BENT_SAMPLE{ + "beam_stats.bent_sample"}; +const std::string + ReflectometryBeamStatistics::LogEntry::FIRST_SLIT_ANGULAR_SPREAD{ + "beam_stats.first_slit_angular_spread"}; +const std::string + ReflectometryBeamStatistics::LogEntry::INCIDENT_ANGULAR_SPREAD{ + "beam_stats.incident_angular_spread"}; +const std::string ReflectometryBeamStatistics::LogEntry::SAMPLE_WAVINESS{ + "beam_stats.sample_waviness"}; +const std::string + ReflectometryBeamStatistics::LogEntry::SECOND_SLIT_ANGULAR_SPREAD{ + "beam_stats.second_slit_angular_spread"}; + +/** Give the gap between the two slits, in meters. + * + * @param instrument an instrument which containts the slit components + * @param slit1Name name of the first slit component + * @param slit2Name name of the second slit component + * @return the slit gap, in meters + */ +double ReflectometryBeamStatistics::slitSeparation( + Geometry::Instrument_const_sptr instrument, const std::string &slit1Name, + const std::string &slit2Name) { + auto slit1 = instrument->getComponentByName(slit1Name); + auto slit2 = instrument->getComponentByName(slit2Name); + return (slit1->getPos() - slit2->getPos()).norm(); +} + +/// Algorithms name for identification. @see Algorithm::name +const std::string ReflectometryBeamStatistics::name() const { + return "ReflectometryBeamStatistics"; +} + +/// Algorithm's version for identification. @see Algorithm::version +int ReflectometryBeamStatistics::version() const { return 1; } + +/// Algorithm's category for identification. @see Algorithm::category +const std::string ReflectometryBeamStatistics::category() const { + return "ILL\\Reflectometry;Reflectometry"; +} + +/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary +const std::string ReflectometryBeamStatistics::summary() const { + return "Calculates statistical quantities of a reflectometry workspace."; +} + +/// Return a vector of related algorithms. +const std::vector<std::string> ReflectometryBeamStatistics::seeAlso() const { + return {"ReflectometryMomentumTransfer", "ReflectometrySumInQ"}; +} + +/** Initialize the algorithm's properties. + */ +void ReflectometryBeamStatistics::init() { + auto threeElementArray = + boost::make_shared<Kernel::ArrayLengthValidator<int>>(3); + auto mandatoryDouble = + boost::make_shared<Kernel::MandatoryValidator<double>>(); + auto mandatoryNonnegativeInt = + boost::make_shared<Kernel::CompositeValidator>(); + mandatoryNonnegativeInt->add<Kernel::MandatoryValidator<int>>(); + auto nonnegativeInt = boost::make_shared<Kernel::BoundedValidator<int>>(); + nonnegativeInt->setLower(0); + mandatoryNonnegativeInt->add(nonnegativeInt); + auto mandatoryString = + boost::make_shared<Kernel::MandatoryValidator<std::string>>(); + declareProperty( + Kernel::make_unique<API::WorkspaceProperty<API::MatrixWorkspace>>( + Prop::REFLECTED_WS, "", Kernel::Direction::InOut), + "A reflected beam workspace."); + declareProperty(Prop::REFLECTED_FOREGROUND, std::vector<int>(), + threeElementArray, + "A list of three workspace indices [start, beam centre, end] " + "defining the reflected foreground."); + declareProperty( + Kernel::make_unique<API::WorkspaceProperty<API::MatrixWorkspace>>( + Prop::DIRECT_WS, "", Kernel::Direction::InOut), + "A direct beam workspace."); + declareProperty(Prop::DIRECT_FOREGROUND, std::vector<int>(), + threeElementArray, + "A list of three workspace indices [start, beam centre, end] " + "defining the direct foreground."); + declareProperty(Prop::PIXEL_SIZE, EMPTY_DBL(), mandatoryDouble, + "Detector pixel size, in meters."); + declareProperty(Prop::DETECTOR_RESOLUTION, EMPTY_DBL(), mandatoryDouble, + "Detector pixel resolution, in meters."); + declareProperty(Prop::FIRST_SLIT_NAME, "", mandatoryString, + "Name of the first slit component."); + declareProperty(Prop::FIRST_SLIT_SIZE_LOG, "", mandatoryString, + "The sample log entry for the first slit opening."); + declareProperty(Prop::SECOND_SLIT_NAME, "", mandatoryString, + "Name of the second slit component."); + declareProperty(Prop::SECOND_SLIT_SIZE_LOG, "", mandatoryString, + "The sample log entry for the second slit opening."); +} + +/// Return issues found in input properties. +std::map<std::string, std::string> +ReflectometryBeamStatistics::validateInputs() { + std::map<std::string, std::string> issues; + API::MatrixWorkspace_const_sptr reflectedWS = getProperty(Prop::REFLECTED_WS); + const std::string slit1Name = getProperty(Prop::FIRST_SLIT_NAME); + auto instrument = reflectedWS->getInstrument(); + auto slit = instrument->getComponentByName(slit1Name); + if (!slit) { + issues[Prop::FIRST_SLIT_NAME] = "No component called '" + slit1Name + + "' found in " + Prop::REFLECTED_WS; + } + const std::string slit2Name = getProperty(Prop::SECOND_SLIT_NAME); + slit = instrument->getComponentByName(slit2Name); + if (!slit) { + issues[Prop::SECOND_SLIT_NAME] = "No component called '" + slit2Name + + "' found in " + Prop::REFLECTED_WS; + } + return issues; +} + +/** Execute the algorithm. + */ +void ReflectometryBeamStatistics::exec() { + API::MatrixWorkspace_sptr reflectedWS = getProperty(Prop::REFLECTED_WS); + API::MatrixWorkspace_sptr directWS = getProperty(Prop::DIRECT_WS); + const auto setup = createSetup(*reflectedWS, *directWS); + Statistics statistics; + const auto beamFWHM = + beamRMSVariation(reflectedWS, setup.foregroundStart, setup.foregroundEnd); + rmsVariationToLogs(*reflectedWS, beamFWHM); + const auto directBeamFWHM = beamRMSVariation( + directWS, setup.directForegroundStart, setup.directForegroundEnd); + rmsVariationToLogs(*directWS, directBeamFWHM); + statistics.incidentAngularSpread = incidentAngularSpread(setup); + statistics.sampleWaviness = sampleWaviness(setup, beamFWHM, directBeamFWHM, + statistics.incidentAngularSpread); + statistics.firstSlitAngularSpread = firstSlitAngularSpread(setup); + statistics.secondSlitAngularSpread = secondSlitAngularSpread(setup); + statistics.bentSample = bentSample(setup, statistics.sampleWaviness, + statistics.firstSlitAngularSpread); + statisticsToLogs(*reflectedWS, statistics); +} + +/** Calculate the beam FWHM or read its value from the sample logs. + * + * @param ws a reference workspace + * @param start foreground start workspace index + * @param end foreground end workspace index + * @return FWHM in units of pixel size + */ +double ReflectometryBeamStatistics::beamRMSVariation( + API::MatrixWorkspace_sptr &ws, const size_t start, const size_t end) { + // det_fwhm and detdb_fwhm in COSMOS + boost::optional<double> rmsVariation; + if (ws->run().hasProperty(LogEntry::BEAM_RMS_VARIATION)) { + try { + rmsVariation = ws->run().getPropertyValueAsType<double>( + LogEntry::BEAM_RMS_VARIATION); + } catch (std::invalid_argument &) { + m_log.warning() + << "Cannot convert '" + LogEntry::BEAM_RMS_VARIATION + + "' sample log into a number. Recalculating the value.\n"; + } + } + if (!rmsVariation) { + using namespace boost::math; + auto integrate = createChildAlgorithm("Integration"); + integrate->setProperty("InputWorkspace", ws); + integrate->setProperty("OutputWorkspace", "unused_for_child"); + integrate->setProperty("StartWorkspaceIndex", static_cast<int>(start)); + integrate->setProperty("EndWorkspaceIndex", static_cast<int>(end)); + integrate->execute(); + API::MatrixWorkspace_const_sptr integratedWS = + integrate->getProperty("OutputWorkspace"); + double sum{0.}; + double weighedSum{0.}; + std::vector<double> thetaDistribution(integratedWS->getNumberHistograms()); + for (size_t i = 0; i < thetaDistribution.size(); ++i) { + const auto total = integratedWS->y(i).front(); + thetaDistribution[i] = total; + sum += total; + weighedSum += static_cast<double>(i) * total; + } + const double massCenter = weighedSum / sum; + double variance{0.}; + for (size_t i = 0; i < thetaDistribution.size(); ++i) { + variance += + thetaDistribution[i] * pow<2>(massCenter - static_cast<double>(i)); + } + variance /= sum; + const double pixelSize = getProperty(Prop::PIXEL_SIZE); + rmsVariation = + 2. * std::sqrt(2. * std::log(2.)) * pixelSize * std::sqrt(variance); + } + return *rmsVariation; +} + +/** Return true if the sample is considered as bent or beam is divergent. + * + * @param setup a setup object + * @param sampleWaviness the value of sample waviness + * @param firstSlitAngularSpread the value of the RMS angular spread of the + * first slit + * @return true if sample is considered bent + */ +bool ReflectometryBeamStatistics::bentSample( + const Setup &setup, const double sampleWaviness, + const double firstSlitAngularSpread) { + return sampleWaviness > 0 && + setup.detectorResolution / setup.l2 > firstSlitAngularSpread; +} + +/** Generate a setup for the reflected beam experiment. + * + * @param ws the reflectivity workspace + * @param directWS corresponding direct beam workspace + * @return a setup object + */ +const ReflectometryBeamStatistics::Setup +ReflectometryBeamStatistics::createSetup(const API::MatrixWorkspace &ws, + const API::MatrixWorkspace &directWS) { + Setup s; + s.detectorResolution = getProperty(Prop::DETECTOR_RESOLUTION); + const std::vector<int> reflectedForeground = + getProperty(Prop::REFLECTED_FOREGROUND); + auto lowPixel = static_cast<size_t>(reflectedForeground.front()); + auto highPixel = static_cast<size_t>(reflectedForeground.back()); + s.foregroundStart = std::min(lowPixel, highPixel); + s.foregroundEnd = std::max(lowPixel, highPixel); + const std::vector<int> directForeground = + getProperty(Prop::DIRECT_FOREGROUND); + lowPixel = static_cast<size_t>(directForeground.front()); + highPixel = static_cast<size_t>(directForeground.back()); + s.directForegroundStart = std::min(lowPixel, highPixel); + s.directForegroundEnd = std::max(lowPixel, highPixel); + const auto &spectrumInfo = ws.spectrumInfo(); + const auto reflectedBeamCentre = static_cast<size_t>(reflectedForeground[1]); + s.l2 = spectrumInfo.l2(reflectedBeamCentre); + const auto &directSpectrumInfo = directWS.spectrumInfo(); + const auto directBeamCentre = static_cast<size_t>(directForeground[1]); + s.directL2 = directSpectrumInfo.l2(static_cast<size_t>(directBeamCentre)); + s.pixelSize = getProperty(Prop::PIXEL_SIZE); + s.slit1Slit2Distance = interslitDistance(ws); + const std::string slit1SizeEntry = getProperty(Prop::FIRST_SLIT_SIZE_LOG); + s.slit1Size = slitSize(ws, slit1SizeEntry); + s.slit1SizeDirectBeam = slitSize(directWS, slit1SizeEntry); + const std::string slit2Name = getProperty(Prop::SECOND_SLIT_NAME); + auto instrument = ws.getInstrument(); + auto slit2 = instrument->getComponentByName(slit2Name); + const auto samplePos = spectrumInfo.samplePosition(); + s.slit2SampleDistance = (slit2->getPos() - samplePos).norm(); + const std::string slit2SizeEntry = getProperty(Prop::SECOND_SLIT_SIZE_LOG); + s.slit2Size = slitSize(ws, slit2SizeEntry); + s.slit2SizeDirectBeam = slitSize(directWS, slit2SizeEntry); + return s; +} + +/** Calculate the detector angular resolution. + * + * @param setup reflected beam setup + * @param incidentFWHM spread of the incident beam + * @return the angular resolution + */ +double ReflectometryBeamStatistics::detectorAngularResolution( + const Setup &setup, const double incidentFWHM) { + // da_det in COSMOS + using namespace boost::math; + const auto slitSizeRatio = setup.slit2Size / setup.slit1Size; + const auto slit2Detector = setup.slit2SampleDistance + setup.l2; + const auto virtualSourceDist = + slit2Detector + + (slitSizeRatio * setup.slit1Slit2Distance) / (1. + slitSizeRatio); + return std::sqrt(pow<2>(incidentFWHM * virtualSourceDist) + + pow<2>(setup.detectorResolution)); +} + +/** Calculate the angular spread due to the first slit. + * + * @param setup a reflected beam setup object + * @return the spread + */ +double ReflectometryBeamStatistics::firstSlitAngularSpread(const Setup &setup) { + // S2_fwhm in COSMOS + return FWHM_GAUSSIAN_EQUIVALENT * setup.slit1Size / setup.slit1Slit2Distance; +} + +/** Calculate the range of angles in the reflection plane determined by + * the collimation. + * + * @param setup a setup object + * @return the incident FWHM + */ +double ReflectometryBeamStatistics::incidentAngularSpread(const Setup &setup) { + // da in COSMOS + using namespace boost::math; + return FWHM_GAUSSIAN_EQUIVALENT * + std::sqrt((pow<2>(setup.slit1Size) + pow<2>(setup.slit2Size))) / + setup.slit1Slit2Distance; +} + +/** Give the gap between the two slits, in meters. + * + * @param ws the reflectivity workspace + * @return the slit gap, in meters + */ +double +ReflectometryBeamStatistics::interslitDistance(const API::MatrixWorkspace &ws) { + const std::string slit1Name = getProperty(Prop::FIRST_SLIT_NAME); + const std::string slit2Name = getProperty(Prop::SECOND_SLIT_NAME); + auto instrument = ws.getInstrument(); + return slitSeparation(instrument, slit1Name, slit2Name); +} + +/** + * Adds beam RMS variation to sample logs if it doesn't exist yet. + * @param ws a workspace sample logs of which to change + * @param variation beam RMS variation value + */ +void ReflectometryBeamStatistics::rmsVariationToLogs(API::MatrixWorkspace &ws, + const double variation) { + auto &run = ws.mutableRun(); + if (!run.hasProperty(LogEntry::BEAM_RMS_VARIATION)) { + const std::string metres{"m"}; + run.addProperty(LogEntry::BEAM_RMS_VARIATION, variation, metres); + } +} + +/** Calculate sample RMS waviness. + * + * @param setup a setup object + * @param beamFWHM reflected beam RMS variation + * @param directBeamFWHM direct beam RMS variation + * @param incidentFWHM incident beam angular spread + * @return the waviness + */ +double ReflectometryBeamStatistics::sampleWaviness(const Setup &setup, + const double beamFWHM, + const double directBeamFWHM, + const double incidentFWHM) { + // om_fwhm in COSMOS + using namespace boost::math; + const double slitSizeTolerance{0.00004}; // From COSMOS. + if (std::abs(setup.slit1Size - setup.slit1SizeDirectBeam) >= + slitSizeTolerance || + std::abs(setup.slit2Size - setup.slit2SizeDirectBeam) >= + slitSizeTolerance) { + // Differing slit sizes branch from COSMOS. + const double daDet = detectorAngularResolution(setup, incidentFWHM); + if (beamFWHM >= daDet) { + const auto a = std::sqrt(pow<2>(beamFWHM) - pow<2>(daDet)); + if (a >= setup.pixelSize) { + return 0.5 * a / setup.directL2; + } + } + } else if (pow<2>(beamFWHM) - pow<2>(directBeamFWHM) >= 0) { + const auto a = std::sqrt(pow<2>(beamFWHM) - pow<2>(directBeamFWHM)); + if (a >= setup.pixelSize) { + return 0.5 * a / setup.directL2; + } + } + return 0.; +} + +/** Calculate the angular spread due to the second slit. + * + * @param setup reflected beam setup + * @return the spread + */ +double +ReflectometryBeamStatistics::secondSlitAngularSpread(const Setup &setup) { + // s3_fwhm in COSMOS. + const auto slit2Detector = setup.slit2SampleDistance + setup.l2; + return FWHM_GAUSSIAN_EQUIVALENT * setup.slit2Size / slit2Detector; +} + +/** Read the slit size from sample logs. + * + * @param ws workspace to investigate + * @param logEntry name of the slit opening sample log + * @return the slit opening, in meters + */ +double ReflectometryBeamStatistics::slitSize(const API::MatrixWorkspace &ws, + const std::string &logEntry) { + auto &run = ws.run(); + const double opening = run.getPropertyValueAsType<double>(logEntry); + const auto &units = run.getProperty(logEntry)->units(); + if (units.empty()) { + m_log.warning() << "Slit opening entry " << logEntry + << " has no unit. Assuming meters.\n"; + return opening; + } else if (units == "m") { + return opening; + } else if (units == "mm") { + return opening * 1e-3; + } else { + m_log.warning() << "Slit opening entry " << logEntry + << " has an unknown unit. Assuming meters.\n"; + return opening; + } +} + +/** + * Add statistics to sample logs overwriting previous values. + * @param ws a workspace to modify + * @param statistics statistics to write + */ +void ReflectometryBeamStatistics::statisticsToLogs( + API::MatrixWorkspace &ws, const Statistics &statistics) { + auto &run = ws.mutableRun(); + constexpr bool overwrite{true}; + const std::string radians{"radians"}; + run.addProperty(LogEntry::BENT_SAMPLE, statistics.bentSample ? 1 : 0, + overwrite); + run.addProperty(LogEntry::FIRST_SLIT_ANGULAR_SPREAD, + statistics.firstSlitAngularSpread, radians, overwrite); + run.addProperty(LogEntry::INCIDENT_ANGULAR_SPREAD, + statistics.incidentAngularSpread, radians, overwrite); + run.addProperty(LogEntry::SAMPLE_WAVINESS, statistics.sampleWaviness, radians, + overwrite); + run.addProperty(LogEntry::SECOND_SLIT_ANGULAR_SPREAD, + statistics.secondSlitAngularSpread, radians, overwrite); +} + +} // namespace Algorithms +} // namespace Mantid diff --git a/Framework/Algorithms/src/ReflectometryMomentumTransfer.cpp b/Framework/Algorithms/src/ReflectometryMomentumTransfer.cpp index 390faf102251f2f28f22119130d1bc243f8dce90..06903325aa4886555ed97fe2ca75132c9746a0b9 100644 --- a/Framework/Algorithms/src/ReflectometryMomentumTransfer.cpp +++ b/Framework/Algorithms/src/ReflectometryMomentumTransfer.cpp @@ -10,8 +10,12 @@ #include "MantidAPI/Run.h" #include "MantidAPI/SpectrumInfo.h" #include "MantidAPI/WorkspaceUnitValidator.h" +#include "MantidAlgorithms/ReflectometryBeamStatistics.h" +#include "MantidGeometry/Crystal/AngleUnits.h" #include "MantidGeometry/Instrument.h" #include "MantidKernel/ArrayLengthValidator.h" +#include "MantidKernel/BoundedValidator.h" +#include "MantidKernel/CompositeValidator.h" #include "MantidKernel/ListValidator.h" #include "MantidKernel/MandatoryValidator.h" #include "MantidKernel/PhysicalConstants.h" @@ -26,13 +30,9 @@ static const std::string CHOPPER_PAIR_DIST{"ChopperPairDistance"}; static const std::string CHOPPER_RADIUS{"ChopperRadius"}; static const std::string CHOPPER_SPEED{"ChopperSpeed"}; static const std::string DETECTOR_RESOLUTION{"DetectorResolution"}; -static const std::string DIRECT_BEAM_WS{"DirectBeamWorkspace"}; -static const std::string DIRECT_FOREGROUND{"DirectForeground"}; static const std::string INPUT_WS{"InputWorkspace"}; static const std::string OUTPUT_WS{"OutputWorkspace"}; static const std::string PIXEL_SIZE{"PixelSize"}; -static const std::string POLARIZED{"Polarized"}; -static const std::string REFLECTED_BEAM_WS{"ReflectedBeamWorkspace"}; static const std::string REFLECTED_FOREGROUND{"ReflectedForeground"}; static const std::string SLIT1_NAME{"FirstSlitName"}; static const std::string SLIT1_SIZE_LOG{"FirstSlitSizeSampleLog"}; @@ -51,15 +51,31 @@ static const std::string Q{"SumInQ"}; /// A conversion factor from e.g. slit opening to FWHM of Gaussian equivalent. constexpr double FWHM_GAUSSIAN_EQUIVALENT{0.68}; -/// Convert degrees to radians. -constexpr double inRad(const double a) noexcept { return a / 180. * M_PI; } +/** + * Returns a double value from sample logs. + * @param run a Run object. + * @param entry name of the sample log entry + * @return the value of the log entry + * @throws NotFoundError if the log does not exist + * @throws runtime_error if the log value cannot be converted to double + */ +double fromLogs(const Mantid::API::Run &run, const std::string &entry) { + if (!run.hasProperty(entry)) { + throw Mantid::Kernel::Exception::NotFoundError( + "Could not find sample log entry", entry); + } + try { + return run.getPropertyValueAsType<double>(entry); + } catch (std::invalid_argument &) { + throw std::runtime_error("Could not parse a number from log entry " + + entry); + } +} } // namespace namespace Mantid { namespace Algorithms { -using Mantid::Kernel::Direction; - // Register the algorithm into the AlgorithmFactory DECLARE_ALGORITHM(ReflectometryMomentumTransfer) @@ -80,7 +96,8 @@ const std::string ReflectometryMomentumTransfer::category() const { /// Return a vector of related algorithms. const std::vector<std::string> ReflectometryMomentumTransfer::seeAlso() const { - return {"ConvertToReflectometryQ", "ConvertUnits"}; + return {"ReflectometryBeamStatistics", "ConvertToReflectometryQ", + "ConvertUnits"}; } /// Algorithm's summary for use in the GUI and help. @see Algorithm::summary @@ -98,6 +115,12 @@ void ReflectometryMomentumTransfer::init() { boost::make_shared<Kernel::ArrayLengthValidator<int>>(2); auto mandatoryDouble = boost::make_shared<Kernel::MandatoryValidator<double>>(); + auto positiveDouble = boost::make_shared<Kernel::BoundedValidator<double>>(); + positiveDouble->setLowerExclusive(0.); + auto mandatoryPositiveDouble = + boost::make_shared<Kernel::CompositeValidator>(); + mandatoryPositiveDouble->add(mandatoryDouble); + mandatoryPositiveDouble->add(positiveDouble); auto mandatoryString = boost::make_shared<Kernel::MandatoryValidator<std::string>>(); std::vector<std::string> sumTypes(2); @@ -107,44 +130,31 @@ void ReflectometryMomentumTransfer::init() { boost::make_shared<Kernel::ListValidator<std::string>>(sumTypes); declareProperty( Kernel::make_unique<API::WorkspaceProperty<API::MatrixWorkspace>>( - Prop::INPUT_WS, "", Direction::Input, inWavelength), + Prop::INPUT_WS, "", Kernel::Direction::Input, inWavelength), "A reflectivity workspace with X units in wavelength."); declareProperty( Kernel::make_unique<API::WorkspaceProperty<API::MatrixWorkspace>>( - Prop::OUTPUT_WS, "", Direction::Output, inWavelength), + Prop::OUTPUT_WS, "", Kernel::Direction::Output), "The input workspace with X units converted to Q and DX values set to " "the Q resolution."); - declareProperty( - Kernel::make_unique<API::WorkspaceProperty<API::MatrixWorkspace>>( - Prop::REFLECTED_BEAM_WS, "", Direction::Input, inWavelength), - "A reflected beam workspace in wavelength."); + declareProperty(Prop::SUM_TYPE, SumTypeChoice::LAMBDA, acceptableSumTypes, + "The type of summation performed for the input workspace."); declareProperty(Prop::REFLECTED_FOREGROUND, std::vector<int>(), twoElementArray, "A two element list [start, end] defining the reflected beam " "foreground region in workspace indices."); - declareProperty( - Kernel::make_unique<API::WorkspaceProperty<API::MatrixWorkspace>>( - Prop::DIRECT_BEAM_WS, "", Direction::Input), - "A direct beam workspace in wavelength."); - declareProperty(Prop::DIRECT_FOREGROUND, std::vector<int>(), twoElementArray, - "A two element list [start, end] defining the direct beam " - "foreground region in workspace indices."); - declareProperty(Prop::SUM_TYPE, SumTypeChoice::LAMBDA, acceptableSumTypes, - "The type of summation performed for the input workspace."); - declareProperty(Prop::POLARIZED, false, - "True if the input workspace is part of polarization " - "analysis experiment, false otherwise."); - declareProperty(Prop::PIXEL_SIZE, EMPTY_DBL(), mandatoryDouble, + declareProperty(Prop::PIXEL_SIZE, EMPTY_DBL(), mandatoryPositiveDouble, "Detector pixel size, in meters."); - declareProperty(Prop::DETECTOR_RESOLUTION, EMPTY_DBL(), mandatoryDouble, + declareProperty(Prop::DETECTOR_RESOLUTION, EMPTY_DBL(), + mandatoryPositiveDouble, "Detector pixel resolution, in meters."); - declareProperty(Prop::CHOPPER_SPEED, EMPTY_DBL(), mandatoryDouble, + declareProperty(Prop::CHOPPER_SPEED, EMPTY_DBL(), mandatoryPositiveDouble, "Chopper speed, in rpm."); - declareProperty(Prop::CHOPPER_OPENING, EMPTY_DBL(), mandatoryDouble, + declareProperty(Prop::CHOPPER_OPENING, EMPTY_DBL(), mandatoryPositiveDouble, "The opening angle between the two choppers, in degrees."); - declareProperty(Prop::CHOPPER_RADIUS, EMPTY_DBL(), mandatoryDouble, + declareProperty(Prop::CHOPPER_RADIUS, EMPTY_DBL(), mandatoryPositiveDouble, "Chopper radius, in meters."); - declareProperty(Prop::CHOPPER_PAIR_DIST, EMPTY_DBL(), mandatoryDouble, + declareProperty(Prop::CHOPPER_PAIR_DIST, EMPTY_DBL(), mandatoryPositiveDouble, "The gap between two choppers, in meters."); declareProperty(Prop::SLIT1_NAME, "", mandatoryString, "Name of the first slit component."); @@ -154,108 +164,131 @@ void ReflectometryMomentumTransfer::init() { "Name of the second slit component."); declareProperty(Prop::SLIT2_SIZE_LOG, "", mandatoryString, "The sample log entry for the second slit opening."); - declareProperty(Prop::TOF_CHANNEL_WIDTH, EMPTY_DBL(), mandatoryDouble, + declareProperty(Prop::TOF_CHANNEL_WIDTH, EMPTY_DBL(), mandatoryPositiveDouble, "TOF bin width, in microseconds."); } +/// Validates the algorithm's input properties. +std::map<std::string, std::string> +ReflectometryMomentumTransfer::validateInputs() { + std::map<std::string, std::string> issues; + API::MatrixWorkspace_const_sptr inWS = getProperty(Prop::INPUT_WS); + if (inWS->getNumberHistograms() != 1) { + issues[Prop::INPUT_WS] = "Expected a workspace with a single histogram."; + } else { + const auto &spectrumInfo = inWS->spectrumInfo(); + if (spectrumInfo.isMonitor(0)) { + issues[Prop::INPUT_WS] = "The only histogram is marked as a monitor."; + } + if (spectrumInfo.isMasked(0)) { + issues[Prop::INPUT_WS] = "The only histogram is masked."; + } + } + const std::string slit1Name = getProperty(Prop::SLIT1_NAME); + auto instrument = inWS->getInstrument(); + auto slit = instrument->getComponentByName(slit1Name); + if (!slit) { + issues[Prop::SLIT1_NAME] = + "No component called '" + slit1Name + "' found in " + Prop::INPUT_WS; + } + const std::string slit2Name = getProperty(Prop::SLIT1_NAME); + slit = instrument->getComponentByName(slit2Name); + if (!slit) { + issues[Prop::SLIT2_NAME] = + "No component called '" + slit2Name + "' found in " + Prop::INPUT_WS; + } + return issues; +} + /** Execute the algorithm. */ void ReflectometryMomentumTransfer::exec() { using namespace boost::math; - API::MatrixWorkspace_sptr inWS = getProperty(Prop::INPUT_WS); - API::MatrixWorkspace_sptr reflectedWS = getProperty(Prop::REFLECTED_BEAM_WS); - API::MatrixWorkspace_sptr directWS = getProperty(Prop::DIRECT_BEAM_WS); - auto setup = createSetup(*reflectedWS, *directWS); - API::MatrixWorkspace_sptr outWS; - outWS = inWS->clone(); + API::MatrixWorkspace_const_sptr inWS = getProperty(Prop::INPUT_WS); + auto setup = createSetup(*inWS); + auto beam = createBeamStatistics(*inWS); + API::MatrixWorkspace_sptr outWS = inWS->clone(); convertToMomentumTransfer(outWS); - const auto beamFWHM = - beamRMSVariation(reflectedWS, setup.foregroundStart, setup.foregroundEnd); - const auto directBeamFWHM = beamRMSVariation( - directWS, setup.directForegroundStart, setup.directForegroundEnd); - const auto incidentFWHM = incidentAngularSpread(setup); - const auto slit1FWHM = slit1AngularSpread(setup); - const auto &spectrumInfo = inWS->spectrumInfo(); - const int64_t nHisto = static_cast<int64_t>(outWS->getNumberHistograms()); - PARALLEL_FOR_IF(Kernel::threadSafe(*inWS, *directWS, *outWS)) - for (int64_t i = 0; i < nHisto; ++i) { - PARALLEL_START_INTERUPT_REGION - const auto wsIndex = static_cast<size_t>(i); - const auto &wavelengths = inWS->points(wsIndex); - const auto &qs = outWS->points(wsIndex); - auto dx = Kernel::make_cow<HistogramData::HistogramDx>(qs.size(), 0); - outWS->setSharedDx(wsIndex, dx); - if (spectrumInfo.isMonitor(wsIndex) || spectrumInfo.isMasked(wsIndex)) { - // Skip monitors & masked spectra, leave DX to zero. - continue; - } - auto &resolutions = outWS->mutableDx(wsIndex); - for (size_t i = 0; i < wavelengths.size(); ++i) { - const auto wavelength = wavelengths[i] * 1e-10; - const auto deltaLambdaSq = - wavelengthResolutionSquared(*inWS, wsIndex, setup, wavelength); - const auto deltaThetaSq = - angularResolutionSquared(inWS, *directWS, wsIndex, setup, beamFWHM, - directBeamFWHM, incidentFWHM, slit1FWHM); - // q is inversely proportional to wavelength but sorted in ascending - // order. - const auto qIndex = qs.size() - i - 1; - resolutions[qIndex] = - qs[qIndex] * std::sqrt(deltaLambdaSq + deltaThetaSq); - } - PARALLEL_END_INTERUPT_REGION - } - PARALLEL_CHECK_INTERUPT_REGION + addResolutionDX(*inWS, *outWS, setup, beam); setProperty(Prop::OUTPUT_WS, outWS); } +/** + * Adds the Q resolution to outWS as the DX values + * @param inWS the input workspace + * @param outWS the workspace to write the resolutions to + * @param setup a setup object + * @param beam a beam statistics object + */ +void ReflectometryMomentumTransfer::addResolutionDX( + const API::MatrixWorkspace &inWS, API::MatrixWorkspace &outWS, + const Setup &setup, const Beam &beam) { + const auto deltaThetaSq = angularResolutionSquared(inWS, setup, beam); + const auto &wavelengths = inWS.points(0); + const auto &qs = outWS.points(0); + auto dx = Kernel::make_cow<HistogramData::HistogramDx>(qs.size(), 0); + outWS.setSharedDx(0, dx); + auto &resolutions = outWS.mutableDx(0); + for (size_t i = 0; i < wavelengths.size(); ++i) { + const auto wavelength = wavelengths[i] * 1e-10; + const auto deltaLambdaSq = wavelengthResolutionSquared(setup, wavelength); + // q is inversely proportional to wavelength but sorted in ascending + // order. + const auto qIndex = qs.size() - i - 1; + resolutions[qIndex] = qs[qIndex] * std::sqrt(deltaLambdaSq + deltaThetaSq); + } +} + /** Calculate the squared angular resolution. * * @param ws the reflectivity workspace - * @param directWS the reference direct beam workspace - * @param wsIndex a workspace index to the reflectivity workspace * @param setup a setup object for the reflected beam - * @param beamFWHM reflected beam angular FWHM - * @param directBeamFWHM direct beam angular FWHM - * @param incidentFWHM RMS spread of the incident beam - * @param slit1FWHM RMS spread due to the first slit + * @param beam a beam statistics object * @return the squared fractional angular resolution */ double ReflectometryMomentumTransfer::angularResolutionSquared( - API::MatrixWorkspace_sptr &ws, const API::MatrixWorkspace &directWS, - const size_t wsIndex, const Setup &setup, const double beamFWHM, - const double directBeamFWHM, const double incidentFWHM, - const double slit1FWHM) { + const API::MatrixWorkspace &ws, const Setup &setup, const Beam &beam) { using namespace boost::math; - const auto waviness = sampleWaviness(ws, directWS, wsIndex, setup, beamFWHM, - directBeamFWHM, incidentFWHM); - const auto slit2FWHM = slit2AngularSpread(*ws, wsIndex, setup); - const auto &spectrumInfo = ws->spectrumInfo(); - const auto l2 = spectrumInfo.l2(wsIndex); - const auto braggAngle = 0.5 * spectrumInfo.twoTheta(wsIndex); + const auto &spectrumInfo = ws.spectrumInfo(); + const auto l2 = spectrumInfo.l2(0); + const auto braggAngle = 0.5 * spectrumInfo.twoTheta(0); if (setup.sumType == SumType::Q) { - if (waviness > 0) { - if (slit1FWHM >= 2. * waviness) { - return (pow<2>(setup.detectorResolution / l2) + pow<2>(slit2FWHM) + - pow<2>(waviness)) / + if (beam.sampleWaviness > 0) { + // Bent sample resolution + if (beam.firstSlitAngularSpread >= 2. * beam.sampleWaviness) { + return (pow<2>(setup.detectorResolution / + (setup.slit2SampleDistance + l2)) + + pow<2>(beam.secondSlitAngularSpread) + + pow<2>(beam.sampleWaviness)) / pow<2>(braggAngle); } else { - return (pow<2>(setup.detectorResolution / 2. / l2) + pow<2>(slit2FWHM) + - pow<2>(slit1FWHM)) / + return (pow<2>(setup.detectorResolution / 2. / + (setup.slit2SampleDistance + l2)) + + pow<2>(beam.secondSlitAngularSpread) + + pow<2>(beam.firstSlitAngularSpread)) / pow<2>(braggAngle); } } else { - if (slit1FWHM > setup.detectorResolution / l2) { - return (pow<2>(setup.detectorResolution / l2) + pow<2>(slit2FWHM)) / + if (beam.firstSlitAngularSpread > setup.detectorResolution / l2) { + // Divergent beam resolution + return (pow<2>(setup.detectorResolution / + (setup.slit2SampleDistance + l2)) + + pow<2>(beam.secondSlitAngularSpread)) / pow<2>(braggAngle); } else { - const double incidentSpread = incidentFWHM; - return (pow<2>(incidentSpread) + - pow<2>(setup.detectorResolution / l2)) / + // Detector resolution is worse than the incoming divergence for a flat + // sample. + return (pow<2>(beam.incidentAngularSpread) + + pow<2>(setup.detectorResolution / + (setup.slit2SampleDistance + l2))) / pow<2>(braggAngle); } } } else { // SumType::LAMBDA + const auto angularResolution = + (pow<2>(beam.incidentAngularSpread) + pow<2>(beam.sampleWaviness)) / + pow<2>(braggAngle); + // In case foreground width is smaller than incoming angular resolution const auto foregroundWidth = static_cast<double>(setup.foregroundEnd - setup.foregroundStart + 1) * setup.pixelSize; @@ -263,56 +296,10 @@ double ReflectometryMomentumTransfer::angularResolutionSquared( pow<2>(FWHM_GAUSSIAN_EQUIVALENT) * (pow<2>(foregroundWidth) + pow<2>(setup.slit2Size)) / pow<2>(l2 * braggAngle); - double angularResolution; - if (setup.polarized) { - angularResolution = pow<2>(incidentFWHM / braggAngle); - } else { - angularResolution = - (pow<2>(incidentFWHM) + pow<2>(waviness)) / pow<2>(braggAngle); - } return std::min(angularResolution, foregroundWidthLimited); } } -/** Calculate the FWHM of a beam (reflected or direct). - * - * @param ws a reference workspace - * @param start foreground start workspace index - * @param end foreground end workspace index - * @return FWHM - */ -double ReflectometryMomentumTransfer::beamRMSVariation( - API::MatrixWorkspace_sptr &ws, const size_t start, const size_t end) { - // det_fwhm and detdb_fwhm in COSMOS - using namespace boost::math; - auto integrate = createChildAlgorithm("Integration"); - integrate->setProperty("InputWorkspace", ws); - integrate->setProperty("OutputWorkspace", "unused_for_child"); - integrate->setProperty("StartWorkspaceIndex", static_cast<int>(start)); - integrate->setProperty("EndWorkspaceIndex", static_cast<int>(end)); - integrate->execute(); - API::MatrixWorkspace_const_sptr integratedWS = - integrate->getProperty("OutputWorkspace"); - double sum{0.}; - double weighedSum{0.}; - std::vector<double> thetaDistribution(integratedWS->getNumberHistograms()); - for (size_t i = 0; i < thetaDistribution.size(); ++i) { - const auto total = integratedWS->y(i).front(); - thetaDistribution[i] = total; - sum += total; - weighedSum += static_cast<double>(i) * total; - } - const double massCenter = weighedSum / sum; - double variance{0.}; - for (size_t i = 0; i < thetaDistribution.size(); ++i) { - variance += - thetaDistribution[i] * pow<2>(massCenter - static_cast<double>(i)); - } - variance /= sum; - const double pixelSize = getProperty(Prop::PIXEL_SIZE); - return 2. * std::sqrt(2. * std::log(2.)) * pixelSize * std::sqrt(variance); -} - /** Convert a workspace's X units to momentum transfer in-place. * * @param ws a workspace to convert. @@ -327,41 +314,37 @@ void ReflectometryMomentumTransfer::convertToMomentumTransfer( ws = convert->getProperty("OutputWorkspace"); } -/** Calculate the detector angular resolution. - * - * @param ws a reflectivity workspace - * @param wsIndex a workspace index to the reflectivity workspace - * @param setup reflected beam setup - * @param incidentFWHM spread of the incident beam - * @return the da quantity +/** + * Creates a beam statistics object from sample logs + * @param ws a workspace + * @return beam statistics */ -double ReflectometryMomentumTransfer::detectorAngularResolution( - const API::MatrixWorkspace &ws, const size_t wsIndex, const Setup &setup, - const double incidentFWHM) { - // da_det in COSMOS - using namespace boost::math; - const auto slitSizeRatio = setup.slit2Size / setup.slit1Size; - const auto &spectrumInfo = ws.spectrumInfo(); - const auto slit2Detector = - setup.slit2SampleDistance + spectrumInfo.l2(wsIndex); - const auto virtualSourceDist = - slit2Detector + - (slitSizeRatio * setup.slit1Slit2Distance) / (1. + slitSizeRatio); - return std::sqrt(pow<2>(incidentFWHM * virtualSourceDist) + - pow<2>(setup.detectorResolution)); +const ReflectometryMomentumTransfer::Beam +ReflectometryMomentumTransfer::createBeamStatistics( + const API::MatrixWorkspace &ws) { + Beam b; + const auto &run = ws.run(); + b.incidentAngularSpread = fromLogs( + run, ReflectometryBeamStatistics::LogEntry::INCIDENT_ANGULAR_SPREAD); + b.firstSlitAngularSpread = fromLogs( + run, ReflectometryBeamStatistics::LogEntry::FIRST_SLIT_ANGULAR_SPREAD); + b.secondSlitAngularSpread = fromLogs( + run, ReflectometryBeamStatistics::LogEntry::SECOND_SLIT_ANGULAR_SPREAD); + b.sampleWaviness = + fromLogs(run, ReflectometryBeamStatistics::LogEntry::SAMPLE_WAVINESS); + return b; } /** Generate a setup for the reflected beam experiment. * * @param ws the reflectivity workspace - * @param directWS corresponding direct beam workspace * @return a setup object */ const ReflectometryMomentumTransfer::Setup -ReflectometryMomentumTransfer::createSetup( - const API::MatrixWorkspace &ws, const API::MatrixWorkspace &directWS) { +ReflectometryMomentumTransfer::createSetup(const API::MatrixWorkspace &ws) { Setup s; - s.chopperOpening = inRad(getProperty(Prop::CHOPPER_OPENING)); + s.chopperOpening = static_cast<double>(getProperty(Prop::CHOPPER_OPENING)) * + Geometry::deg2rad; s.chopperPairDistance = getProperty(Prop::CHOPPER_PAIR_DIST); s.chopperPeriod = 1. / (static_cast<double>(getProperty(Prop::CHOPPER_SPEED)) / 60.); @@ -372,26 +355,20 @@ ReflectometryMomentumTransfer::createSetup( auto highPixel = static_cast<size_t>(foreground.back()); s.foregroundStart = std::min(lowPixel, highPixel); s.foregroundEnd = std::max(lowPixel, highPixel); - foreground = getProperty(Prop::DIRECT_FOREGROUND); - lowPixel = static_cast<size_t>(foreground.front()); - highPixel = static_cast<size_t>(foreground.back()); - s.directForegroundStart = std::min(lowPixel, highPixel); - s.directForegroundEnd = std::max(lowPixel, highPixel); + const auto &spectrumInfo = ws.spectrumInfo(); + s.l1 = spectrumInfo.l1(); + s.l2 = spectrumInfo.l2(0); s.pixelSize = getProperty(Prop::PIXEL_SIZE); - s.polarized = getProperty(Prop::POLARIZED); s.slit1Slit2Distance = interslitDistance(ws); const std::string slit1SizeEntry = getProperty(Prop::SLIT1_SIZE_LOG); s.slit1Size = slitSize(ws, slit1SizeEntry); - s.slit1SizeDirectBeam = slitSize(directWS, slit1SizeEntry); const std::string slit2Name = getProperty(Prop::SLIT2_NAME); auto instrument = ws.getInstrument(); auto slit2 = instrument->getComponentByName(slit2Name); - const auto &spectrumInfo = ws.spectrumInfo(); const auto samplePos = spectrumInfo.samplePosition(); s.slit2SampleDistance = (slit2->getPos() - samplePos).norm(); const std::string slit2SizeEntry = getProperty(Prop::SLIT2_SIZE_LOG); s.slit2Size = slitSize(ws, slit2SizeEntry); - s.slit2SizeDirectBeam = slitSize(directWS, slit2SizeEntry); const std::string sumType = getProperty(Prop::SUM_TYPE); s.sumType = sumType == SumTypeChoice::LAMBDA ? SumType::LAMBDA : SumType::Q; s.tofChannelWidth = @@ -399,21 +376,6 @@ ReflectometryMomentumTransfer::createSetup( return s; } -/** Calculate the range of angles in the reflection plane determined by - * the collimation. - * - * @param setup a setup object - * @return the incident FWHM - */ -double -ReflectometryMomentumTransfer::incidentAngularSpread(const Setup &setup) { - // da in COSMOS - using namespace boost::math; - return FWHM_GAUSSIAN_EQUIVALENT * - std::sqrt((pow<2>(setup.slit1Size) + pow<2>(setup.slit2Size))) / - setup.slit1Slit2Distance; -} - /** Give the gap between the two slits, in meters. * * @param ws the reflectivity workspace @@ -424,77 +386,8 @@ double ReflectometryMomentumTransfer::interslitDistance( const std::string slit1Name = getProperty(Prop::SLIT1_NAME); const std::string slit2Name = getProperty(Prop::SLIT2_NAME); auto instrument = ws.getInstrument(); - auto slit1 = instrument->getComponentByName(slit1Name); - auto slit2 = instrument->getComponentByName(slit2Name); - return (slit1->getPos() - slit2->getPos()).norm(); -} - -/** Calculate sample RMS waviness. - * - * @param ws the reflectivity workspace - * @param directWS reference direct beam workspace - * @param wsIndex workspace index to the reflectivity workspace - * @param setup a setup object - * @param beamFWHM reflected beam RMS variation - * @param directBeamFWHM direct beam RMS variation - * @param incidentFWHM incident beam angular spread - * @return the waviness - */ -double ReflectometryMomentumTransfer::sampleWaviness( - API::MatrixWorkspace_sptr &ws, const API::MatrixWorkspace &directWS, - const size_t wsIndex, const Setup &setup, const double beamFWHM, - const double directBeamFWHM, const double incidentFWHM) { - // om_fwhm in COSMOS - using namespace boost::math; - const double slitSizeTolerance{0.00004}; - if (std::abs(setup.slit1Size - setup.slit1SizeDirectBeam) >= - slitSizeTolerance || - std::abs(setup.slit2Size - setup.slit2SizeDirectBeam) >= - slitSizeTolerance) { - // Differing slit sizes branch from COSMOS. - const double daDet = - detectorAngularResolution(*ws, wsIndex, setup, incidentFWHM); - if (beamFWHM - daDet >= 0) { - const auto a = std::sqrt(pow<2>(beamFWHM) - pow<2>(daDet)); - if (a >= setup.pixelSize) { - const auto directL2 = directWS.spectrumInfo().l2(wsIndex); - return 0.5 * a / directL2; - } - } - } else if (pow<2>(beamFWHM) - pow<2>(directBeamFWHM) >= 0) { - const auto a = std::sqrt(pow<2>(beamFWHM) - pow<2>(directBeamFWHM)); - if (a >= setup.pixelSize) { - const auto directL2 = directWS.spectrumInfo().l2(wsIndex); - return 0.5 * a / directL2; - } - } - return 0.; -} - -/** Calculate the angular spread due to the first slit. - * - * @param setup a reflected beam setup object - * @return the spread - */ -double ReflectometryMomentumTransfer::slit1AngularSpread(const Setup &setup) { - // S2_fwhm in COSMOS - return FWHM_GAUSSIAN_EQUIVALENT * setup.slit1Size / setup.slit1Slit2Distance; -} - -/** Calculate the angular spread due to the second slit. - * - * @param ws the reflectivity workspace - * @param wsIndex a workspace index to the reflectivity workspace - * @param setup reflected beam setup - * @return the spread - */ -double ReflectometryMomentumTransfer::slit2AngularSpread( - const API::MatrixWorkspace &ws, const size_t wsIndex, const Setup &setup) { - // s3_fwhm in COSMOS. - const auto &spectrumInfo = ws.spectrumInfo(); - const auto slit2Detector = - setup.slit2SampleDistance + spectrumInfo.l2(wsIndex); - return FWHM_GAUSSIAN_EQUIVALENT * setup.slit2Size / slit2Detector; + return ReflectometryBeamStatistics::slitSeparation(instrument, slit1Name, + slit2Name); } /** Read the slit size from samle logs. @@ -525,22 +418,16 @@ double ReflectometryMomentumTransfer::slitSize(const API::MatrixWorkspace &ws, /** Calculate the squared resolution due to wavelength variance. * - * @param ws the reflectivity workspace - * @param wsIndex a workspace index to the reflectivity workspace * @param setup reflected beam setup * @param wavelength wavelength, in meters * @return the fractional resolution squared */ double ReflectometryMomentumTransfer::wavelengthResolutionSquared( - const API::MatrixWorkspace &ws, const size_t wsIndex, const Setup &setup, - const double wavelength) { + const Setup &setup, const double wavelength) { // err_res in COSMOS using namespace boost::math; using namespace PhysicalConstants; - const auto &spectrumInfo = ws.spectrumInfo(); - const auto l1 = spectrumInfo.l1(); - const auto l2 = spectrumInfo.l2(wsIndex); - const auto flightDistance = l1 + l2; + const auto flightDistance = setup.l1 + setup.l2; const auto chopperResolution = setup.chopperPairDistance + h * setup.chopperOpening * setup.chopperPeriod / @@ -553,7 +440,7 @@ double ReflectometryMomentumTransfer::wavelengthResolutionSquared( 3. * chopperResolution * detectorResolution) / (2. * chopperResolution + detectorResolution) / flightDistance; const auto flightDistRatio = - (l1 - setup.slit2SampleDistance) / setup.slit1Slit2Distance; + (setup.l1 - setup.slit2SampleDistance) / setup.slit1Slit2Distance; const auto a = flightDistRatio * (setup.slit1Size + setup.slit2Size) + setup.slit1Size; const auto b = flightDistRatio * std::abs(setup.slit1Size - setup.slit2Size) + diff --git a/Framework/Algorithms/src/ReflectometryReductionOne2.cpp b/Framework/Algorithms/src/ReflectometryReductionOne2.cpp index f95ec2d82e557c105707e46dc139314b22698c1f..318b04456088057f29202af9a2ab134b70792e93 100644 --- a/Framework/Algorithms/src/ReflectometryReductionOne2.cpp +++ b/Framework/Algorithms/src/ReflectometryReductionOne2.cpp @@ -86,131 +86,6 @@ double getLambda(const HistogramX &xValues, const int xIdx) { return xValues[xIdx] + getLambdaRange(xValues, xIdx) / 2.0; } -/** - * Map a spectrum index from the given map to the given workspace - * @param originWS : the original workspace - * @param mapIdx : the index in the original workspace - * @param destWS : the destination workspace - * @return : the index in the destination workspace - */ -size_t mapSpectrumIndexToWorkspace(MatrixWorkspace_const_sptr originWS, - const size_t originIdx, - MatrixWorkspace_const_sptr destWS) { - - SpectrumNumber specId = originWS->indexInfo().spectrumNumber(originIdx); - size_t wsIdx = - destWS->getIndexFromSpectrumNumber(static_cast<specnum_t>(specId)); - return wsIdx; -} - -/** - * @param originWS : Origin workspace, which provides the original workspace - * index to spectrum number mapping. - * @param hostWS : Workspace onto which the resulting workspace indexes will be - * hosted - * @throws :: If the specId are not found to exist on the host end-point - *workspace. - * @return :: Remapped workspace indexes applicable for the host workspace, - *as a vector of groups of vectors of spectrum indices - */ -std::vector<std::vector<size_t>> mapSpectrumIndicesToWorkspace( - MatrixWorkspace_const_sptr originWS, MatrixWorkspace_const_sptr hostWS, - const std::vector<std::vector<size_t>> &detectorGroups) { - - std::vector<std::vector<size_t>> hostGroups; - - for (auto group : detectorGroups) { - std::vector<size_t> hostDetectors; - for (auto i : group) { - const size_t hostIdx = mapSpectrumIndexToWorkspace(originWS, i, hostWS); - hostDetectors.push_back(hostIdx); - } - hostGroups.push_back(hostDetectors); - } - - return hostGroups; -} - -/** - * Translate all the workspace indexes in an origin workspace into workspace - * indexes of a host end-point workspace. This is done using spectrum numbers as - * the intermediate. - * - * @param originWS : Origin workspace, which provides the original workspace - * index to spectrum number mapping. - * @param hostWS : Workspace onto which the resulting workspace indexes will be - * hosted - * @throws :: If the specId are not found to exist on the host end-point - *workspace. - * @return :: Remapped workspace indexes applicable for the host workspace, - *as comma separated string. - */ -std::string createProcessingCommandsFromDetectorWS( - MatrixWorkspace_const_sptr originWS, MatrixWorkspace_const_sptr hostWS, - const std::vector<std::vector<size_t>> &detectorGroups) { - - std::string result; - - // Map the original indices to the host workspace - std::vector<std::vector<size_t>> hostGroups = - mapSpectrumIndicesToWorkspace(originWS, hostWS, detectorGroups); - - // Add each group to the output, separated by ',' - - /// @todo Low priority: Add support to separate contiguous groups by ':' to - /// avoid having long lists of spectrum indices in the processing - /// instructions. This would not make any functional difference but would be - /// a cosmetic improvement when you view the history. - for (auto groupIt = hostGroups.begin(); groupIt != hostGroups.end(); - ++groupIt) { - const auto &hostDetectors = *groupIt; - - // Add each detector index to the output string separated by '+' to indicate - // that all detectors in this group will be summed. We also check for - // contiguous ranges so we output e.g. 3-5 instead of 3+4+5 - bool contiguous = false; - size_t contiguousStart = 0; - - for (auto it = hostDetectors.begin(); it != hostDetectors.end(); ++it) { - // Check if the next iterator is a contiguous increment from this one - auto nextIt = it + 1; - if (nextIt != hostDetectors.end() && *nextIt == *it + 1) { - // If this is a start of a new contiguous region, remember the start - // index - if (!contiguous) { - contiguousStart = *it; - contiguous = true; - } - // Continue to find the end of the contiguous region - continue; - } - - if (contiguous) { - // Output the contiguous range, then reset the flag - result.append(std::to_string(contiguousStart)) - .append("-") - .append(std::to_string(*it)); - contiguousStart = 0; - contiguous = false; - } else { - // Just output the value - result.append(std::to_string(*it)); - } - - // Add a separator ready for the next value/range - if (nextIt != hostDetectors.end()) { - result.append("+"); - } - } - - if (groupIt + 1 != hostGroups.end()) { - result.append(","); - } - } - - return result; -} - /** * Get the topbottom extent of a detector for the given axis * @@ -265,7 +140,7 @@ void ReflectometryReductionOne2::init() { "ProcessingInstructions", "", boost::make_shared<MandatoryValidator<std::string>>(), Direction::Input), - "Grouping pattern on workspace indexes to yield only " + "Grouping pattern on spectrum numbers to yield only " "the detectors of interest. See GroupDetectors for details."); // Minimum wavelength @@ -351,6 +226,10 @@ void ReflectometryReductionOne2::exec() { m_runWS = getProperty("InputWorkspace"); const auto xUnitID = m_runWS->getAxis(0)->unit()->unitID(); + // Handle processing instructions conversion from spectra number to workspace + // indexes + convertProcessingInstructions(m_runWS); + // Neither TOF or Lambda? Abort. if ((xUnitID != "Wavelength") && (xUnitID != "TOF")) throw std::invalid_argument( @@ -617,7 +496,6 @@ MatrixWorkspace_sptr ReflectometryReductionOne2::transOrAlgCorrection( MatrixWorkspace_sptr ReflectometryReductionOne2::transmissionCorrection( MatrixWorkspace_sptr detectorWS, const bool detectorWSReduced) { - const bool strictSpectrumChecking = getProperty("StrictSpectrumChecking"); MatrixWorkspace_sptr transmissionWS = getProperty("FirstTransmissionRun"); // Reduce the transmission workspace, if not already done (assume that if @@ -625,20 +503,15 @@ MatrixWorkspace_sptr ReflectometryReductionOne2::transmissionCorrection( Unit_const_sptr xUnit = transmissionWS->getAxis(0)->unit(); if (xUnit->unitID() == "TOF") { - // Processing instructions for transmission workspace. If strict spectrum - // checking is not enabled then just use the same processing instructions - // that were passed in. - std::string transmissionCommands = getProperty("ProcessingInstructions"); - if (strictSpectrumChecking) { - // If we have strict spectrum checking, we should have the same - // spectrum numbers in both workspaces, but not necessarily with the - // same workspace indices. Therefore, map the processing instructions - // from the original workspace to the correct indices in the - // transmission workspace. Note that we use the run workspace here - // because the detectorWS may already have been reduced and may not - // contain the original spectra. - transmissionCommands = createProcessingCommandsFromDetectorWS( - m_runWS, transmissionWS, detectorGroups()); + // If TransmissionProcessingInstructions are not passed then use + // passed processing instrucions + std::string transmissionCommands = ""; + if (getPointerToProperty("TransmissionProcessingInstructions") + ->isDefault()) { + transmissionCommands = m_processingInstructions; + } else { + transmissionCommands = + getPropertyValue("TransmissionProcessingInstructions"); } MatrixWorkspace_sptr secondTransmissionWS = @@ -678,7 +551,7 @@ MatrixWorkspace_sptr ReflectometryReductionOne2::transmissionCorrection( // If the detector workspace has been reduced then the spectrum maps // should match AFTER reducing the transmission workspace if (detectorWSReduced) { - verifySpectrumMaps(detectorWS, transmissionWS, strictSpectrumChecking); + verifySpectrumMaps(detectorWS, transmissionWS); } MatrixWorkspace_sptr normalized = divide(detectorWS, transmissionWS); @@ -779,7 +652,7 @@ bool ReflectometryReductionOne2::summingInQ() { * Find and cache the indicies of the detectors of interest */ void ReflectometryReductionOne2::findDetectorGroups() { - std::string instructions = getPropertyValue("ProcessingInstructions"); + std::string instructions = m_processingInstructionsWorkspaceIndex; m_detectorGroups = Kernel::Strings::parseGroups<size_t>(instructions); @@ -795,16 +668,17 @@ void ReflectometryReductionOne2::findDetectorGroups() { } for (const auto &group : m_detectorGroups) { - for (const auto &spIdx : group) { - if (m_spectrumInfo->isMonitor(spIdx)) { - throw std::invalid_argument("A detector is expected at spectrum " + - std::to_string(spIdx) + - ", found a monitor"); + for (const auto &wsIdx : group) { + if (m_spectrumInfo->isMonitor(wsIdx)) { + throw std::invalid_argument( + "A detector is expected at workspace index " + + std::to_string(wsIdx) + + " (Was converted from specnum), found a monitor"); } - if (spIdx > m_spectrumInfo->size() - 1) { + if (wsIdx > m_spectrumInfo->size() - 1) { throw std::runtime_error( "ProcessingInstructions contains an out-of-range index: " + - std::to_string(spIdx)); + std::to_string(wsIdx)); } } } @@ -1312,12 +1186,10 @@ Check whether the spectra for the given workspaces are the same. @param ws1 : First workspace to compare @param ws2 : Second workspace to compare against -@param severe: True to indicate that failure to verify should result in an exception. Otherwise a warning is generated. */ void ReflectometryReductionOne2::verifySpectrumMaps( - MatrixWorkspace_const_sptr ws1, MatrixWorkspace_const_sptr ws2, - const bool severe) { + MatrixWorkspace_const_sptr ws1, MatrixWorkspace_const_sptr ws2) { bool mismatch = false; // Check that the number of histograms is the same @@ -1338,13 +1210,8 @@ void ReflectometryReductionOne2::verifySpectrumMaps( if (mismatch) { const std::string message = "Spectrum maps between workspaces do NOT match up."; - if (severe) { - throw std::invalid_argument(message); - } else { - g_log.warning(message); - } + g_log.warning(message); } } - } // namespace Algorithms } // namespace Mantid diff --git a/Framework/Algorithms/src/ReflectometryReductionOneAuto2.cpp b/Framework/Algorithms/src/ReflectometryReductionOneAuto2.cpp index ca20b555ba60323d0953229e82ace2fb7a60647a..9a0ad2ed831489bab315be4ea9c99b7c5b673972 100644 --- a/Framework/Algorithms/src/ReflectometryReductionOneAuto2.cpp +++ b/Framework/Algorithms/src/ReflectometryReductionOneAuto2.cpp @@ -217,7 +217,7 @@ void ReflectometryReductionOneAuto2::init() { // Processing instructions declareProperty(make_unique<PropertyWithValue<std::string>>( "ProcessingInstructions", "", Direction::Input), - "Grouping pattern of workspace indices to yield only the" + "Grouping pattern of spectrum numbers to yield only the" " detectors of interest. See GroupDetectors for syntax."); // Theta @@ -365,12 +365,11 @@ void ReflectometryReductionOneAuto2::exec() { this, "WavelengthMax", instrument, "LambdaMax"); alg->setProperty("WavelengthMax", wavMax); - const auto instructions = - populateProcessingInstructions(alg, instrument, inputWS); - - // Now that we know the detectors of interest, we can move them if necessary - // (i.e. if theta is given). If not, we calculate theta from the current - // detector positions + convertProcessingInstructions(instrument, inputWS); + alg->setProperty("ProcessingInstructions", m_processingInstructions); + // Now that we know the detectors of interest, we can move them if + // necessary (i.e. if theta is given). If not, we calculate theta from the + // current detector positions bool correctDetectors = getProperty("CorrectDetectors"); double theta; if (!getPointerToProperty("ThetaIn")->isDefault()) { @@ -379,7 +378,7 @@ void ReflectometryReductionOneAuto2::exec() { theta = getThetaFromLogs(inputWS, getPropertyValue("ThetaLogName")); } else { // Calculate theta from detector positions - theta = calculateTheta(instructions, inputWS); + theta = calculateTheta(inputWS); // Never correct detector positions if ThetaIn or ThetaLogName is not // specified correctDetectors = false; @@ -389,19 +388,18 @@ void ReflectometryReductionOneAuto2::exec() { alg->setProperty("ThetaIn", theta); if (correctDetectors) { - inputWS = correctDetectorPositions(instructions, inputWS, 2 * theta); + inputWS = correctDetectorPositions(inputWS, 2 * theta); } // Optional properties + alg->setPropertyValue("TransmissionProcessingInstructions", + getPropertyValue("TransmissionProcessingInstructions")); populateMonitorProperties(alg, instrument); alg->setPropertyValue("NormalizeByIntegratedMonitors", getPropertyValue("NormalizeByIntegratedMonitors")); bool transRunsFound = populateTransmissionProperties(alg); - if (transRunsFound) - alg->setProperty("StrictSpectrumChecking", - getPropertyValue("StrictSpectrumChecking")); - else + if (!transRunsFound) populateAlgorithmicCorrectionProperties(alg, instrument); alg->setProperty("InputWorkspace", inputWS); @@ -437,15 +435,15 @@ void ReflectometryReductionOneAuto2::exec() { * last spectrum indices in the processing instructions. It is assumed that all * the interim detectors have the same parent. * - * @param instructions :: processing instructions defining detectors of interest * @param inputWS :: the input workspace * @return :: the names of the detectors of interest */ -std::vector<std::string> ReflectometryReductionOneAuto2::getDetectorNames( - const std::string &instructions, MatrixWorkspace_sptr inputWS) { +std::vector<std::string> +ReflectometryReductionOneAuto2::getDetectorNames(MatrixWorkspace_sptr inputWS) { std::vector<std::string> wsIndices; - boost::split(wsIndices, instructions, boost::is_any_of(":,-+")); + boost::split(wsIndices, m_processingInstructionsWorkspaceIndex, + boost::is_any_of(":,-+")); // vector of comopnents std::vector<std::string> detectors; @@ -466,7 +464,7 @@ std::vector<std::string> ReflectometryReductionOneAuto2::getDetectorNames( } } catch (boost::bad_lexical_cast &) { throw std::runtime_error("Invalid processing instructions: " + - instructions); + m_processingInstructionsWorkspaceIndex); } return detectors; @@ -475,17 +473,14 @@ std::vector<std::string> ReflectometryReductionOneAuto2::getDetectorNames( /** Correct an instrument component by shifting it vertically or * rotating it around the sample. * - * @param instructions :: processing instructions defining the detectors of - * interest * @param inputWS :: the input workspace * @param twoTheta :: the angle to move detectors to * @return :: the corrected workspace */ MatrixWorkspace_sptr ReflectometryReductionOneAuto2::correctDetectorPositions( - const std::string &instructions, MatrixWorkspace_sptr inputWS, - const double twoTheta) { + MatrixWorkspace_sptr inputWS, const double twoTheta) { - auto detectorsOfInterest = getDetectorNames(instructions, inputWS); + auto detectorsOfInterest = getDetectorNames(inputWS); // Detectors of interest may be empty. This happens for instance when we input // a workspace that was previously reduced using this algorithm. In this case, @@ -517,16 +512,13 @@ MatrixWorkspace_sptr ReflectometryReductionOneAuto2::correctDetectorPositions( /** Calculate the theta value of the detector of interest specified via * processing instructions * - * @param instructions :: processing instructions defining the detectors of - * interest * @param inputWS :: the input workspace * @return :: the angle of the detector (only the first detector is considered) */ double -ReflectometryReductionOneAuto2::calculateTheta(const std::string &instructions, - MatrixWorkspace_sptr inputWS) { +ReflectometryReductionOneAuto2::calculateTheta(MatrixWorkspace_sptr inputWS) { - const auto detectorsOfInterest = getDetectorNames(instructions, inputWS); + const auto detectorsOfInterest = getDetectorNames(inputWS); // Detectors of interest may be empty. This happens for instance when we input // a workspace that was previously reduced using this algorithm. In this case, @@ -804,6 +796,8 @@ bool ReflectometryReductionOneAuto2::processGroups() { if (!firstTransG) { alg->setProperty("FirstTransmissionRun", firstTrans); } else { + g_log.information("A group has been passed as the first transmission run " + "so the first run only is being used"); alg->setProperty("FirstTransmissionRun", firstTransG->getItem(0)); } } @@ -817,6 +811,8 @@ bool ReflectometryReductionOneAuto2::processGroups() { if (!secondTransG) { alg->setProperty("SecondTransmissionRun", secondTrans); } else { + g_log.information("A group has been passed as the second transmission " + "run so the first run only is being used"); alg->setProperty("secondTransmissionRun", secondTransG->getItem(0)); } } @@ -896,13 +892,19 @@ bool ReflectometryReductionOneAuto2::processGroups() { alg->setProperty("FirstTransmissionRun", ""); alg->setProperty("SecondTransmissionRun", ""); alg->setProperty("CorrectionAlgorithm", "None"); - alg->setProperty("ProcessingInstructions", "0"); + auto outputIvsLamNames = workspaceNamesInGroup(outputIvsLam); for (size_t i = 0; i < outputIvsLamNames.size(); ++i) { const std::string IvsQName = outputIvsQ + "_" + std::to_string(i + 1); const std::string IvsQBinnedName = outputIvsQBinned + "_" + std::to_string(i + 1); const std::string IvsLamName = outputIvsLamNames[i]; + + // Find the spectrum processing instructions for ws index 0 + auto currentWorkspace = boost::dynamic_pointer_cast<MatrixWorkspace>( + AnalysisDataService::Instance().retrieve(outputIvsLamNames[i])); + auto newProcInst = convertToSpectrumNumber("0", currentWorkspace); + alg->setProperty("ProcessingInstructions", newProcInst); alg->setProperty("InputWorkspace", IvsLamName); alg->setProperty("OutputWorkspace", IvsQName); alg->setProperty("OutputWorkspaceBinned", IvsQBinnedName); diff --git a/Framework/Algorithms/src/ReflectometryWorkflowBase2.cpp b/Framework/Algorithms/src/ReflectometryWorkflowBase2.cpp index 36bd251e2e11baa47fa78270092f1954e2a0ac6e..4c9374a205e35e7793f11648e6134c43e97ce89a 100644 --- a/Framework/Algorithms/src/ReflectometryWorkflowBase2.cpp +++ b/Framework/Algorithms/src/ReflectometryWorkflowBase2.cpp @@ -6,10 +6,12 @@ // SPDX - License - Identifier: GPL - 3.0 + #include "MantidAlgorithms/ReflectometryWorkflowBase2.h" #include "MantidAPI/Axis.h" +#include "MantidAPI/MatrixWorkspace.h" #include "MantidAPI/Run.h" #include "MantidAPI/WorkspaceUnitValidator.h" #include "MantidAlgorithms/BoostOptionalToAlgorithmProperty.h" #include "MantidGeometry/Instrument.h" +#include "MantidIndexing/IndexInfo.h" #include "MantidKernel/ArrayProperty.h" #include "MantidKernel/CompositeValidator.h" #include "MantidKernel/EnabledWhenProperty.h" @@ -23,6 +25,20 @@ using namespace Mantid::API; using namespace Mantid::Kernel; using namespace Mantid::Geometry; +namespace { +int convertStringNumToInt(const std::string &string) { + try { + auto returnValue = std::stoi(string.c_str()); + return returnValue; + } catch (std::invalid_argument &) { + throw std::runtime_error("Invalid argument for processing instructions"); + } catch (std::out_of_range &) { + throw std::runtime_error( + "Out of range value given for processing instructions"); + } +} +} // namespace + namespace Mantid { namespace Algorithms { @@ -121,18 +137,17 @@ void ReflectometryWorkflowBase2::initTransmissionProperties() { initStitchProperties(); - declareProperty(make_unique<PropertyWithValue<bool>>("StrictSpectrumChecking", - true, Direction::Input), - "Enforces spectrum number checking prior to normalization by " - "transmission workspace. Applies to input workspace and " - "transmission workspace."); + declareProperty( + make_unique<PropertyWithValue<std::string>>( + "TransmissionProcessingInstructions", "", Direction::Input), + "These processing instructions will be passed to the transmission " + "workspace algorithm"); setPropertyGroup("FirstTransmissionRun", "Transmission"); setPropertyGroup("SecondTransmissionRun", "Transmission"); setPropertyGroup("Params", "Transmission"); setPropertyGroup("StartOverlap", "Transmission"); setPropertyGroup("EndOverlap", "Transmission"); - setPropertyGroup("StrictSpectrumChecking", "Transmission"); } /** Initialize properties used for stitching transmission runs @@ -399,17 +414,22 @@ MatrixWorkspace_sptr ReflectometryWorkflowBase2::cropWavelength( wavelengthMin = getProperty("WavelengthMin"); wavelengthMax = getProperty("WavelengthMax"); } - - auto cropWorkspaceAlg = createChildAlgorithm("CropWorkspace"); - cropWorkspaceAlg->initialize(); - cropWorkspaceAlg->setProperty("InputWorkspace", inputWS); - cropWorkspaceAlg->setProperty("XMin", wavelengthMin); - cropWorkspaceAlg->setProperty("XMax", wavelengthMax); - cropWorkspaceAlg->execute(); - MatrixWorkspace_sptr outputWS = - cropWorkspaceAlg->getProperty("OutputWorkspace"); - - return outputWS; + try { + auto cropWorkspaceAlg = createChildAlgorithm("CropWorkspace"); + cropWorkspaceAlg->initialize(); + cropWorkspaceAlg->setProperty("InputWorkspace", inputWS); + cropWorkspaceAlg->setProperty("XMin", wavelengthMin); + cropWorkspaceAlg->setProperty("XMax", wavelengthMax); + cropWorkspaceAlg->execute(); + MatrixWorkspace_sptr outputWS = + cropWorkspaceAlg->getProperty("OutputWorkspace"); + + return outputWS; + } catch (std::out_of_range &e) { + throw std::runtime_error("The processing instruction(s) are likely out of " + "bounds on the workspace, actual error: " + + std::string(e.what())); + } } /** Process an input workspace in TOF according to specified processing commands @@ -421,12 +441,10 @@ MatrixWorkspace_sptr ReflectometryWorkflowBase2::cropWavelength( MatrixWorkspace_sptr ReflectometryWorkflowBase2::makeDetectorWS(MatrixWorkspace_sptr inputWS, const bool convert) { - - const std::string processingCommands = - getPropertyValue("ProcessingInstructions"); auto groupAlg = createChildAlgorithm("GroupDetectors"); groupAlg->initialize(); - groupAlg->setProperty("GroupingPattern", processingCommands); + groupAlg->setProperty("GroupingPattern", + m_processingInstructionsWorkspaceIndex); groupAlg->setProperty("InputWorkspace", inputWS); groupAlg->execute(); MatrixWorkspace_sptr detectorWS = groupAlg->getProperty("OutputWorkspace"); @@ -564,22 +582,13 @@ void ReflectometryWorkflowBase2::populateMonitorProperties( alg->setProperty("MonitorIntegrationWavelengthMax", integrationMax.get()); } -/** Set processing instructions - * - * @param alg :: ReflectometryReductionOne algorithm +/** Finding processing instructions from the parameters file * @param instrument :: the instrument attached to the workspace * @param inputWS :: the input workspace * @return :: processing instructions as a string */ -std::string ReflectometryWorkflowBase2::populateProcessingInstructions( - IAlgorithm_sptr alg, Instrument_const_sptr instrument, - MatrixWorkspace_sptr inputWS) const { - - if (!getPointerToProperty("ProcessingInstructions")->isDefault()) { - const std::string instructions = getProperty("ProcessingInstructions"); - alg->setProperty("ProcessingInstructions", instructions); - return instructions; - } +std::string ReflectometryWorkflowBase2::findProcessingInstructions( + Instrument_const_sptr instrument, MatrixWorkspace_sptr inputWS) const { const std::string analysisMode = getProperty("AnalysisMode"); @@ -601,7 +610,6 @@ std::string ReflectometryWorkflowBase2::populateProcessingInstructions( auto instructions = std::to_string(detStart); if (detStart != detStop) instructions += ":" + std::to_string(detStop); - alg->setProperty("ProcessingInstructions", instructions); return instructions; } @@ -616,7 +624,6 @@ std::string ReflectometryWorkflowBase2::populateProcessingInstructions( auto instructions = std::to_string(static_cast<int>(multiStart[0])) + ":" + std::to_string(inputWS->getNumberHistograms() - 1); - alg->setProperty("ProcessingInstructions", instructions); return instructions; } } @@ -691,5 +698,88 @@ ReflectometryWorkflowBase2::getRunNumber(MatrixWorkspace const &ws) const { return ""; } +std::string +ReflectometryWorkflowBase2::convertProcessingInstructionsToWorkspaceIndices( + const std::string &instructions, MatrixWorkspace_const_sptr ws) const { + std::string converted = ""; + std::string currentNumber = ""; + std::string ignoreThese = "-,:+"; + for (auto i = 0u; i < instructions.size(); ++i) { + if (std::find(ignoreThese.begin(), ignoreThese.end(), instructions[i]) != + ignoreThese.end()) { + // Found a spacer so add currentNumber to converted followed by separator + converted.append(convertToWorkspaceIndex(currentNumber, ws)); + converted.push_back(instructions[i]); + currentNumber = ""; + } else { + currentNumber.push_back(instructions[i]); + } + } + // Add currentNumber onto converted + converted.append(convertToWorkspaceIndex(currentNumber, ws)); + return converted; +} + +std::string ReflectometryWorkflowBase2::convertToWorkspaceIndex( + const std::string &spectrumNumber, MatrixWorkspace_const_sptr ws) const { + auto specNum = convertStringNumToInt(spectrumNumber); + std::string wsIdx = std::to_string( + ws->getIndexFromSpectrumNumber(static_cast<specnum_t>(specNum))); + return wsIdx; +} + +std::string +ReflectometryWorkflowBase2::convertProcessingInstructionsToSpectrumNumbers( + const std::string &instructions, + Mantid::API::MatrixWorkspace_const_sptr ws) const { + std::string converted = ""; + std::string currentNumber = ""; + std::string ignoreThese = "-,:+"; + for (auto i = 0u; i < instructions.size(); ++i) { + if (std::find(ignoreThese.begin(), ignoreThese.end(), instructions[i]) != + ignoreThese.end()) { + // Found a spacer so add currentNumber to converted after seperator + converted.append(convertToSpectrumNumber(currentNumber, ws)); + converted.push_back(instructions[i]); + currentNumber = ""; + } else { + currentNumber.push_back(instructions[i]); + } + } + // Add currentNumber onto converted + converted.append(convertToSpectrumNumber(currentNumber, ws)); + return converted; +} +std::string ReflectometryWorkflowBase2::convertToSpectrumNumber( + const std::string &workspaceIndex, + Mantid::API::MatrixWorkspace_const_sptr ws) const { + auto wsIndx = convertStringNumToInt(workspaceIndex); + std::string specId = std::to_string( + static_cast<int32_t>(ws->indexInfo().spectrumNumber(wsIndx))); + return specId; +} + +void ReflectometryWorkflowBase2::convertProcessingInstructions( + Instrument_const_sptr instrument, MatrixWorkspace_sptr inputWS) { + m_processingInstructions = getPropertyValue("ProcessingInstructions"); + if (!getPointerToProperty("ProcessingInstructions")->isDefault()) { + m_processingInstructionsWorkspaceIndex = + convertProcessingInstructionsToWorkspaceIndices( + m_processingInstructions, inputWS); + } else { + m_processingInstructionsWorkspaceIndex = + findProcessingInstructions(instrument, inputWS); + m_processingInstructions = convertProcessingInstructionsToSpectrumNumbers( + m_processingInstructionsWorkspaceIndex, inputWS); + } +} + +void ReflectometryWorkflowBase2::convertProcessingInstructions( + MatrixWorkspace_sptr inputWS) { + m_processingInstructions = getPropertyValue("ProcessingInstructions"); + m_processingInstructionsWorkspaceIndex = + convertProcessingInstructionsToWorkspaceIndices(m_processingInstructions, + inputWS); +} } // namespace Algorithms } // namespace Mantid diff --git a/Framework/Algorithms/src/SmoothNeighbours.cpp b/Framework/Algorithms/src/SmoothNeighbours.cpp index 617541c2d3a4b1306b84aca2b447a3edc6513630..91369a366328367f6b4e7941f60912f3b60c63ee 100644 --- a/Framework/Algorithms/src/SmoothNeighbours.cpp +++ b/Framework/Algorithms/src/SmoothNeighbours.cpp @@ -23,7 +23,6 @@ #include "MantidKernel/ListValidator.h" #include <boost/algorithm/string.hpp> -#include <boost/regex.hpp> using namespace Mantid::Kernel; using namespace Mantid::Geometry; diff --git a/Framework/Algorithms/src/SpecularReflectionPositionCorrect2.cpp b/Framework/Algorithms/src/SpecularReflectionPositionCorrect2.cpp index 80f13db0e4af5395dd03c2ba825db02cc1c5103b..2e0a983c66d9b6031e6dd00348c6cab577c06f6e 100644 --- a/Framework/Algorithms/src/SpecularReflectionPositionCorrect2.cpp +++ b/Framework/Algorithms/src/SpecularReflectionPositionCorrect2.cpp @@ -6,17 +6,54 @@ // SPDX - License - Identifier: GPL - 3.0 + #include "MantidAlgorithms/SpecularReflectionPositionCorrect2.h" #include "MantidAPI/MatrixWorkspace.h" +#include "MantidAPI/SpectrumInfo.h" #include "MantidGeometry/Instrument.h" #include "MantidGeometry/Instrument/ReferenceFrame.h" #include "MantidKernel/BoundedValidator.h" -#include "MantidKernel/CompositeValidator.h" #include "MantidKernel/ListValidator.h" -#include "MantidKernel/MandatoryValidator.h" using namespace Mantid::API; using namespace Mantid::Geometry; using namespace Mantid::Kernel; +namespace { +/// Enumerations to define the rotation plane of the detector. +enum class Plane { horizontal, vertical }; + +/** Return true if twoTheta increases with workspace index. + * @param spectrumInfo a spectrum info + * @return true if twoTheta increases with workspace index + */ +bool isAngleIncreasingWithIndex(const Mantid::API::SpectrumInfo &spectrumInfo) { + const auto first = spectrumInfo.signedTwoTheta(0); + const auto last = spectrumInfo.signedTwoTheta(spectrumInfo.size() - 1); + return first < last; +} + +/** Calculate a pixel's offset angle from detector centre. + * @param ws a workspace + * @param l2 sample to detector distance + * @param linePosition pixel's workspace index + * @return the offset angle, in radians + */ +double offsetAngleFromCentre(const MatrixWorkspace &ws, const double l2, + const double linePosition, + const double pixelSize) { + const auto &spectrumInfo = ws.spectrumInfo(); + const size_t nSpec = spectrumInfo.size(); + if (linePosition + 1. > static_cast<double>(nSpec)) { + std::ostringstream msg; + msg << "LinePosition is greater than the maximum workspace index " + << spectrumInfo.size() - 1; + throw std::runtime_error(msg.str()); + } + auto const centreIndex = static_cast<double>(nSpec - 1) / 2.; + auto const sign = isAngleIncreasingWithIndex(spectrumInfo) ? -1. : 1.; + double const offsetWidth = (centreIndex - linePosition) * pixelSize; + return sign * std::atan2(offsetWidth, l2); +} +} // namespace + namespace Mantid { namespace Algorithms { @@ -31,7 +68,7 @@ const std::string SpecularReflectionPositionCorrect2::name() const { /// Algorithm's summary. @see Algorithm::summary const std::string SpecularReflectionPositionCorrect2::summary() const { - return "Corrects a detector component's position based on TwoTheta."; + return "Corrects a reflectometer's detector component's position."; } /// Algorithm's version for identification. @see Algorithm::version @@ -53,22 +90,14 @@ void SpecularReflectionPositionCorrect2::init() { "InputWorkspace", "", Direction::Input), "An input workspace to correct."); - auto thetaValidator = boost::make_shared<CompositeValidator>(); - thetaValidator->add(boost::make_shared<MandatoryValidator<double>>()); - thetaValidator->add( - boost::make_shared<BoundedValidator<double>>(0, 90, true)); - declareProperty( - make_unique<PropertyWithValue<double>>("TwoTheta", Mantid::EMPTY_DBL(), - thetaValidator, Direction::Input), - "Angle used to correct the detector component."); + declareProperty(make_unique<PropertyWithValue<double>>( + "TwoTheta", Mantid::EMPTY_DBL(), Direction::Input), + "Angle used to correct the detector component [degrees]."); const std::vector<std::string> correctionType{"VerticalShift", "RotateAroundSample"}; - auto correctionTypeValidator = boost::make_shared<CompositeValidator>(); - correctionTypeValidator->add( - boost::make_shared<MandatoryValidator<std::string>>()); - correctionTypeValidator->add( - boost::make_shared<StringListValidator>(correctionType)); + auto correctionTypeValidator = + boost::make_shared<StringListValidator>(correctionType); declareProperty( "DetectorCorrectionType", correctionType[0], correctionTypeValidator, "Whether detectors should be shifted vertically or rotated around the " @@ -78,80 +107,140 @@ void SpecularReflectionPositionCorrect2::init() { declareProperty( Mantid::Kernel::make_unique<PropertyWithValue<std::string>>( "DetectorComponentName", "", Direction::Input), - "Name of the detector component to correct, i.e. point-detector"); - - declareProperty("DetectorID", -1, - "The ID of the detector to correct. If both " + "Name of the detector component to correct, for example point-detector"); + auto nonNegativeInt = boost::make_shared<Kernel::BoundedValidator<int>>(); + nonNegativeInt->setLower(0); + declareProperty("DetectorID", EMPTY_INT(), nonNegativeInt, + "The ID of the detector to correct; if both " "the component name and the detector ID " "are set the latter will be used."); declareProperty( Mantid::Kernel::make_unique<PropertyWithValue<std::string>>( "SampleComponentName", "some-surface-holder", Direction::Input), - "Name of the sample component, i.e. some-surface-holder"); + "Name of the sample component; if the given name is not found in the " + "instrument, the default sample is used."); declareProperty( Mantid::Kernel::make_unique<WorkspaceProperty<MatrixWorkspace>>( "OutputWorkspace", "", Direction::Output), - "An output workspace."); + "A workspace with corrected detector position."); + declareProperty("DetectorFacesSample", false, + "If true, a normal vector at the centre of the detector " + "always points towards the sample."); + auto nonNegativeDouble = + boost::make_shared<Kernel::BoundedValidator<double>>(); + nonNegativeDouble->setLower(0.); + declareProperty("LinePosition", EMPTY_DBL(), nonNegativeDouble, + "A fractional workspace index for the specular line centre."); + declareProperty("DirectLinePosition", EMPTY_DBL(), nonNegativeDouble, + "A fractional workspace index for the direct line centre."); + auto positiveDouble = boost::make_shared<Kernel::BoundedValidator<double>>(); + nonNegativeDouble->setLowerExclusive(0.); + declareProperty("PixelSize", EMPTY_DBL(), positiveDouble, + "Size of a detector pixel, in metres."); + declareProperty( + make_unique<WorkspaceProperty<MatrixWorkspace>>( + "DirectLineWorkspace", "", Direction::Input, PropertyMode::Optional), + "A direct beam workspace for reference."); +} + +/// Validate the algorithm's inputs. +std::map<std::string, std::string> +SpecularReflectionPositionCorrect2::validateInputs() { + std::map<std::string, std::string> issues; + if (isDefault("DetectorID") && isDefault("DetectorComponentName")) { + issues["DetectorID"] = + "DetectorID or DetectorComponentName has to be specified."; + } + if (!isDefault("TwoTheta")) { + if (!isDefault("LinePosition") && isDefault("PixelSize")) { + issues["PixelSize"] = "Pixel size required."; + } + } else { + if (isDefault("DirectLinePosition")) { + issues["DirectLinePosition"] = + "Direct line position required when no TwoTheta supplied."; + } + if (isDefault("DirectLineWorkspace")) { + issues["DirectLineWorkspace"] = + "Direct beam workspace required when no TwoTheta supplied."; + } + if (isDefault("PixelSize")) { + issues["PixelSize"] = "Pixel size required for direct beam calibration."; + } + } + return issues; } -//---------------------------------------------------------------------------------------------- /** Execute the algorithm. */ void SpecularReflectionPositionCorrect2::exec() { MatrixWorkspace_sptr inWS = this->getProperty("InputWorkspace"); - - auto cloneWS = createChildAlgorithm("CloneWorkspace"); - cloneWS->initialize(); - cloneWS->setProperty("InputWorkspace", inWS); - cloneWS->execute(); - Workspace_sptr tmp = cloneWS->getProperty("OutputWorkspace"); - MatrixWorkspace_sptr outWS = - boost::dynamic_pointer_cast<MatrixWorkspace>(tmp); - - const double twoThetaIn = getProperty("TwoTheta"); - const double twoThetaInRad = twoThetaIn * (M_PI / 180.0); - - auto inst = outWS->getInstrument(); - - // Detector - const int detectorID = getProperty("DetectorID"); - const std::string detectorName = getProperty("DetectorComponentName"); - - IComponent_const_sptr detector; - if (detectorID > 0) { - detector = inst->getDetector(detectorID); - } else { - if (!inst->getComponentByName(detectorName)) - throw std::runtime_error("Detector component not found."); - detector = inst->getComponentByName(detectorName); + MatrixWorkspace_sptr outWS = getProperty("OutputWorkspace"); + if (outWS != inWS) { + outWS = inWS->clone(); } - const V3D detectorPosition = detector->getPos(); - // Sample - const std::string sampleName = getProperty("SampleComponentName"); - if (!inst->getComponentByName(sampleName)) - throw std::runtime_error("Sample component not found."); - IComponent_const_sptr sample = inst->getComponentByName(sampleName); - const V3D samplePosition = sample->getPos(); + const V3D samplePosition = declareSamplePosition(*inWS); // Type of movement (vertical shift or rotation around the sample) const std::string correctionType = getProperty("DetectorCorrectionType"); + // Detector + auto inst = inWS->getInstrument(); + const detid_t detectorID = static_cast<int>(getProperty("DetectorID")); + const std::string detectorName = getProperty("DetectorComponentName"); + const V3D detectorPosition = + declareDetectorPosition(*inst, detectorName, detectorID); // Sample-to-detector const V3D sampleToDetector = detectorPosition - samplePosition; - // Reference frame + const double l2 = sampleToDetector.norm(); auto referenceFrame = inst->getReferenceFrame(); - auto beamAxis = referenceFrame->pointingAlongBeamAxis(); - auto horizontalAxis = referenceFrame->pointingHorizontalAxis(); - auto upAxis = referenceFrame->pointingUpAxis(); + const auto alongDir = referenceFrame->vecPointingAlongBeam(); + const double beamOffsetOld = sampleToDetector.scalar_prod(alongDir); + + const double twoThetaInRad = + isDefault("TwoTheta") + ? twoThetaFromDirectLine(detectorName, detectorID, samplePosition, l2, + alongDir, beamOffsetOld) + : twoThetaFromProperties(*inWS, l2); + + correctDetectorPosition(outWS, detectorName, detectorID, twoThetaInRad, + correctionType, *referenceFrame, samplePosition, + sampleToDetector, beamOffsetOld); + setProperty("OutputWorkspace", outWS); +} + +/** + * Move and rotate the detector to its correct position + * @param outWS the workspace to modify + * @param detectorName name of the detector component + * @param detectorID detector component's id + * @param twoThetaInRad beam-detector angle in radians + * @param correctionType type of correction + * @param referenceFrame instrument's reference frame + * @param samplePosition sample position + * @param sampleToDetector a vector from sample to detector + * @param beamOffsetOld sample detector distance on the beam axis + */ +void SpecularReflectionPositionCorrect2::correctDetectorPosition( + MatrixWorkspace_sptr &outWS, const std::string &detectorName, + const detid_t detectorID, const double twoThetaInRad, + const std::string &correctionType, const ReferenceFrame &referenceFrame, + const V3D &samplePosition, const V3D &sampleToDetector, + const double beamOffsetOld) { + const auto beamAxis = referenceFrame.pointingAlongBeamAxis(); + const auto horizontalAxis = referenceFrame.pointingHorizontalAxis(); + const auto upAxis = referenceFrame.pointingUpAxis(); + const auto thetaSignDir = referenceFrame.vecThetaSign(); + const auto upDir = referenceFrame.vecPointingUp(); + const auto plane = thetaSignDir.scalar_prod(upDir) == 1. ? Plane::vertical + : Plane::horizontal; // Get the offset from the sample in the beam direction double beamOffset = 0.0; - const double beamOffsetOld = - sampleToDetector.scalar_prod(referenceFrame->vecPointingAlongBeam()); if (correctionType == "VerticalShift") { // Only shifting vertically, so the beam offset remains the same @@ -159,27 +248,25 @@ void SpecularReflectionPositionCorrect2::exec() { } else if (correctionType == "RotateAroundSample") { // The radius for the rotation is the distance from the sample to // the detector in the Beam-Vertical plane - const double upOffsetOld = - sampleToDetector.scalar_prod(referenceFrame->vecPointingUp()); - const double radius = - std::sqrt(beamOffsetOld * beamOffsetOld + upOffsetOld * upOffsetOld); + const double perpendicularOffsetOld = + sampleToDetector.scalar_prod(thetaSignDir); + const double radius = std::hypot(beamOffsetOld, perpendicularOffsetOld); beamOffset = radius * std::cos(twoThetaInRad); } else { // Shouldn't get here unless there's been a coding error std::ostringstream message; - message << "Invalid correction type '" << correctionType; + message << "Invalid correction type '" << correctionType << "'"; throw std::runtime_error(message.str()); } // Calculate the offset in the vertical direction, and the total // offset in the beam direction - const double upOffset = (beamOffset * std::tan(twoThetaInRad)); + const double perpendicularOffset = beamOffset * std::tan(twoThetaInRad); const double beamOffsetFromOrigin = beamOffset + - samplePosition.scalar_prod(referenceFrame->vecPointingAlongBeam()); + samplePosition.scalar_prod(referenceFrame.vecPointingAlongBeam()); auto moveAlg = createChildAlgorithm("MoveInstrumentComponent"); - moveAlg->initialize(); moveAlg->setProperty("Workspace", outWS); if (!detectorName.empty()) { moveAlg->setProperty("ComponentName", detectorName); @@ -188,11 +275,130 @@ void SpecularReflectionPositionCorrect2::exec() { } moveAlg->setProperty("RelativePosition", false); moveAlg->setProperty(beamAxis, beamOffsetFromOrigin); - moveAlg->setProperty(horizontalAxis, 0.0); - moveAlg->setProperty(upAxis, upOffset); + if (plane == Plane::vertical) { + moveAlg->setProperty(horizontalAxis, 0.0); + moveAlg->setProperty(upAxis, perpendicularOffset); + } else { + moveAlg->setProperty(horizontalAxis, perpendicularOffset); + moveAlg->setProperty(upAxis, 0.0); + } moveAlg->execute(); - setProperty("OutputWorkspace", outWS); + const bool rotateFace = getProperty("DetectorFacesSample"); + if (rotateFace) { + auto rotate = createChildAlgorithm("RotateInstrumentComponent"); + rotate->setProperty("Workspace", outWS); + if (!detectorName.empty()) + rotate->setProperty("ComponentName", detectorName); + else + rotate->setProperty("DetectorID", detectorID); + if (plane == Plane::horizontal) { + rotate->setProperty("X", upDir.X()); + rotate->setProperty("Y", upDir.Y()); + rotate->setProperty("Z", upDir.Z()); + } else { + const V3D horizontalDir = -referenceFrame.vecPointingHorizontal(); + rotate->setProperty("X", horizontalDir.X()); + rotate->setProperty("Y", horizontalDir.Y()); + rotate->setProperty("Z", horizontalDir.Z()); + } + rotate->setProperty("RelativeRotation", false); + rotate->setProperty("Angle", twoThetaInRad * 180. / M_PI); + rotate->execute(); + } +} + +/** + * Return the detector's position + * @param inst an instrument + * @param detectorName detector component's name + * @param detectorID detector's id + * @return a position + */ +Kernel::V3D SpecularReflectionPositionCorrect2::declareDetectorPosition( + const Geometry::Instrument &inst, const std::string &detectorName, + const detid_t detectorID) { + // Detector + IComponent_const_sptr detector; + if (detectorName.empty()) { + detector = inst.getDetector(detectorID); + } else { + detector = inst.getComponentByName(detectorName); + if (!detector) + throw Exception::NotFoundError("Detector component not found", + detectorName); + } + return detector->getPos(); +} + +/** + * Return the sample position + * @param ws a workspace + * @return a position + */ +V3D SpecularReflectionPositionCorrect2::declareSamplePosition( + const MatrixWorkspace &ws) { + V3D position; + const std::string sampleName = getProperty("SampleComponentName"); + auto inst = ws.getInstrument(); + IComponent_const_sptr sample = inst->getComponentByName(sampleName); + if (sample) + position = sample->getPos(); + else + position = ws.spectrumInfo().samplePosition(); + return position; +} + +/** + * Return the user-given TwoTheta, augmented by LinePosition if needed + * @param inWS the input workspace + * @param l2 sample-to-detector distance + * @return TwoTheta, in radians + */ +double SpecularReflectionPositionCorrect2::twoThetaFromProperties( + const MatrixWorkspace &inWS, const double l2) { + double twoThetaInRad = + static_cast<double>(getProperty("TwoTheta")) * M_PI / 180.0; + if (!isDefault("LinePosition")) { + const double linePosition = getProperty("LinePosition"); + const double pixelSize = getProperty("PixelSize"); + const double offset = + offsetAngleFromCentre(inWS, l2, linePosition, pixelSize); + twoThetaInRad -= offset; + } + return twoThetaInRad; +} + +/** + * Return a direct beam calibrated TwoTheta + * @param detectorName name of the detector component + * @param detectorID detector's id + * @param samplePosition sample position + * @param l2 sample-to-detector distance + * @param alongDir a unit vector pointing along the beam + * @param beamOffset sample-to-detector distance on the beam axis + * @return TwoTheta, in radians + */ +double SpecularReflectionPositionCorrect2::twoThetaFromDirectLine( + const std::string &detectorName, const detid_t detectorID, + const V3D &samplePosition, const double l2, const V3D &alongDir, + const double beamOffset) { + double twoThetaInRad; + MatrixWorkspace_sptr directWS = getProperty("DirectLineWorkspace"); + const double directLinePosition = getProperty("DirectLinePosition"); + const double pixelSize = getProperty("PixelSize"); + const double directOffset = + offsetAngleFromCentre(*directWS, l2, directLinePosition, pixelSize); + const auto reflectedDetectorAngle = std::acos(beamOffset / l2); + auto directInst = directWS->getInstrument(); + const auto directDetPos = + declareDetectorPosition(*directInst, detectorName, detectorID); + const auto directSampleToDet = directDetPos - samplePosition; + const double directBeamOffset = directSampleToDet.scalar_prod(alongDir); + const double directL2 = directSampleToDet.norm(); + const auto directDetectorAngle = std::acos(directBeamOffset / directL2); + twoThetaInRad = reflectedDetectorAngle - directDetectorAngle - directOffset; + return twoThetaInRad; } } // namespace Algorithms diff --git a/Framework/Algorithms/src/Stitch1DMany.cpp b/Framework/Algorithms/src/Stitch1DMany.cpp index 051bc72214bd5e869c7471d6fc21b53123d5dd38..e2570533823c0d11ebf9bd536e2740872874a2e4 100644 --- a/Framework/Algorithms/src/Stitch1DMany.cpp +++ b/Framework/Algorithms/src/Stitch1DMany.cpp @@ -14,7 +14,6 @@ #include "MantidKernel/BoundedValidator.h" #include "MantidKernel/RebinParamsValidator.h" #include "MantidKernel/VisibleWhenProperty.h" - #include <boost/make_shared.hpp> using namespace Mantid::Kernel; @@ -42,19 +41,18 @@ void Stitch1DMany::init() { declareProperty( make_unique<ArrayProperty<double>>("StartOverlaps", Direction::Input), - "Start overlaps for stitched workspaces; if specified, the number of " - "StartOverlaps must be 1 less than the number of input workspaces. " - "Optional."); + "Start overlaps for stitched workspaces " + "(number of input workspaces minus one)."); declareProperty( make_unique<ArrayProperty<double>>("EndOverlaps", Direction::Input), - "End overlaps for stitched workspaces; if specified, the number of " - "EndOverlaps must be the same as the number of StartOverlaps."); + "End overlaps for stitched workspaces " + "(number of input workspaces minus one)."); declareProperty(make_unique<PropertyWithValue<bool>>("ScaleRHSWorkspace", true, Direction::Input), "Scaling either with respect to first (first hand side, LHS) " - "or second (right hand side, RHS) workspace"); + "or second (right hand side, RHS) workspace."); declareProperty(make_unique<PropertyWithValue<bool>>("UseManualScaleFactors", false, Direction::Input), @@ -62,10 +60,9 @@ void Stitch1DMany::init() { declareProperty(make_unique<ArrayProperty<double>>("ManualScaleFactors", Direction::Input), - "Provided values for the scale factors; if specified, the " - "number of ManualScaleFactors must either be one (in which " - "case the provided value is applied to all input workspaces) " - "or 1 less than the number of input workspaces"); + "Either a single scale factor which will be applied to all " + "input workspaces or individual scale factors " + "(number of input workspaces minus one)"); setPropertySettings("ManualScaleFactors", make_unique<VisibleWhenProperty>("UseManualScaleFactors", IS_EQUAL_TO, "1")); @@ -109,7 +106,7 @@ std::map<std::string, std::string> Stitch1DMany::validateInputs() { /* * Column: one column of MatrixWorkspaces or several columns of * MatrixWorkspaces from a group - * Row: each period + * Row: each period only for groups */ m_inputWSMatrix.reserve(inputWorkspacesStr.size()); std::vector<MatrixWorkspace_sptr> column; @@ -135,11 +132,6 @@ std::map<std::string, std::string> Stitch1DMany::validateInputs() { } } - // Either stitch MatrixWorkspaces or workspaces of the group - size_t numStitchableWS = (workspaces.size() == inputWorkspacesStr.size()) - ? workspaces.size() - : inputWorkspacesStr.size(); - if (m_inputWSMatrix.empty()) { // no group workspaces // A column of matrix workspaces will be stitched RunCombinationHelper combHelper; @@ -179,18 +171,16 @@ std::map<std::string, std::string> Stitch1DMany::validateInputs() { } } } - } - - int scaleFactorFromPeriod = this->getProperty("ScaleFactorFromPeriod"); - m_scaleFactorFromPeriod = static_cast<size_t>(scaleFactorFromPeriod); - m_scaleFactorFromPeriod--; // To account for period being indexed from - // 1 - if (m_scaleFactorFromPeriod >= m_inputWSMatrix.size()) { - std::stringstream expectedRange; - expectedRange << m_inputWSMatrix.size(); - issues["ScaleFactorFromPeriod"] = - "Period index out of range, must be smaller than " + - expectedRange.str(); + int scaleFactorFromPeriod = this->getProperty("ScaleFactorFromPeriod"); + // Period -1 corresponds to workspace index + m_scaleFactorFromPeriod = static_cast<size_t>(--scaleFactorFromPeriod); + if (m_scaleFactorFromPeriod >= m_inputWSMatrix.front().size()) { + std::stringstream expectedRange; + expectedRange << m_inputWSMatrix.front().size() + 1; + issues["ScaleFactorFromPeriod"] = + "Period index out of range, must be smaller than " + + expectedRange.str(); + } } m_startOverlaps = this->getProperty("StartOverlaps"); @@ -198,6 +188,10 @@ std::map<std::string, std::string> Stitch1DMany::validateInputs() { m_scaleRHSWorkspace = this->getProperty("ScaleRHSWorkspace"); m_params = this->getProperty("Params"); + // Either stitch MatrixWorkspaces or workspaces of the group + size_t numStitchableWS = (workspaces.size() == inputWorkspacesStr.size()) + ? workspaces.size() + : inputWorkspacesStr.size(); std::stringstream expectedVal; expectedVal << numStitchableWS - 1; if (!m_startOverlaps.empty() && @@ -243,12 +237,9 @@ void Stitch1DMany::exec() { std::string outName; // Determine whether or not we are scaling workspaces using scale - // factors - // from a specific period - Property *manualSF = this->getProperty("ManualScaleFactors"); - bool usingScaleFromPeriod = m_useManualScaleFactors && - manualSF->isDefault() && - m_scaleFactorFromPeriod; + // factors from a specific period + const bool usingScaleFromPeriod = + m_useManualScaleFactors && isDefault("ManualScaleFactors"); if (!usingScaleFromPeriod) { for (size_t i = 0; i < m_inputWSMatrix.front().size(); ++i) { @@ -268,9 +259,10 @@ void Stitch1DMany::exec() { // Obtain scale factors for the specified period std::string tempOutName; std::vector<double> periodScaleFactors; + constexpr bool storeInADS = false; doStitch1DMany(m_scaleFactorFromPeriod, false, tempOutName, - periodScaleFactors); + periodScaleFactors, storeInADS); // Iterate over each period for (size_t i = 0; i < m_inputWSMatrix.front().size(); ++i) { @@ -280,9 +272,7 @@ void Stitch1DMany::exec() { outName = groupName; Workspace_sptr outStitchedWS; - doStitch1D(inMatrix, periodScaleFactors, outStitchedWS, outName); - // Add name of stitched workspaces to group list and ADS toGroup.emplace_back(outName); AnalysisDataService::Instance().addOrReplace(outName, outStitchedWS); @@ -363,11 +353,13 @@ void Stitch1DMany::doStitch1D(std::vector<MatrixWorkspace_sptr> &toStitch, * factors * @param outName :: Output stitched workspace name * @param outScaleFactors :: Actual values used for scale factors + * @param storeInADS :: Whether to store in ADS or not */ void Stitch1DMany::doStitch1DMany(const size_t period, const bool useManualScaleFactors, std::string &outName, - std::vector<double> &outScaleFactors) { + std::vector<double> &outScaleFactors, + const bool storeInADS) { // List of workspaces to stitch std::vector<std::string> toProcess; @@ -380,7 +372,7 @@ void Stitch1DMany::doStitch1DMany(const size_t period, IAlgorithm_sptr alg = createChildAlgorithm("Stitch1DMany"); alg->initialize(); - alg->setChild(false); + alg->setAlwaysStoreInADS(storeInADS); alg->setProperty("InputWorkspaces", toProcess); if (!outName.empty()) alg->setProperty("OutputWorkspace", outName); diff --git a/Framework/Algorithms/test/CMakeLists.txt b/Framework/Algorithms/test/CMakeLists.txt index 0af518f5afc4981455c30108cbcbb47a10ec77f9..402e6f7a712604359f0ecb1519c57a38a7f832f3 100644 --- a/Framework/Algorithms/test/CMakeLists.txt +++ b/Framework/Algorithms/test/CMakeLists.txt @@ -32,9 +32,10 @@ if ( CXXTEST_FOUND ) # It will go out of scope at the end of this file so doesn't need un-setting set ( TESTHELPER_SRCS ../../TestHelpers/src/ComponentCreationHelper.cpp ../../TestHelpers/src/FileComparisonHelper.cpp + ../../TestHelpers/src/IndirectFitDataCreationHelper.cpp ../../TestHelpers/src/InstrumentCreationHelper.cpp ../../TestHelpers/src/MDEventsTestHelper.cpp - ../../TestHelpers/src/MuonWorkspaceCreationHelper.cpp + ../../TestHelpers/src/MuonWorkspaceCreationHelper.cpp ../../TestHelpers/src/SANSInstrumentCreationHelper.cpp ../../TestHelpers/src/ScopedFileHelper.cpp ../../TestHelpers/src/TearDownWorld.cpp diff --git a/Framework/Algorithms/test/CalculateDynamicRangeTest.h b/Framework/Algorithms/test/CalculateDynamicRangeTest.h new file mode 100644 index 0000000000000000000000000000000000000000..1cf6c51c2947799d1f834443e0adbab7180fc9d9 --- /dev/null +++ b/Framework/Algorithms/test/CalculateDynamicRangeTest.h @@ -0,0 +1,110 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_ALGORITHMS_CALCULATEDYNAMICRANGETEST_H_ +#define MANTID_ALGORITHMS_CALCULATEDYNAMICRANGETEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "MantidAPI/FrameworkManager.h" +#include "MantidAPI/MatrixWorkspace.h" +#include "MantidAPI/Run.h" +#include "MantidAlgorithms/CalculateDynamicRange.h" +#include "MantidAlgorithms/CreateSampleWorkspace.h" +#include "MantidDataHandling/MaskDetectorsInShape.h" +#include "MantidDataHandling/MoveInstrumentComponent.h" + +#include <boost/cast.hpp> + +using Mantid::API::FrameworkManager; +using Mantid::API::MatrixWorkspace; +using Mantid::API::MatrixWorkspace_sptr; +using Mantid::API::Workspace; +using Mantid::API::Workspace_sptr; +using Mantid::Algorithms::CalculateDynamicRange; +using Mantid::Algorithms::CreateSampleWorkspace; +using Mantid::DataHandling::MaskDetectorsInShape; +using Mantid::DataHandling::MoveInstrumentComponent; + +class CalculateDynamicRangeTest : public CxxTest::TestSuite { +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static CalculateDynamicRangeTest *createSuite() { + return new CalculateDynamicRangeTest(); + } + static void destroySuite(CalculateDynamicRangeTest *suite) { delete suite; } + + CalculateDynamicRangeTest() { FrameworkManager::Instance(); } + + void test_init() { + CalculateDynamicRange alg; + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + } + + void test_exec() { + CalculateDynamicRange alg; + alg.setChild(true); + alg.setRethrows(true); + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + MatrixWorkspace_sptr ws = create_workspace(); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("Workspace", ws)) + TS_ASSERT_THROWS_NOTHING(alg.execute();); + TS_ASSERT(alg.isExecuted()); + ws = alg.getProperty("Workspace"); + TS_ASSERT(ws); + const auto &run = ws->run(); + TS_ASSERT(run.hasProperty("qmin")) + TS_ASSERT(run.hasProperty("qmax")) + const double qmin = run.getPropertyAsSingleValue("qmin"); + const double qmax = run.getPropertyAsSingleValue("qmax"); + TS_ASSERT_DELTA(qmin, 0.03553, 1E-5) + TS_ASSERT_DELTA(qmax, 0.88199, 1E-5) + } + +private: + MatrixWorkspace_sptr create_workspace() { + CreateSampleWorkspace creator; + creator.initialize(); + creator.setChild(true); + creator.setPropertyValue("OutputWorkspace", "__unused"); + creator.setProperty("XUnit", "Wavelength"); + creator.setProperty("NumBanks", 1); + creator.setProperty("PixelSpacing", 0.1); + creator.setProperty("XMin", 1.); + creator.setProperty("XMax", 5.); + creator.setProperty("BinWidth", 0.4); + creator.execute(); + MatrixWorkspace_sptr sampleWS = creator.getProperty("OutputWorkspace"); + MoveInstrumentComponent mover; + mover.initialize(); + mover.setChild(true); + mover.setProperty("Workspace", sampleWS); + mover.setProperty("ComponentName", "bank1"); + mover.setProperty("RelativePosition", true); + mover.setProperty("Y", -0.5); + mover.setProperty("X", -0.5); + mover.execute(); + Workspace_sptr movedWS = mover.getProperty("Workspace"); + const std::string shapeXML = "<infinite-cylinder id ='A'>" + "<centre x ='0' y ='0' z ='0'/>" + "<axis x = '0' y = '0' z = '1'/>" + "<radius val = '0.1'/>" + "</infinite-cylinder>"; + MaskDetectorsInShape masker; + masker.initialize(); + masker.setChild(true); + masker.setProperty("Workspace", movedWS); + masker.setPropertyValue("ShapeXML", shapeXML); + masker.execute(); + MatrixWorkspace_sptr maskedWS = masker.getProperty("Workspace"); + return maskedWS; + } +}; + +#endif /* MANTID_ALGORITHMS_CALCULATEDYNAMICRANGETEST_H_ */ diff --git a/Framework/Algorithms/test/CreateSampleWorkspaceTest.h b/Framework/Algorithms/test/CreateSampleWorkspaceTest.h index 2b6b89344942d8759a55f0dc6c3e15c8f6784858..8f4d21e18ed54cc7e8e8feef39239c15c0ecba8c 100644 --- a/Framework/Algorithms/test/CreateSampleWorkspaceTest.h +++ b/Framework/Algorithms/test/CreateSampleWorkspaceTest.h @@ -546,7 +546,7 @@ public: const auto radiansToDegrees = 180.0 / M_PI; // The centre pixel should go from 0 -> 10 degrees, all at the same l2 - for (size_t j = 0; j < detectorInfo.scanCount(centreDetector); ++j) { + for (size_t j = 0; j < detectorInfo.scanCount(); ++j) { const auto index = std::pair<size_t, size_t>(centreDetector, j); TS_ASSERT_DELTA(10.0, detectorInfo.l2(index), 1e-10); TS_ASSERT_DELTA(j, detectorInfo.twoTheta(index) * radiansToDegrees, diff --git a/Framework/Algorithms/test/CreateTransmissionWorkspace2Test.h b/Framework/Algorithms/test/CreateTransmissionWorkspace2Test.h index 7c3bdd9c12b7c51bebcb8f950ea0fb1499e9bdb3..48aac6f65539434fb6b71079ce8b47d4ff30604f 100644 --- a/Framework/Algorithms/test/CreateTransmissionWorkspace2Test.h +++ b/Framework/Algorithms/test/CreateTransmissionWorkspace2Test.h @@ -54,7 +54,7 @@ public: alg.initialize(); alg.setChild(true); alg.setProperty("FirstTransmissionRun", m_multiDetectorWS); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); alg.setPropertyValue("OutputWorkspace", "outWS"); @@ -78,7 +78,7 @@ public: alg.initialize(); alg.setChild(true); alg.setProperty("FirstTransmissionRun", m_multiDetectorWS); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("WavelengthMax", 15.0); alg.setPropertyValue("OutputWorkspace", "outWS"); TS_ASSERT_THROWS_ANYTHING(alg.execute()); @@ -90,7 +90,7 @@ public: alg.initialize(); alg.setChild(true); alg.setProperty("FirstTransmissionRun", m_multiDetectorWS); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("WavelengthMin", 1.5); alg.setPropertyValue("OutputWorkspace", "outWS"); TS_ASSERT_THROWS_ANYTHING(alg.execute()); @@ -114,7 +114,7 @@ public: alg.initialize(); alg.setChild(true); alg.setProperty("FirstTransmissionRun", m_multiDetectorWS); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("WavelengthMin", 15.0); alg.setProperty("WavelengthMax", 1.5); alg.setPropertyValue("OutputWorkspace", "outWS"); @@ -127,7 +127,7 @@ public: alg.initialize(); alg.setChild(true); alg.setProperty("FirstTransmissionRun", m_multiDetectorWS); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); alg.setProperty("MonitorBackgroundWavelengthMin", 15.0); @@ -142,7 +142,7 @@ public: alg.initialize(); alg.setChild(true); alg.setProperty("FirstTransmissionRun", m_multiDetectorWS); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); alg.setProperty("MonitorIntegrationWavelengthMin", 1.0); @@ -159,7 +159,7 @@ public: alg.setProperty("FirstTransmissionRun", m_multiDetectorWS); alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); - alg.setPropertyValue("ProcessingInstructions", "1"); + alg.setPropertyValue("ProcessingInstructions", "2"); alg.setPropertyValue("OutputWorkspace", "outWS"); alg.execute(); MatrixWorkspace_sptr outLam = alg.getProperty("OutputWorkspace"); @@ -182,7 +182,7 @@ public: alg.setProperty("FirstTransmissionRun", m_multiDetectorWS); alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); - alg.setPropertyValue("ProcessingInstructions", "1+2"); + alg.setPropertyValue("ProcessingInstructions", "2+3"); alg.setPropertyValue("OutputWorkspace", "outWS"); alg.execute(); MatrixWorkspace_sptr outLam = alg.getProperty("OutputWorkspace"); @@ -219,7 +219,7 @@ public: alg.setProperty("I0MonitorIndex", "0"); alg.setProperty("MonitorBackgroundWavelengthMin", 0.5); alg.setProperty("MonitorBackgroundWavelengthMax", 3.0); - alg.setPropertyValue("ProcessingInstructions", "1"); + alg.setPropertyValue("ProcessingInstructions", "2"); alg.setPropertyValue("OutputWorkspace", "outWS"); alg.execute(); MatrixWorkspace_sptr outLam = alg.getProperty("OutputWorkspace"); @@ -257,7 +257,7 @@ public: alg.setProperty("MonitorBackgroundWavelengthMax", 3.0); alg.setProperty("MonitorIntegrationWavelengthMin", 1.5); alg.setProperty("MonitorIntegrationWavelengthMax", 15.0); - alg.setPropertyValue("ProcessingInstructions", "1"); + alg.setPropertyValue("ProcessingInstructions", "2"); alg.setPropertyValue("OutputWorkspace", "outWS"); alg.execute(); MatrixWorkspace_sptr outLam = alg.getProperty("OutputWorkspace"); @@ -280,7 +280,7 @@ public: alg.setProperty("SecondTransmissionRun", m_multiDetectorWS); alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); - alg.setPropertyValue("ProcessingInstructions", "1"); + alg.setPropertyValue("ProcessingInstructions", "2"); alg.setPropertyValue("OutputWorkspace", "outWS"); alg.execute(); MatrixWorkspace_sptr outLam = alg.getProperty("OutputWorkspace"); @@ -303,7 +303,7 @@ public: alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); alg.setProperty("Params", "0.1"); - alg.setPropertyValue("ProcessingInstructions", "1"); + alg.setPropertyValue("ProcessingInstructions", "2"); alg.setPropertyValue("OutputWorkspace", "outWS"); alg.execute(); MatrixWorkspace_sptr outLam = alg.getProperty("OutputWorkspace"); @@ -328,7 +328,7 @@ public: alg.setProperty("FirstTransmissionRun", inputWS); alg.setProperty("WavelengthMin", 3.0); alg.setProperty("WavelengthMax", 12.0); - alg.setPropertyValue("ProcessingInstructions", "1"); + alg.setPropertyValue("ProcessingInstructions", "2"); alg.setPropertyValue("OutputWorkspace", "outWS"); alg.execute(); @@ -354,7 +354,7 @@ public: alg.setProperty("FirstTransmissionRun", inputWS); alg.setProperty("WavelengthMin", 3.0); alg.setProperty("WavelengthMax", 12.0); - alg.setPropertyValue("ProcessingInstructions", "1"); + alg.setPropertyValue("ProcessingInstructions", "2"); alg.execute(); TS_ASSERT(AnalysisDataService::Instance().doesExist("TRANS_LAM_1234")); @@ -382,7 +382,7 @@ public: alg.setProperty("SecondTransmissionRun", inputWS2); alg.setProperty("WavelengthMin", 3.0); alg.setProperty("WavelengthMax", 12.0); - alg.setPropertyValue("ProcessingInstructions", "1"); + alg.setPropertyValue("ProcessingInstructions", "2"); alg.setPropertyValue("OutputWorkspace", "outWS"); alg.execute(); MatrixWorkspace_sptr firstLam = @@ -422,7 +422,7 @@ public: alg.setProperty("SecondTransmissionRun", inputWS2); alg.setProperty("WavelengthMin", 3.0); alg.setProperty("WavelengthMax", 12.0); - alg.setPropertyValue("ProcessingInstructions", "1"); + alg.setPropertyValue("ProcessingInstructions", "2"); alg.execute(); MatrixWorkspace_sptr firstLam = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>( @@ -460,7 +460,7 @@ public: alg.setProperty("SecondTransmissionRun", inputWS2); alg.setProperty("WavelengthMin", 3.0); alg.setProperty("WavelengthMax", 12.0); - alg.setPropertyValue("ProcessingInstructions", "1"); + alg.setPropertyValue("ProcessingInstructions", "2"); alg.execute(); MatrixWorkspace_sptr outWS = alg.getProperty("OutputWorkspace"); TS_ASSERT(outWS); diff --git a/Framework/Algorithms/test/CreateTransmissionWorkspaceAuto2Test.h b/Framework/Algorithms/test/CreateTransmissionWorkspaceAuto2Test.h index 56dcf8cb997d62a7f3ba834992b30f8d8728c296..a7267121f66215ac8f207fd1ba93f61fad6c97ba 100644 --- a/Framework/Algorithms/test/CreateTransmissionWorkspaceAuto2Test.h +++ b/Framework/Algorithms/test/CreateTransmissionWorkspaceAuto2Test.h @@ -89,7 +89,7 @@ public: alg->setProperty("FirstTransmissionRun", m_dataWS); alg->setPropertyValue("OutputWorkspace", "outWS"); - alg->execute(); + TS_ASSERT_THROWS_NOTHING(alg->execute()); TS_ASSERT(alg->isExecuted()); MatrixWorkspace_sptr outWS = @@ -118,10 +118,14 @@ public: vecPropertyHistories, "MonitorIntegrationWavelengthMax"); const int i0MonitorIndex = findPropertyValue<int>(vecPropertyHistories, "I0MonitorIndex"); - const std::string processingInstructions = findPropertyValue<std::string>( - vecPropertyHistories, "ProcessingInstructions"); - std::vector<std::string> pointDetectorStartStop; - boost::split(pointDetectorStartStop, processingInstructions, + const std::string processingInstructionsString = + findPropertyValue<std::string>(vecPropertyHistories, + "ProcessingInstructions"); + // In workspace indices form the processing instructions should be the same + // However this has been changed to specNum and that is +1 of the instrument + // Parameter files value for PointDetectorStart + std::vector<std::string> processingInstructionsList; + boost::split(processingInstructionsList, processingInstructionsString, boost::is_any_of(":")); auto inst = m_dataWS->getInstrument(); @@ -143,9 +147,10 @@ public: monitorIntegrationWavelengthMax); TS_ASSERT_EQUALS(inst->getNumberParameter("I0MonitorIndex").at(0), i0MonitorIndex); - TS_ASSERT_EQUALS(inst->getNumberParameter("PointDetectorStart").at(0), - boost::lexical_cast<double>(pointDetectorStartStop.at(0))); - TS_ASSERT_EQUALS(pointDetectorStartStop.size(), 1); + TS_ASSERT_EQUALS( + inst->getNumberParameter("PointDetectorStart").at(0), + boost::lexical_cast<double>(processingInstructionsList.at(0)) - 1); + TS_ASSERT_EQUALS(processingInstructionsList.size(), 1); AnalysisDataService::Instance().remove("outWS"); } diff --git a/Framework/Algorithms/test/GenerateIPythonNotebookTest.h b/Framework/Algorithms/test/GenerateIPythonNotebookTest.h index 2580f0614b14ce7f15a6f637a897f5fda5677fbb..b1e0539ef8136c8f0253c7404618b84d21535329 100644 --- a/Framework/Algorithms/test/GenerateIPythonNotebookTest.h +++ b/Framework/Algorithms/test/GenerateIPythonNotebookTest.h @@ -9,7 +9,6 @@ #include "MantidKernel/System.h" #include "MantidKernel/Timer.h" -#include <boost/regex.hpp> #include <cxxtest/TestSuite.h> #include <fstream> diff --git a/Framework/Algorithms/test/GeneratePythonScriptTest.h b/Framework/Algorithms/test/GeneratePythonScriptTest.h index 5b536c93b18e8e862a7aa0f4bb3ccbc17adb720d..72f0212221a5d47c6ac932c1653a3ed6ae03e2c6 100644 --- a/Framework/Algorithms/test/GeneratePythonScriptTest.h +++ b/Framework/Algorithms/test/GeneratePythonScriptTest.h @@ -27,7 +27,6 @@ #include "MantidKernel/make_unique.h" #include <Poco/File.h> -#include <boost/regex.hpp> #include <fstream> @@ -85,7 +84,6 @@ public: "#Python Script Generated by GeneratePythonScript Algorithm", "#####################################################################" "#", - "NonExistingAlgorithm()", "CreateWorkspace(OutputWorkspace='" "testGeneratePython', DataX='1,2,3,5,6', " "DataY='7,9,16,4,3', DataE='2,3,4,2,1', " @@ -94,6 +92,7 @@ public: "OutputWorkspace='testGeneratePython', XMin=2, XMax=5)", "Power(InputWorkspace='testGeneratePython', " "OutputWorkspace='testGeneratePython', Exponent=1.5)", + "NonExistingAlgorithm()", ""}; // Set up and execute the algorithm. @@ -122,8 +121,8 @@ public: // Verify that if we set the content of ScriptText that it is set correctly. alg.setPropertyValue("ScriptText", result[5]); TS_ASSERT_EQUALS(alg.getPropertyValue("ScriptText"), - "CropWorkspace(InputWorkspace='testGeneratePython', " - "OutputWorkspace='testGeneratePython', XMin=2, XMax=5)"); + "Power(InputWorkspace='testGeneratePython', " + "OutputWorkspace='testGeneratePython', Exponent=1.5)"); file.close(); if (Poco::File(filename).exists()) @@ -178,21 +177,6 @@ public: pAlg.reset(nullptr); } - - MatrixWorkspace_sptr createSimpleWorkspace(const std::vector<double> xData, - const std::vector<double> yData, - const std::vector<double> eData, - const int nSpec = 1) { - Workspace2D_sptr outputWorkspace = create<DataObjects::Workspace2D>( - nSpec, Mantid::HistogramData::Histogram( - Mantid::HistogramData::Points(xData.size()))); - for (int i = 0; i < nSpec; ++i) { - outputWorkspace->mutableY(i) = yData; - outputWorkspace->mutableE(i) = eData; - outputWorkspace->mutableX(i) = xData; - } - return outputWorkspace; - } }; #endif /* MANTID_ALGORITHMS_GENERATEPYTHONSCRIPTTEST_H_ */ diff --git a/Framework/Algorithms/test/IndirectFitDataCreationHelperTest.h b/Framework/Algorithms/test/IndirectFitDataCreationHelperTest.h new file mode 100644 index 0000000000000000000000000000000000000000..d110ed764ef2b88f621fe0a397ba83f9936f73c2 --- /dev/null +++ b/Framework/Algorithms/test/IndirectFitDataCreationHelperTest.h @@ -0,0 +1,186 @@ +#ifndef MANTID_INDIRECTFITDATACREATIONHELPERTEST_H_ +#define MANTID_INDIRECTFITDATACREATIONHELPERTEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/MatrixWorkspace.h" +#include "MantidTestHelpers/IndirectFitDataCreationHelper.h" + +using namespace Mantid::API; +using namespace Mantid::IndirectFitDataCreationHelper; + +namespace { + +void storeWorkspaceInADS(std::string const &workspaceName, + MatrixWorkspace_sptr workspace) { + SetUpADSWithWorkspace ads(workspaceName, workspace); +} + +/// The classes TypeOne and TypeTwo are used to test AreSpectraEquals which +/// compares the values of boost::variant types +class TypeOne { +public: + explicit TypeOne(const std::string &value) : m_value(value) {} + + const std::string &getValue() const { return m_value; } + + bool operator==(TypeOne const &value) const { + return this->getValue() == value.getValue(); + } + +private: + std::string m_value; +}; + +class TypeTwo { +public: + explicit TypeTwo(const std::size_t &value) : m_value(value) {} + + const std::size_t &getValue() const { return m_value; } + + bool operator==(TypeTwo const &value) const { + return this->getValue() == value.getValue(); + } + +private: + std::size_t m_value; +}; + +using Types = boost::variant<TypeOne, TypeTwo>; + +} // namespace + +class IndirectFitDataCreationHelperTest : public CxxTest::TestSuite { +public: + static IndirectFitDataCreationHelperTest *createSuite() { + return new IndirectFitDataCreationHelperTest(); + } + + static void destroySuite(IndirectFitDataCreationHelperTest *suite) { + delete suite; + } + + void tearDown() override { AnalysisDataService::Instance().clear(); } + + void + test_that_createWorkspace_returns_a_workspace_with_the_number_of_spectra_specified() { + auto const workspace = createWorkspace(10); + TS_ASSERT(workspace); + TS_ASSERT_EQUALS(workspace->getNumberHistograms(), 10); + } + + void + test_that_createInstrumentWorkspace_returns_a_workspace_with_the_number_of_spectra_specified() { + auto const workspace = createInstrumentWorkspace(6, 5); + TS_ASSERT(workspace); + TS_ASSERT_EQUALS(workspace->getNumberHistograms(), 6); + } + + void + test_that_setWorkspaceEFixed_does_not_throw_when_setting_EFixed_values() { + auto workspace = createInstrumentWorkspace(6, 5); + TS_ASSERT_THROWS_NOTHING(setWorkspaceEFixed(workspace, 6)); + } + + void test_that_setWorkspaceBinEdges_returns_a_workspace_with_binEdges_set() { + auto const workspace = + setWorkspaceBinEdges(createInstrumentWorkspace(6, 5), 6, 5); + for (std::size_t i = 0; i < workspace->getNumberHistograms(); ++i) + TS_ASSERT(workspace->binEdges(i)); + } + + void + test_that_setWorkspaceBinEdges_throws_when_provided_an_out_of_range_number_of_spectra() { + auto workspace = createInstrumentWorkspace(6, 5); + TS_ASSERT_THROWS_ANYTHING(setWorkspaceBinEdges(workspace, 8, 5)); + } + + void + test_that_SetUpADSWithWorkspace_returns_true_when_a_workspace_exists_in_the_ads() { + auto const workspace = createWorkspace(10); + + SetUpADSWithWorkspace ads("WorkspaceName", workspace); + + TS_ASSERT(ads.doesExist("WorkspaceName")); + } + + void + test_that_SetUpADSWithWorkspace_returns_false_when_a_workspace_does_not_exists_in_the_ads() { + auto const workspace = createWorkspace(10); + + SetUpADSWithWorkspace ads("WorkspaceName", workspace); + + TS_ASSERT(!ads.doesExist("WorkspaceWrongName")); + } + + void + test_that_SetUpADSWithWorkspace_retrieves_the_given_workspace_as_expected() { + auto const workspace = createWorkspace(10); + + SetUpADSWithWorkspace ads("WorkspaceName", workspace); + + TS_ASSERT(ads.doesExist("WorkspaceName")); + auto const storedWorkspace = ads.retrieveWorkspace("WorkspaceName"); + TS_ASSERT(storedWorkspace) + TS_ASSERT_EQUALS(storedWorkspace->getNumberHistograms(), 10); + } + + void + test_that_SetUpADSWithWorkspace_is_instantiated_with_the_given_workspace_as_expected() { + auto const workspace = createWorkspace(10); + + SetUpADSWithWorkspace ads("WorkspaceName", workspace); + + auto const storedWorkspace = ads.retrieveWorkspace("WorkspaceName"); + TS_ASSERT_EQUALS(storedWorkspace, workspace); + TS_ASSERT_EQUALS(storedWorkspace->getNumberHistograms(), 10); + } + + void + test_that_SetUpADSWithWorkspace_adds_a_given_workspace_to_the_ads_as_expected() { + auto const workspace = createWorkspace(10); + + SetUpADSWithWorkspace ads("WorkspaceName1", workspace); + ads.addOrReplace("WorkspaceName2", workspace); + + TS_ASSERT(ads.doesExist("WorkspaceName1")); + TS_ASSERT(ads.doesExist("WorkspaceName2")); + auto const storedWorkspace = ads.retrieveWorkspace("WorkspaceName2"); + TS_ASSERT_EQUALS(storedWorkspace->getNumberHistograms(), 10); + } + + void + test_that_the_ads_instance_is_not_destructed_when_it_goes_out_of_scope() { + auto const workspace = createWorkspace(10); + + storeWorkspaceInADS("WorkspaceName", workspace); + + TS_ASSERT(AnalysisDataService::Instance().doesExist("WorkspaceName")); + } + + void + test_that_AreSpectraEqual_returns_true_when_given_two_identical_values_of_same_type() { + Types const value1 = TypeOne("SameValue"); + Types const value2 = TypeOne("SameValue"); + TS_ASSERT(boost::apply_visitor(AreSpectraEqual(), value1, value2)); + } + + void + test_that_AreSpectraEqual_returns_false_when_given_two_different_values_of_the_same_type() { + Types const value1 = TypeOne("Value"); + Types const value2 = TypeOne("DifferentValue"); + + TS_ASSERT(!boost::apply_visitor(AreSpectraEqual(), value1, value2)); + } + + void + test_that_AreSpectraEqual_returns_false_when_given_two_different_values_with_different_types() { + Types const value1 = TypeOne("Value"); + Types const value2 = TypeTwo(2); + + TS_ASSERT(!boost::apply_visitor(AreSpectraEqual(), value1, value2)); + } +}; + +#endif // MANTID_INDIRECTFITDATACREATIONHELPER_TEST diff --git a/Framework/Algorithms/test/LineProfileTest.h b/Framework/Algorithms/test/LineProfileTest.h index 2fc6d8c54b07991622dd9a6bae51d980ada6ac12..740da3e737f85b47cb6563b78c1a6a39a4fac6b4 100644 --- a/Framework/Algorithms/test/LineProfileTest.h +++ b/Framework/Algorithms/test/LineProfileTest.h @@ -18,6 +18,10 @@ #include "MantidDataObjects/WorkspaceCreation.h" #include "MantidTestHelpers/WorkspaceCreationHelper.h" +#include <boost/uuid/uuid.hpp> +#include <boost/uuid/uuid_generators.hpp> +#include <boost/uuid/uuid_io.hpp> + using Mantid::Algorithms::CompareWorkspaces; using Mantid::Algorithms::LineProfile; using namespace Mantid::API; @@ -409,7 +413,8 @@ public: MatrixWorkspace_sptr inputWS = create2DWorkspace154(nHist, nBins); auto &oldHistory = inputWS->history(); auto historyEntry = boost::make_shared<AlgorithmHistory>( - "LineProfileTestDummyAlgorithmName", 1); + "LineProfileTestDummyAlgorithmName", 1, + boost::uuids::to_string(boost::uuids::random_generator()())); oldHistory.addHistory(historyEntry); const int start = 2; const int end = nBins - 2; diff --git a/Framework/Algorithms/test/MaskBinsIfTest.h b/Framework/Algorithms/test/MaskBinsIfTest.h new file mode 100644 index 0000000000000000000000000000000000000000..6cb44d48f82d4b870b9bff2273803ce80796b6f7 --- /dev/null +++ b/Framework/Algorithms/test/MaskBinsIfTest.h @@ -0,0 +1,136 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_ALGORITHMS_MASKBINSIFTEST_H_ +#define MANTID_ALGORITHMS_MASKBINSIFTEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "MantidAPI/FrameworkManager.h" +#include "MantidAPI/MatrixWorkspace.h" +#include "MantidAlgorithms/CreateSampleWorkspace.h" +#include "MantidAlgorithms/CreateWorkspace.h" +#include "MantidAlgorithms/MaskBinsIf.h" + +using Mantid::API::MatrixWorkspace_sptr; +using Mantid::Algorithms::CreateSampleWorkspace; +using Mantid::Algorithms::CreateWorkspace; +using Mantid::Algorithms::MaskBinsIf; + +class MaskBinsIfTest : public CxxTest::TestSuite { +private: + MatrixWorkspace_sptr createWorkspace() { + CreateWorkspace creator; + creator.initialize(); + creator.setChild(true); + creator.setAlwaysStoreInADS(false); + const std::vector<double> x = {1.1, 2.5, 3.2, 4.5, 6.7, 8.9, + 10.3, 12.4, 13.9, 14.1, 15.3, 16.8}; + const std::vector<double> y = {7, 23, 54, 34, 23, 64, + 34, 23, 58, 63, 34, 25}; + const std::vector<double> e = {3.2, 2.1, 8.4, 3.5, 6.3, 4.7, + 4.9, 3.6, 4.1, 6.7, 5.1, 3.2}; + const std::vector<double> dx = {0.1, 0.2, 0.4, 0.7, 0.9, 1.3, + 1.5, 1.7, 1.9, 1.2, 4.5, 2.3}; + const std::vector<std::string> spectrumAxis = {"3", "7", "11", "17"}; + creator.setProperty("DataX", x); + creator.setProperty("DataY", y); + creator.setProperty("DataE", e); + creator.setProperty("Dx", dx); + creator.setProperty("NSpec", 4); + creator.setProperty("VerticalAxisValues", spectrumAxis); + creator.setProperty("VerticalAxisUnit", "Label"); + creator.setProperty("OutputWorkspace", "__unused"); + creator.execute(); + MatrixWorkspace_sptr ws = creator.getProperty("OutputWorkspace"); + return ws; + } + +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static MaskBinsIfTest *createSuite() { return new MaskBinsIfTest(); } + static void destroySuite(MaskBinsIfTest *suite) { delete suite; } + + void test_init() { + MaskBinsIf alg; + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + } + + void test_exec() { + MaskBinsIf alg; + alg.setChild(true); + alg.setRethrows(true); + alg.setAlwaysStoreInADS(false); + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + const auto inputWS = createWorkspace(); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspace", inputWS)); + TS_ASSERT_THROWS_NOTHING( + alg.setPropertyValue("OutputWorkspace", "__unused_for_child")); + const std::string criterion = "y>50 || e>6 || s<5 || dx>1.6"; + TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Criterion", criterion)); + TS_ASSERT_THROWS_NOTHING(alg.execute()); + TS_ASSERT(alg.isExecuted()); + Mantid::API::MatrixWorkspace_sptr outputWS = + alg.getProperty("OutputWorkspace"); + TS_ASSERT(outputWS); + const auto maskedBins1 = outputWS->maskedBins(0); + TS_ASSERT(maskedBins1.find(0) != maskedBins1.end()); + TS_ASSERT(maskedBins1.find(1) != maskedBins1.end()); + TS_ASSERT(maskedBins1.find(2) != maskedBins1.end()); + const auto maskedBins2 = outputWS->maskedBins(1); + TS_ASSERT(maskedBins2.find(0) == maskedBins2.end()); + TS_ASSERT(maskedBins2.find(1) != maskedBins2.end()); + TS_ASSERT(maskedBins2.find(2) != maskedBins2.end()); + const auto maskedBins3 = outputWS->maskedBins(2); + TS_ASSERT(maskedBins3.find(0) == maskedBins3.end()); + TS_ASSERT(maskedBins3.find(1) != maskedBins3.end()); + TS_ASSERT(maskedBins3.find(2) != maskedBins3.end()); + const auto maskedBins4 = outputWS->maskedBins(3); + TS_ASSERT(maskedBins4.find(0) != maskedBins4.end()); + TS_ASSERT(maskedBins4.find(1) != maskedBins4.end()); + TS_ASSERT(maskedBins4.find(2) != maskedBins4.end()); + } +}; + +class MaskBinsIfTestPerformance : public CxxTest::TestSuite { +public: + static MaskBinsIfTestPerformance *createSuite() { + return new MaskBinsIfTestPerformance(); + } + static void destroySuite(MaskBinsIfTestPerformance *suite) { delete suite; } + + void setUp() override { + Mantid::API::FrameworkManager::Instance(); + CreateSampleWorkspace creator; + creator.initialize(); + creator.setChild(true); + creator.setAlwaysStoreInADS(false); + creator.setProperty("BankPixelWidth", 100); + creator.setProperty("NumBanks", 20); + creator.setProperty("BinWidth", 200.); + creator.setProperty("Random", true); + creator.setPropertyValue("OutputWorkspace", "__unused"); + creator.execute(); + Mantid::API::MatrixWorkspace_sptr ws = + creator.getProperty("OutputWorkspace"); + m_alg.initialize(); + m_alg.setChild(true); + m_alg.setAlwaysStoreInADS(false); + m_alg.setProperty("InputWorkspace", ws); + m_alg.setPropertyValue("Criterion", "y>100 || y<1"); + m_alg.setProperty("OutputWorkspace", "__out"); + } + + void test_performance() { m_alg.execute(); } + +private: + MaskBinsIf m_alg; +}; + +#endif /* MANTID_ALGORITHMS_MASKBINSIFTEST_H_ */ diff --git a/Framework/Algorithms/test/MergeRunsTest.h b/Framework/Algorithms/test/MergeRunsTest.h index fb5b57d1e6de2bc42c7a8c6f3a0c870582dd24f7..8977e0beb170f5f7579f464b952a6f93303fe7f4 100644 --- a/Framework/Algorithms/test/MergeRunsTest.h +++ b/Framework/Algorithms/test/MergeRunsTest.h @@ -1547,10 +1547,8 @@ public: const auto PAIR_1 = std::pair<DateAndTime, DateAndTime>(TIME_1, TIME_2); const auto PAIR_2 = std::pair<DateAndTime, DateAndTime>(TIME_2, TIME_3); - TS_ASSERT_EQUALS(detInfo.scanInterval({0, 0}), PAIR_1) - TS_ASSERT_EQUALS(detInfo.scanInterval({1, 0}), PAIR_1) - TS_ASSERT_EQUALS(detInfo.scanInterval({0, 1}), PAIR_2) - TS_ASSERT_EQUALS(detInfo.scanInterval({1, 1}), PAIR_2) + TS_ASSERT_EQUALS(detInfo.scanIntervals()[0], PAIR_1) + TS_ASSERT_EQUALS(detInfo.scanIntervals()[1], PAIR_2) if (extraTimes) { const auto TIME_4 = DateAndTime(20, 0); @@ -1560,10 +1558,8 @@ public: const auto PAIR_3 = std::pair<DateAndTime, DateAndTime>(TIME_4, TIME_5); const auto PAIR_4 = std::pair<DateAndTime, DateAndTime>(TIME_5, TIME_6); - TS_ASSERT_EQUALS(detInfo.scanInterval({0, 2}), PAIR_3) - TS_ASSERT_EQUALS(detInfo.scanInterval({1, 2}), PAIR_3) - TS_ASSERT_EQUALS(detInfo.scanInterval({0, 3}), PAIR_4) - TS_ASSERT_EQUALS(detInfo.scanInterval({1, 3}), PAIR_4) + TS_ASSERT_EQUALS(detInfo.scanIntervals()[2], PAIR_3) + TS_ASSERT_EQUALS(detInfo.scanIntervals()[3], PAIR_4) } } @@ -1618,8 +1614,7 @@ public: const auto &detInfo = outputWS->detectorInfo(); TS_ASSERT_EQUALS(detInfo.size(), 2) - TS_ASSERT_EQUALS(detInfo.scanCount(0), 4) - TS_ASSERT_EQUALS(detInfo.scanCount(1), 4) + TS_ASSERT_EQUALS(detInfo.scanCount(), 4) assert_scan_intervals_are_correct(detInfo, true); const auto &specInfo = outputWS->spectrumInfo(); @@ -1640,16 +1635,15 @@ public: alg.setPropertyValue("OutputWorkspace", "outWS"); TS_ASSERT_THROWS_EQUALS(alg.execute(), const std::runtime_error &e, std::string(e.what()), - "Cannot merge DetectorInfo: " - "sync scan intervals " - "overlap but not identical") + "Cannot merge ComponentInfo: " + "scan intervals overlap but not identical") } void test_merging_detector_scan_workspaces_does_not_append_workspaces() { auto outputWS = do_MergeRuns_with_scanning_workspaces(); TS_ASSERT_EQUALS(outputWS->detectorInfo().size(), 2) - TS_ASSERT_EQUALS(outputWS->detectorInfo().scanCount(0), 2) + TS_ASSERT_EQUALS(outputWS->detectorInfo().scanCount(), 2) TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 4) // Check bins are set correctly @@ -1674,7 +1668,7 @@ public: alg.setPropertyValue("OutputWorkspace", "outWS"); TS_ASSERT_THROWS_EQUALS(alg.execute(), const std::runtime_error &e, std::string(e.what()), - "Cannot merge DetectorInfo: " + "Cannot merge ComponentInfo: " "matching scan interval but " "positions differ") } @@ -1695,9 +1689,8 @@ public: alg.setPropertyValue("OutputWorkspace", "outWS"); TS_ASSERT_THROWS_EQUALS(alg.execute(), const std::runtime_error &e, std::string(e.what()), - "Cannot merge DetectorInfo: " - "sync scan intervals " - "overlap but not identical") + "Cannot merge ComponentInfo: " + "scan intervals overlap but not identical") } void test_merging_detector_scan_workspaces_failure_case() { @@ -1727,7 +1720,7 @@ public: "outWS")); TS_ASSERT_EQUALS(outputWS->detectorInfo().size(), 2) - TS_ASSERT_EQUALS(outputWS->detectorInfo().scanCount(0), 2) + TS_ASSERT_EQUALS(outputWS->detectorInfo().scanCount(), 2) TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 4) // Check bins are set correctly diff --git a/Framework/Algorithms/test/NormaliseToMonitorTest.h b/Framework/Algorithms/test/NormaliseToMonitorTest.h index b19b0956c14aaa2b8dd6dc0410c6261f37618506..e937733ae95532df121be24305f22b5046d1b579 100644 --- a/Framework/Algorithms/test/NormaliseToMonitorTest.h +++ b/Framework/Algorithms/test/NormaliseToMonitorTest.h @@ -15,7 +15,6 @@ #include "MantidAlgorithms/NormaliseToMonitor.h" #include "MantidDataObjects/WorkspaceCreation.h" #include "MantidGeometry/Instrument.h" -#include "MantidGeometry/Instrument/DetectorInfo.h" #include "MantidHistogramData/BinEdges.h" #include "MantidHistogramData/Counts.h" #include "MantidHistogramData/LinearGenerator.h" @@ -506,40 +505,6 @@ public: } } - void test_with_async_scan_workspace_throws() { - const size_t N_DET = 10; - const size_t N_BINS = 5; - - // Set up 2 workspaces to be merged - auto ws1 = WorkspaceCreationHelper::create2DWorkspaceWithFullInstrument( - N_DET, N_BINS, true); - auto ws2 = WorkspaceCreationHelper::create2DWorkspaceWithFullInstrument( - N_DET, N_BINS, true); - auto &detInfo1 = ws1->mutableDetectorInfo(); - auto &detInfo2 = ws2->mutableDetectorInfo(); - for (size_t i = 0; i < N_DET; ++i) { - detInfo1.setScanInterval(i, {10, 20}); - detInfo2.setScanInterval(i, {20, 30}); - } - // Merge - auto merged = WorkspaceFactory::Instance().create(ws1, 2 * N_DET); - auto &detInfo = merged->mutableDetectorInfo(); - detInfo.merge(detInfo2); - merged->setIndexInfo(Indexing::IndexInfo(merged->getNumberHistograms())); - - NormaliseToMonitor alg; - alg.setChild(true); - alg.setRethrows(true); - TS_ASSERT_THROWS_NOTHING(alg.initialize()) - TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspace", merged)) - TS_ASSERT_THROWS_NOTHING(alg.setProperty("OutputWorkspace", "outputWS")) - TS_ASSERT_THROWS_NOTHING(alg.setProperty("MonitorID", "9")) - TS_ASSERT_THROWS_EQUALS( - alg.execute(), std::runtime_error & e, std::string(e.what()), - "More then one spectrum corresponds to the requested monitor ID. This " - "is unexpected in a non-scanning workspace.") - } - void test_with_single_count_point_data_workspace() { const size_t N_DET = 10; const size_t N_BINS = 1; diff --git a/Framework/Algorithms/test/ReflectometryBeamStatisticsTest.h b/Framework/Algorithms/test/ReflectometryBeamStatisticsTest.h new file mode 100644 index 0000000000000000000000000000000000000000..2e8cc1c03211b1fabc218bba857910955cc94bff --- /dev/null +++ b/Framework/Algorithms/test/ReflectometryBeamStatisticsTest.h @@ -0,0 +1,366 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_ALGORITHMS_REFLECTOMETRYBEAMSTATISTICSTEST_H_ +#define MANTID_ALGORITHMS_REFLECTOMETRYBEAMSTATISTICSTEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "MantidAlgorithms/ReflectometryBeamStatistics.h" + +#include "MantidAPI/AlgorithmManager.h" +#include "MantidAPI/MatrixWorkspace.h" +#include "MantidAPI/SpectrumInfo.h" +#include "MantidGeometry/Crystal/AngleUnits.h" +#include "MantidTestHelpers/WorkspaceCreationHelper.h" + +#include <boost/math/special_functions/pow.hpp> + +using namespace Mantid; + +namespace { +constexpr double DET_DIST{4.}; +constexpr double DET_RESOLUTION{0.002}; +constexpr double L1{8.}; +constexpr int N_DET{64}; +constexpr int BEAM_CENTRE{N_DET / 2}; +constexpr int FGD_FIRST{BEAM_CENTRE - 2}; +constexpr int FGD_LAST{BEAM_CENTRE + 2}; +constexpr double PIXEL_SIZE{0.0015}; +// h / NeutronMass +constexpr double PLANCK_PER_KG{3.9560340102631226e-7}; +constexpr double SLIT1_DIST{1.2}; +constexpr double SLIT1_SIZE{0.03}; +constexpr double SLIT2_DIST{0.3}; +constexpr double SLIT2_SIZE{0.02}; +constexpr double INTERSLIT{SLIT1_DIST - SLIT2_DIST}; +constexpr double S2_FWHM{0.68 * SLIT1_SIZE / INTERSLIT}; +constexpr double TOF_BIN_WIDTH{70.}; // microseconds +} // namespace + +class ReflectometryBeamStatisticsTest : public CxxTest::TestSuite { +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static ReflectometryBeamStatisticsTest *createSuite() { + return new ReflectometryBeamStatisticsTest(); + } + static void destroySuite(ReflectometryBeamStatisticsTest *suite) { + delete suite; + } + + void test_Init() { + Algorithms::ReflectometryBeamStatistics alg; + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + } + + void test_LogsGetAdded() { + auto reflectedWS = makeWS(0.7); + const std::vector<int> reflectedForeground{FGD_FIRST, BEAM_CENTRE, + FGD_LAST}; + auto directWS = makeWS(0.); + const std::vector<int> directForeground{FGD_FIRST, BEAM_CENTRE, FGD_LAST}; + Algorithms::ReflectometryBeamStatistics alg; + alg.setChild(true); + alg.setRethrows(true); + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("ReflectedBeamWorkspace", reflectedWS)) + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("ReflectedForeground", reflectedForeground)) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("DirectBeamWorkspace", directWS)) + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("DirectForeground", directForeground)) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("PixelSize", PIXEL_SIZE)) + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("DetectorResolution", DET_RESOLUTION)) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("FirstSlitName", "slit1")) + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("FirstSlitSizeSampleLog", "slit1.size")) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("SecondSlitName", "slit2")) + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("SecondSlitSizeSampleLog", "slit2.size")) + TS_ASSERT_THROWS_NOTHING(alg.execute()) + TS_ASSERT(alg.isExecuted()) + const auto &reflectedRun = reflectedWS->run(); + checkReflectedStatisticsContainedInSampleLogs(reflectedRun); + const auto &directRun = directWS->run(); + checkDirectStatisticsContainedInSampleLogs(directRun); + const auto reflectedDetFwhm = det_fwhm(*reflectedWS, FGD_FIRST, FGD_LAST); + const auto directDetFwhm = det_fwhm(*directWS, FGD_FIRST, FGD_LAST); + const auto omFwhm = om_fwhm(DET_DIST, DET_DIST, SLIT1_SIZE, SLIT2_SIZE, + reflectedDetFwhm, directDetFwhm); + TS_ASSERT_EQUALS(reflectedRun.getPropertyValueAsType<double>( + "beam_stats.beam_rms_variation"), + reflectedDetFwhm) + TS_ASSERT_EQUALS(directRun.getPropertyValueAsType<double>( + "beam_stats.beam_rms_variation"), + directDetFwhm) + const auto bentSample = + omFwhm > 0. && DET_RESOLUTION / DET_DIST > S2_FWHM ? 1 : 0; + TS_ASSERT_EQUALS( + reflectedRun.getPropertyValueAsType<int>("beam_stats.bent_sample"), + bentSample) + TS_ASSERT_EQUALS(reflectedRun.getPropertyValueAsType<double>( + "beam_stats.first_slit_angular_spread"), + S2_FWHM) + const auto firstAngularSpread = da(); + TS_ASSERT_EQUALS(reflectedRun.getPropertyValueAsType<double>( + "beam_stats.incident_angular_spread"), + firstAngularSpread) + TS_ASSERT_EQUALS(reflectedRun.getPropertyValueAsType<double>( + "beam_stats.sample_waviness"), + omFwhm) + const auto secondAngularSpread = s3_fwhm(DET_DIST); + TS_ASSERT_EQUALS(reflectedRun.getPropertyValueAsType<double>( + "beam_stats.second_slit_angular_spread"), + secondAngularSpread) + } + + void test_failsGracefullyWhenSlitsNotFound() { + constexpr int firstSlit{1}; + checkWrongSlitsThrows(firstSlit); + constexpr int secondSlit{2}; + checkWrongSlitsThrows(secondSlit); + } + +private: + static void checkDirectStatisticsContainedInSampleLogs(const API::Run &run) { + TS_ASSERT(run.hasProperty("beam_stats.beam_rms_variation")) + TS_ASSERT_EQUALS(run.getProperty("beam_stats.beam_rms_variation")->units(), + "m") + TS_ASSERT(!run.hasProperty("beam_stats.bent_sample")) + TS_ASSERT(!run.hasProperty("beam_stats.first_slit_angular_spread")) + TS_ASSERT(!run.hasProperty("beam_stats.incident_angular_spread")) + TS_ASSERT(!run.hasProperty("beam_stats.sample_waviness")) + TS_ASSERT(!run.hasProperty("beam_stats.second_slit_angular_spread")) + } + static void + checkReflectedStatisticsContainedInSampleLogs(const API::Run &run) { + TS_ASSERT(run.hasProperty("beam_stats.beam_rms_variation")) + TS_ASSERT_EQUALS(run.getProperty("beam_stats.beam_rms_variation")->units(), + "m") + TS_ASSERT(run.hasProperty("beam_stats.bent_sample")) + TS_ASSERT_EQUALS(run.getProperty("beam_stats.bent_sample")->units(), "") + TS_ASSERT(run.hasProperty("beam_stats.first_slit_angular_spread")) + TS_ASSERT_EQUALS( + run.getProperty("beam_stats.first_slit_angular_spread")->units(), + "radians") + TS_ASSERT(run.hasProperty("beam_stats.incident_angular_spread")) + TS_ASSERT_EQUALS( + run.getProperty("beam_stats.incident_angular_spread")->units(), + "radians") + TS_ASSERT(run.hasProperty("beam_stats.sample_waviness")) + TS_ASSERT_EQUALS(run.getProperty("beam_stats.sample_waviness")->units(), + "radians") + TS_ASSERT(run.hasProperty("beam_stats.second_slit_angular_spread")) + TS_ASSERT_EQUALS( + run.getProperty("beam_stats.second_slit_angular_spread")->units(), + "radians") + } + + void checkWrongSlitsThrows(const int slit) { + const std::string slit1 = slit == 1 ? "non-existent" : "slit1"; + const std::string slit2 = slit == 2 ? "non-existent" : "slit2"; + auto reflectedWS = makeWS(0.7); + const std::vector<int> reflectedForeground{FGD_FIRST, BEAM_CENTRE, + FGD_LAST}; + auto directWS = makeWS(0.); + const std::vector<int> directForeground{FGD_FIRST, BEAM_CENTRE, FGD_LAST}; + Algorithms::ReflectometryBeamStatistics alg; + alg.setChild(true); + alg.setRethrows(true); + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("ReflectedBeamWorkspace", reflectedWS)) + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("ReflectedForeground", reflectedForeground)) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("DirectBeamWorkspace", directWS)) + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("DirectForeground", directForeground)) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("PixelSize", PIXEL_SIZE)) + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("DetectorResolution", DET_RESOLUTION)) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("FirstSlitName", slit1)) + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("FirstSlitSizeSampleLog", "slit1.size")) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("SecondSlitName", slit2)) + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("SecondSlitSizeSampleLog", "slit2.size")) + TS_ASSERT_THROWS_EQUALS(alg.execute(), std::runtime_error & e, e.what(), + std::string("Some invalid Properties found")) + TS_ASSERT(!alg.isExecuted()) + } + + static API::MatrixWorkspace_sptr makeWS(const double braggAngle) { + using namespace WorkspaceCreationHelper; + using Geometry::deg2rad; + constexpr double startX{1000.}; + const Kernel::V3D sourcePos{0., 0., -L1}; + const Kernel::V3D &monitorPos = sourcePos; + const Kernel::V3D samplePos{0., 0., 0.}; + const auto detZ = DET_DIST * std::cos(2 * braggAngle * deg2rad); + const auto detY = DET_DIST * std::sin(2 * braggAngle * deg2rad); + const Kernel::V3D detectorPos{0., detY, detZ}; + const Kernel::V3D slit1Pos{0., 0., -SLIT1_DIST}; + const Kernel::V3D slit2Pos{0., 0., -SLIT2_DIST}; + constexpr int nBins{100}; + auto ws = create2DWorkspaceWithReflectometryInstrumentMultiDetector( + startX, PIXEL_SIZE, slit1Pos, slit2Pos, SLIT1_SIZE, SLIT2_SIZE, + sourcePos, monitorPos, samplePos, detectorPos, N_DET, nBins, + TOF_BIN_WIDTH); + // Add slit sizes to sample logs, too. + auto &run = ws->mutableRun(); + constexpr bool overwrite{true}; + const std::string meters{"m"}; + run.addProperty("slit1.size", SLIT1_SIZE, meters, overwrite); + run.addProperty("slit2.size", SLIT2_SIZE, meters, overwrite); + // Build a step-like peak in the middle of the detector. + auto zeros = Kernel::make_cow<HistogramData::HistogramY>(nBins, 0.); + auto zeroErrors = Kernel::make_cow<HistogramData::HistogramE>(nBins, 0.); + auto peak = Kernel::make_cow<HistogramData::HistogramY>(nBins, 10.); + auto peakErrors = + Kernel::make_cow<HistogramData::HistogramE>(nBins, std::sqrt(10.)); + for (size_t i = 0; i < N_DET; ++i) { + if (i >= FGD_FIRST && i <= FGD_LAST) { + ws->setSharedY(i, peak); + ws->setSharedE(i, peakErrors); + } else { + ws->setSharedY(i, zeros); + ws->setSharedE(i, zeroErrors); + } + } + auto convertUnits = + API::AlgorithmManager::Instance().createUnmanaged("ConvertUnits"); + convertUnits->initialize(); + convertUnits->setChild(true); + convertUnits->setRethrows(true); + convertUnits->setProperty("InputWorkspace", ws); + convertUnits->setPropertyValue("OutputWorkspace", "_unused_for_child"); + convertUnits->setProperty("Target", "Wavelength"); + convertUnits->setProperty("EMode", "Elastic"); + convertUnits->execute(); + API::MatrixWorkspace_sptr outWS = + convertUnits->getProperty("OutputWorkspace"); + return outWS; + } + + static double det_fwhm(const API::MatrixWorkspace &ws, const size_t fgd_first, + const size_t fgd_last) { + // This function comes from COSMOS. + using namespace boost::math; + std::vector<double> angd; + const auto &spectrumInfo = ws.spectrumInfo(); + for (size_t i = fgd_first; i <= fgd_last; ++i) { + if (spectrumInfo.isMonitor(i)) { + continue; + } + const auto &ys = ws.y(i); + const auto sum = std::accumulate(ys.cbegin(), ys.cend(), 0.0); + angd.emplace_back(sum); + } + const auto temp = [&angd]() { + double sum{0.0}; + for (size_t i = 0; i < angd.size(); ++i) { + sum += static_cast<double>(i) * angd[i]; + } + return sum; + }(); + const auto total_angd = std::accumulate(angd.cbegin(), angd.cend(), 0.0); + const auto pref = temp / total_angd + static_cast<double>(fgd_first); + const auto angd_cen = pref - static_cast<double>(fgd_first); + const auto tt = [&angd, &angd_cen]() { + double sum{0.0}; + for (size_t i = 0; i < angd.size(); ++i) { + sum += angd[i] * pow<2>(angd_cen - static_cast<double>(i)); + } + return sum; + }(); + return 2. * std::sqrt(2. * std::log(2.)) * PIXEL_SIZE * + std::sqrt(tt / total_angd); + } + + static double s3_fwhm(const double l2) { + // This function comes from COSMOS. + return 0.68 * SLIT2_SIZE / (SLIT2_DIST + l2); + } + + static double err_ray(const double l2, const double angle_bragg, + const std::string &sumType, const bool polarized, + const double om_fwhm) { + // This function comes from COSMOS. + using namespace boost::math; + double err_ray1; + if (sumType == "SumInQ") { + if (om_fwhm > 0) { + if (S2_FWHM >= 2 * om_fwhm) { + err_ray1 = std::sqrt(pow<2>(DET_RESOLUTION / l2) + + pow<2>(s3_fwhm(l2)) + pow<2>(om_fwhm)) / + angle_bragg; + } else { + err_ray1 = std::sqrt(pow<2>(DET_RESOLUTION / (2. * l2)) + + pow<2>(s3_fwhm(l2)) + pow<2>(S2_FWHM)) / + angle_bragg; + } + } else { + if (S2_FWHM > DET_RESOLUTION / l2) { + err_ray1 = + std::sqrt(pow<2>(DET_RESOLUTION / l2) + pow<2>(s3_fwhm(l2))) / + angle_bragg; + } else { + err_ray1 = std::sqrt(pow<2>(da()) + pow<2>(DET_RESOLUTION / l2)) / + angle_bragg; + } + } + } else { + if (polarized) { + err_ray1 = std::sqrt(pow<2>(da())) / angle_bragg; + } else { + err_ray1 = std::sqrt(pow<2>(da()) + pow<2>(om_fwhm)) / angle_bragg; + } + } + const auto err_ray_temp = + 0.68 * + std::sqrt((pow<2>(PIXEL_SIZE) + pow<2>(SLIT2_SIZE)) / pow<2>(l2)) / + angle_bragg; + return std::min(err_ray1, err_ray_temp); + } + + static double da() { + // This function comes from COSMOS. + using namespace boost::math; + return 0.68 * std::sqrt((pow<2>(SLIT1_SIZE) + pow<2>(SLIT2_SIZE)) / + pow<2>(INTERSLIT)); + } + + static double om_fwhm(const double l2, const double dirl2, + const double dirs2w, const double dirs3w, + const double det_fwhm, const double detdb_fwhm) { + // This function comes from COSMOS. + using namespace boost::math; + const double sdr = SLIT2_DIST + l2; + const double ratio = SLIT2_SIZE / SLIT1_SIZE; + const double vs = sdr + (ratio * INTERSLIT) / (1 + ratio); + const double da_det = std::sqrt(pow<2>(da() * vs) + pow<2>(DET_RESOLUTION)); + double om_fwhm{0.}; + if ((std::abs(SLIT1_SIZE - dirs2w) >= 0.00004 || + std::abs(SLIT2_SIZE - dirs3w) >= 0.00004) && + (det_fwhm - da_det >= 0.) && + (std::sqrt(pow<2>(det_fwhm) - pow<2>(da_det)) >= PIXEL_SIZE)) { + om_fwhm = 0.5 * std::sqrt(pow<2>(det_fwhm) - pow<2>(da_det)) / dirl2; + } else if ((pow<2>(det_fwhm) - pow<2>(detdb_fwhm) >= 0.) && + (std::sqrt(pow<2>(det_fwhm) - pow<2>(detdb_fwhm)) >= + PIXEL_SIZE)) { + om_fwhm = 0.5 * std::sqrt(pow<2>(det_fwhm) - pow<2>(detdb_fwhm)) / dirl2; + } + return om_fwhm; + } +}; + +#endif /* MANTID_ALGORITHMS_REFLECTOMETRYBEAMSTATISTICSTEST_H_ */ diff --git a/Framework/Algorithms/test/ReflectometryMomentumTransferTest.h b/Framework/Algorithms/test/ReflectometryMomentumTransferTest.h index 2d8ef86e333dc984ad65f3cc4ccf26393ccbfd40..ee76c707209cc17366ef3a6e8b56e29de9598b45 100644 --- a/Framework/Algorithms/test/ReflectometryMomentumTransferTest.h +++ b/Framework/Algorithms/test/ReflectometryMomentumTransferTest.h @@ -15,6 +15,7 @@ #include "MantidAPI/Axis.h" #include "MantidAPI/FrameworkManager.h" #include "MantidAPI/SpectrumInfo.h" +#include "MantidGeometry/Crystal/AngleUnits.h" #include "MantidKernel/Unit.h" #include "MantidTestHelpers/WorkspaceCreationHelper.h" @@ -42,6 +43,16 @@ constexpr double TOF_BIN_WIDTH{70.}; // microseconds class ReflectometryMomentumTransferTest : public CxxTest::TestSuite { public: + struct LogValues { + const double om_fwhm; + const double s2_fwhm; + const double s3_fwhm; + const double da; + LogValues(const double om_fwhm_, const double s2_fwhm_, + const double s3_fwhm_, const double da_) + : om_fwhm(om_fwhm_), s2_fwhm(s2_fwhm_), s3_fwhm(s3_fwhm_), da(da_) {} + }; + // This pair of boilerplate methods prevent the suite being created statically // This means the constructor isn't called when running other tests static ReflectometryMomentumTransferTest *createSuite() { @@ -51,6 +62,34 @@ public: delete suite; } + template <typename LogValues> + static API::MatrixWorkspace_sptr make_ws(const double braggAngle, + const int nBins, + const LogValues &logValues) { + using namespace WorkspaceCreationHelper; + constexpr double startX{1000.}; + const Kernel::V3D sourcePos{0., 0., -L1}; + const Kernel::V3D &monitorPos = sourcePos; + const Kernel::V3D samplePos{ + 0., + 0., + 0., + }; + const auto detZ = DET_DIST * std::cos(2 * braggAngle); + const auto detY = DET_DIST * std::sin(2 * braggAngle); + const Kernel::V3D detectorPos{0., detY, detZ}; + const Kernel::V3D slit1Pos{0., 0., -SLIT1_DIST}; + const Kernel::V3D slit2Pos{0., 0., -SLIT2_DIST}; + auto ws = create2DWorkspaceWithReflectometryInstrument( + startX, slit1Pos, slit2Pos, SLIT1_SIZE, SLIT2_SIZE, sourcePos, + monitorPos, samplePos, detectorPos, nBins, TOF_BIN_WIDTH); + ws = extractNonMonitorSpectrum(ws); + addSlitSampleLogs(*ws); + addBeamStatisticsSampleLogs(*ws, logValues); + ws = convertToWavelength(ws); + return ws; + } + ReflectometryMomentumTransferTest() { API::FrameworkManager::Instance(); } void test_Init() { @@ -60,11 +99,14 @@ public: TS_ASSERT(alg.isInitialized()) } - void test_XYEFromInputUnchangedAndMonitorDXSetToZero() { - auto inputWS = make_ws(0.5 / 180. * M_PI); - API::MatrixWorkspace_sptr directWS = inputWS->clone(); - auto alg = make_alg(inputWS, directWS, "SumInLambda", false); - TS_ASSERT_THROWS_NOTHING(alg->execute();) + void test_XYEFromInputUnchanged() { + using Geometry::deg2rad; + const LogValues logValues(0.1, 0.1, 0.1, 0.1); + constexpr int nBins{10}; + auto inputWS = make_ws(0.5 * deg2rad, nBins, logValues); + const std::vector<int> foreground(2, 0); + auto alg = make_alg(inputWS, "SumInLambda", foreground); + TS_ASSERT_THROWS_NOTHING(alg->execute()) TS_ASSERT(alg->isExecuted()) API::MatrixWorkspace_sptr outputWS = alg->getProperty("OutputWorkspace"); @@ -73,66 +115,95 @@ public: TS_ASSERT_EQUALS(axis->unit()->unitID(), "MomentumTransfer") TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), inputWS->getNumberHistograms()) - for (size_t i = 0; i < outputWS->getNumberHistograms(); ++i) { - const auto &inXs = inputWS->x(i); - const auto &outXs = outputWS->x(i); - TS_ASSERT_EQUALS(outXs.size(), inXs.size()) - TS_ASSERT(outputWS->hasDx(i)) - if (i == 1) { - // Monitor should have Dx = 0 - TS_ASSERT(outputWS->spectrumInfo().isMonitor(i)) - const auto &outDx = outputWS->dx(i); - for (size_t j = 0; j < outDx.size(); ++j) { - TS_ASSERT_EQUALS(outDx[j], 0.) - } - } - const auto &inYs = inputWS->y(i); - const auto &outYs = outputWS->y(i); - TS_ASSERT_EQUALS(outYs.rawData(), inYs.rawData()) - const auto &inEs = inputWS->e(i); - const auto &outEs = outputWS->e(i); - TS_ASSERT_EQUALS(outEs.rawData(), inEs.rawData()) - } + const auto &inXs = inputWS->x(0); + const auto &outXs = outputWS->x(0); + TS_ASSERT_EQUALS(outXs.size(), inXs.size()) + TS_ASSERT(outputWS->hasDx(0)) + const auto &inYs = inputWS->y(0); + const auto &outYs = outputWS->y(0); + TS_ASSERT_EQUALS(outYs.rawData(), inYs.rawData()) + const auto &inEs = inputWS->e(0); + const auto &outEs = outputWS->e(0); + TS_ASSERT_EQUALS(outEs.rawData(), inEs.rawData()) } - void test_nonpolarizedSumInLambdaResultsAreValid() { - const bool polarized(false); - const std::string sumType{"SumInLambda"}; - sameReflectedAndDirectSlitSizes(polarized, sumType); + void test_sumInQResultsAreValid() { + using Geometry::deg2rad; + const std::string sumType{"SumInQ"}; + constexpr double s3_fwhm{0.1}; + constexpr double da{0.1}; + constexpr double angleBragg{1.5 * deg2rad}; + const std::vector<int> foreground(2, 0); + sameReflectedAndDirectSlitSizes(sumType, angleBragg, foreground, + sumInQBeamDivergenceDominated(s3_fwhm, da)); + sameReflectedAndDirectSlitSizes( + sumType, angleBragg, foreground, + sumInQBentSampleDominateSmallSecondSlitAngularSpread(s3_fwhm, da)); + sameReflectedAndDirectSlitSizes( + sumType, angleBragg, foreground, + sumInQBentSampleDominatedLargeSecondSlitAngularSpread(s3_fwhm, da)); + sameReflectedAndDirectSlitSizes( + sumType, angleBragg, foreground, + sumInQDetectorResolutionDominated(s3_fwhm, da)); } - void test_polarizedSumInLambdaResultsAreValid() { - const bool polarized(true); + void test_sumInLambdaAngularResolutionDominatesResultsAreValid() { + using Geometry::deg2rad; const std::string sumType{"SumInLambda"}; - sameReflectedAndDirectSlitSizes(polarized, sumType); + constexpr double om_fwhm{0.001}; + constexpr double s2_fwhm{0.1}; + constexpr double s3_fwhm{0.1}; + constexpr double da{0.001}; + constexpr double angleBragg{1.23 * deg2rad}; + const LogValues angularResolutionDominatedLogValues(om_fwhm, s2_fwhm, + s3_fwhm, da); + std::vector<int> foreground(2); + foreground.front() = 0; + foreground.back() = 40; + TS_ASSERT( + err_ray_SumInLambda(angleBragg, angularResolutionDominatedLogValues) < + err_ray_temp(foreground, DET_DIST, angleBragg)) + sameReflectedAndDirectSlitSizes(sumType, angleBragg, foreground, + angularResolutionDominatedLogValues); } - void test_nonpolarizedSumInQResultsAreValid() { - const bool polarized(false); - const std::string sumType{"SumInQ"}; - sameReflectedAndDirectSlitSizes(polarized, sumType); + void test_sumInLambdaForegroundWidthDominatesResultsAreValid() { + using Geometry::deg2rad; + const std::string sumType{"SumInLambda"}; + constexpr double om_fwhm{0.1}; + constexpr double s2_fwhm{0.1}; + constexpr double s3_fwhm{0.1}; + constexpr double da{0.1}; + constexpr double angleBragg{1.23 * deg2rad}; + const LogValues foregroundWidthDominatedLogValues(om_fwhm, s2_fwhm, s3_fwhm, + da); + std::vector<int> foreground(2); + foreground.front() = 0; + foreground.back() = 10; + TS_ASSERT( + err_ray_SumInLambda(angleBragg, foregroundWidthDominatedLogValues) > + err_ray_temp(foreground, DET_DIST, angleBragg)) + sameReflectedAndDirectSlitSizes(sumType, angleBragg, foreground, + foregroundWidthDominatedLogValues); } - void test_polarizedSumInQResultsAreValid() { - const bool polarized(true); - const std::string sumType{"SumInQ"}; - sameReflectedAndDirectSlitSizes(polarized, sumType); + void test_failsGracefullyWhenSlitsNotFound() { + constexpr int firstSlit{1}; + wrongSlitNames(firstSlit); + constexpr int secondSlit{2}; + wrongSlitNames(secondSlit); } - void test_differentReflectedAndDirectSlitSizes() { +private: + static void sameReflectedAndDirectSlitSizes( + const std::string &sumType, const double angleBragg, + const std::vector<int> &foreground, const LogValues &logValues) { using namespace boost::math; - const bool polarized{false}; - const std::string sumType{"SumInLambda"}; - auto inputWS = make_ws(0.5 / 180. * M_PI); + constexpr int nBins{10}; + auto inputWS = make_ws(angleBragg, nBins, logValues); inputWS->mutableY(0) = 1. / static_cast<double>(inputWS->y(0).size()); - API::MatrixWorkspace_sptr directWS = inputWS->clone(); - auto &run = directWS->mutableRun(); - constexpr bool overwrite{true}; - const std::string meters{"m"}; - run.addProperty("slit1.size", 1.5 * SLIT1_SIZE, meters, overwrite); - run.addProperty("slit2.size", 1.5 * SLIT2_SIZE, meters, overwrite); - auto alg = make_alg(inputWS, directWS, sumType, polarized); - TS_ASSERT_THROWS_NOTHING(alg->execute();) + auto alg = make_alg(inputWS, sumType, foreground); + TS_ASSERT_THROWS_NOTHING(alg->execute()) TS_ASSERT(alg->isExecuted()) API::MatrixWorkspace_sptr outputWS = alg->getProperty("OutputWorkspace"); TS_ASSERT(outputWS); @@ -150,128 +221,72 @@ public: TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), inputWS->getNumberHistograms()) const auto &spectrumInfo = outputWS->spectrumInfo(); - const auto &dirSpectrumInfo = directWS->spectrumInfo(); - for (size_t i = 0; i < outputWS->getNumberHistograms(); ++i) { - const auto &inQs = qWS->points(i); - const auto &outPoints = outputWS->points(i); - TS_ASSERT_EQUALS(outPoints.size(), inQs.size()) - TS_ASSERT(outputWS->hasDx(i)) - if (i != 1) { - TS_ASSERT(!outputWS->spectrumInfo().isMonitor(i)) - const auto &outDx = outputWS->dx(i); - TS_ASSERT_EQUALS(outDx.size(), inQs.size()) - const auto &lambdas = inputWS->points(i); - const auto l2 = spectrumInfo.l2(i); - const auto dirL2 = dirSpectrumInfo.l2(i); - const auto angle_bragg = spectrumInfo.twoTheta(i) / 2.; - for (size_t j = 0; j < lambdas.size(); ++j) { - const auto lambda = lambdas[j] * 1e-10; - const size_t qIndex = inQs.size() - j - 1; - const auto q = inQs[qIndex]; - const auto resE = std::sqrt(pow<2>(err_res(lambda, l2)) + - pow<2>(width_res(lambda, l2))); - const auto detFwhm = det_fwhm(*inputWS, 0, 0); - const auto dirDetFwhm = det_fwhm(*directWS, 0, 0); - const auto omFwhm = - om_fwhm(l2, dirL2, SLIT1_SIZE, SLIT2_SIZE, detFwhm, dirDetFwhm); - const auto rayE = - err_ray(l2, angle_bragg, sumType, polarized, omFwhm); - const auto fractionalResolution = - std::sqrt(pow<2>(resE) + pow<2>(rayE)); - TS_ASSERT_EQUALS(outPoints[qIndex], q) - TS_ASSERT_DELTA(outDx[qIndex], q * fractionalResolution, 1e-7) - } - } else { - // Monitor should have Dx = 0 - TS_ASSERT(outputWS->spectrumInfo().isMonitor(i)) - const auto &outDx = outputWS->dx(i); - for (size_t j = 0; j < outDx.size(); ++j) { - TS_ASSERT_EQUALS(outDx[j], 0.) - } - } + const auto &inQs = qWS->points(0); + const auto &outPoints = outputWS->points(0); + TS_ASSERT_EQUALS(outPoints.size(), inQs.size()) + TS_ASSERT(outputWS->hasDx(0)) + TS_ASSERT(!outputWS->spectrumInfo().isMonitor(0)) + const auto &outDx = outputWS->dx(0); + TS_ASSERT_EQUALS(outDx.size(), inQs.size()) + const auto &lambdas = inputWS->points(0); + const auto l2 = spectrumInfo.l2(0); + const auto angle_bragg = spectrumInfo.twoTheta(0) / 2.; + const auto rayE = err_ray(foreground, l2, angle_bragg, sumType, logValues); + for (size_t j = 0; j < lambdas.size(); ++j) { + const auto lambda = lambdas[j] * 1e-10; + const size_t qIndex = inQs.size() - j - 1; + const auto q = inQs[qIndex]; + const auto resE = std::sqrt(pow<2>(err_res(lambda, l2)) + + pow<2>(width_res(lambda, l2))); + const auto fractionalResolution = std::sqrt(pow<2>(resE) + pow<2>(rayE)); + TS_ASSERT_EQUALS(outPoints[qIndex], q) + TS_ASSERT_DELTA(outDx[qIndex], q * fractionalResolution, 1e-7) } } -private: - void sameReflectedAndDirectSlitSizes(const bool polarized, - const std::string &sumType) { - using namespace boost::math; - auto inputWS = make_ws(0.5 / 180. * M_PI); - inputWS->mutableY(0) = 1. / static_cast<double>(inputWS->y(0).size()); - API::MatrixWorkspace_sptr directWS = inputWS->clone(); - auto &run = directWS->mutableRun(); - constexpr bool overwrite{true}; - const std::string meters{"m"}; - run.addProperty("slit1.size", SLIT1_SIZE, meters, overwrite); - run.addProperty("slit2.size", SLIT2_SIZE, meters, overwrite); - auto alg = make_alg(inputWS, directWS, sumType, polarized); - TS_ASSERT_THROWS_NOTHING(alg->execute();) - TS_ASSERT(alg->isExecuted()) - API::MatrixWorkspace_sptr outputWS = alg->getProperty("OutputWorkspace"); - TS_ASSERT(outputWS); - alg = API::AlgorithmManager::Instance().createUnmanaged("ConvertUnits"); - alg->initialize(); + void wrongSlitNames(const int nonexistentSlit) { + using Geometry::deg2rad; + const std::string slit1 = nonexistentSlit == 1 ? "non-existent" : "slit1"; + const std::string slit2 = nonexistentSlit == 2 ? "non-existent" : "slit2"; + const LogValues logValues(0.1, 0.1, 0.1, 0.1); + constexpr int nBins{10}; + auto inputWS = make_ws(0.5 * deg2rad, nBins, logValues); + const std::vector<int> foreground(2, 0); + auto alg = boost::make_shared<Algorithms::ReflectometryMomentumTransfer>(); alg->setChild(true); alg->setRethrows(true); - alg->setProperty("InputWorkspace", inputWS); - alg->setProperty("OutputWorkspace", "unused_for_child"); - alg->setProperty("Target", "MomentumTransfer"); - alg->execute(); - API::MatrixWorkspace_sptr qWS = alg->getProperty("OutputWorkspace"); - const auto axis = outputWS->getAxis(0); - TS_ASSERT_EQUALS(axis->unit()->unitID(), "MomentumTransfer") - TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), - inputWS->getNumberHistograms()) - const auto &spectrumInfo = outputWS->spectrumInfo(); - const auto &dirSpectrumInfo = directWS->spectrumInfo(); - for (size_t i = 0; i < outputWS->getNumberHistograms(); ++i) { - const auto &inQs = qWS->points(i); - const auto &outPoints = outputWS->points(i); - TS_ASSERT_EQUALS(outPoints.size(), inQs.size()) - TS_ASSERT(outputWS->hasDx(i)) - if (i != 1) { - TS_ASSERT(!outputWS->spectrumInfo().isMonitor(i)) - const auto &outDx = outputWS->dx(i); - TS_ASSERT_EQUALS(outDx.size(), inQs.size()) - const auto &lambdas = inputWS->points(i); - const auto l2 = spectrumInfo.l2(i); - const auto dirL2 = dirSpectrumInfo.l2(i); - const auto angle_bragg = spectrumInfo.twoTheta(i) / 2.; - for (size_t j = 0; j < lambdas.size(); ++j) { - const auto lambda = lambdas[j] * 1e-10; - const size_t qIndex = inQs.size() - j - 1; - const auto q = inQs[qIndex]; - const auto resE = std::sqrt(pow<2>(err_res(lambda, l2)) + - pow<2>(width_res(lambda, l2))); - const auto detFwhm = det_fwhm(*inputWS, 0, 0); - const auto dirDetFwhm = det_fwhm(*directWS, 0, 0); - const auto omFwhm = - om_fwhm(l2, dirL2, SLIT1_SIZE, SLIT2_SIZE, detFwhm, dirDetFwhm); - const auto rayE = - err_ray(l2, angle_bragg, sumType, polarized, omFwhm); - const auto fractionalResolution = - std::sqrt(pow<2>(resE) + pow<2>(rayE)); - TS_ASSERT_EQUALS(outPoints[qIndex], q) - TS_ASSERT_DELTA(outDx[qIndex], q * fractionalResolution, 1e-7) - } - } else { - // Monitor should have Dx = 0 - TS_ASSERT(outputWS->spectrumInfo().isMonitor(i)) - const auto &outDx = outputWS->dx(i); - for (size_t j = 0; j < outDx.size(); ++j) { - TS_ASSERT_EQUALS(outDx[j], 0.) - } - } - } + TS_ASSERT_THROWS_NOTHING(alg->initialize()) + TS_ASSERT(alg->isInitialized()) + TS_ASSERT_THROWS_NOTHING(alg->setProperty("InputWorkspace", inputWS)) + TS_ASSERT_THROWS_NOTHING( + alg->setPropertyValue("OutputWorkspace", "_unused_for_child")) + TS_ASSERT_THROWS_NOTHING( + alg->setProperty("ReflectedForeground", foreground)) + TS_ASSERT_THROWS_NOTHING(alg->setProperty("SummationType", "SumInLambda")) + TS_ASSERT_THROWS_NOTHING(alg->setProperty("PixelSize", PIXEL_SIZE)) + TS_ASSERT_THROWS_NOTHING( + alg->setProperty("DetectorResolution", DET_RESOLUTION)) + TS_ASSERT_THROWS_NOTHING(alg->setProperty("ChopperSpeed", CHOPPER_SPEED)) + TS_ASSERT_THROWS_NOTHING( + alg->setProperty("ChopperOpening", CHOPPER_OPENING_ANGLE)) + TS_ASSERT_THROWS_NOTHING(alg->setProperty("ChopperRadius", CHOPPER_RADIUS)) + TS_ASSERT_THROWS_NOTHING( + alg->setProperty("ChopperpairDistance", CHOPPER_GAP)) + TS_ASSERT_THROWS_NOTHING(alg->setProperty("FirstSlitName", "non-existent")) + TS_ASSERT_THROWS_NOTHING( + alg->setProperty("FirstSlitSizeSampleLog", "slit1.size")) + TS_ASSERT_THROWS_NOTHING(alg->setProperty("SecondSlitName", slit1)) + TS_ASSERT_THROWS_NOTHING(alg->setProperty("SecondSlitSizeSampleLog", slit2)) + TS_ASSERT_THROWS_NOTHING(alg->setProperty("TOFChannelWidth", TOF_BIN_WIDTH)) + TS_ASSERT_THROWS_EQUALS(alg->execute(), const std::runtime_error &e, + e.what(), + std::string("Some invalid Properties found")) + TS_ASSERT(!alg->isExecuted()) } - API::Algorithm_sptr make_alg(API::MatrixWorkspace_sptr inputWS, - API::MatrixWorkspace_sptr directWS, - const std::string &sumType, - const bool polarized) { - std::vector<int> foreground(2); - foreground.front() = 0; - foreground.back() = 0; + static API::Algorithm_sptr make_alg(API::MatrixWorkspace_sptr inputWS, + const std::string &sumType, + const std::vector<int> &foreground) { auto alg = boost::make_shared<Algorithms::ReflectometryMomentumTransfer>(); alg->setChild(true); alg->setRethrows(true); @@ -280,14 +295,9 @@ private: TS_ASSERT_THROWS_NOTHING(alg->setProperty("InputWorkspace", inputWS)) TS_ASSERT_THROWS_NOTHING( alg->setPropertyValue("OutputWorkspace", "_unused_for_child")) - TS_ASSERT_THROWS_NOTHING( - alg->setProperty("ReflectedBeamWorkspace", inputWS)) TS_ASSERT_THROWS_NOTHING( alg->setProperty("ReflectedForeground", foreground)) - TS_ASSERT_THROWS_NOTHING(alg->setProperty("DirectBeamWorkspace", directWS)) - TS_ASSERT_THROWS_NOTHING(alg->setProperty("DirectForeground", foreground)) TS_ASSERT_THROWS_NOTHING(alg->setProperty("SummationType", sumType)) - TS_ASSERT_THROWS_NOTHING(alg->setProperty("Polarized", polarized)) TS_ASSERT_THROWS_NOTHING(alg->setProperty("PixelSize", PIXEL_SIZE)) TS_ASSERT_THROWS_NOTHING( alg->setProperty("DetectorResolution", DET_RESOLUTION)) @@ -307,36 +317,32 @@ private: return alg; } - API::MatrixWorkspace_sptr make_ws(const double braggAngle) { - using namespace WorkspaceCreationHelper; - constexpr double startX{1000.}; - const Kernel::V3D sourcePos{0., 0., -L1}; - const Kernel::V3D &monitorPos = sourcePos; - const Kernel::V3D samplePos{ - 0., - 0., - 0., - }; - const auto detZ = DET_DIST * std::cos(2 * braggAngle); - const auto detY = DET_DIST * std::sin(2 * braggAngle); - const Kernel::V3D detectorPos{0., detY, detZ}; - const Kernel::V3D slit1Pos{0., 0., -SLIT1_DIST}; - const Kernel::V3D slit2Pos{0., 0., -SLIT2_DIST}; - constexpr int nBins{100}; - auto ws = create2DWorkspaceWithReflectometryInstrument( - startX, slit1Pos, slit2Pos, SLIT1_SIZE, SLIT2_SIZE, sourcePos, - monitorPos, samplePos, detectorPos, nBins, TOF_BIN_WIDTH); - // Add slit sizes to sample logs, too. - auto &run = ws->mutableRun(); + static void addBeamStatisticsSampleLogs(API::MatrixWorkspace &ws, + const LogValues &values) { + auto &run = ws.mutableRun(); + constexpr bool overwrite{true}; + run.addProperty("beam_stats.incident_angular_spread", values.da, overwrite); + run.addProperty("beam_stats.first_slit_angular_spread", values.s2_fwhm, + overwrite); + run.addProperty("beam_stats.second_slit_angular_spread", values.s3_fwhm, + overwrite); + run.addProperty("beam_stats.sample_waviness", values.om_fwhm, overwrite); + } + + static void addSlitSampleLogs(API::MatrixWorkspace &ws) { + auto &run = ws.mutableRun(); constexpr bool overwrite{true}; const std::string meters{"m"}; run.addProperty("slit1.size", SLIT1_SIZE, meters, overwrite); run.addProperty("slit2.size", SLIT2_SIZE, meters, overwrite); + } + + static API::MatrixWorkspace_sptr + convertToWavelength(API::MatrixWorkspace_sptr &ws) { auto alg = API::AlgorithmManager::Instance().createUnmanaged("ConvertUnits"); alg->initialize(); alg->setChild(true); - alg->setRethrows(true); alg->setProperty("InputWorkspace", ws); alg->setPropertyValue("OutputWorkspace", "_unused_for_child"); alg->setProperty("Target", "Wavelength"); @@ -345,85 +351,101 @@ private: return alg->getProperty("OutputWorkspace"); } - double det_fwhm(const API::MatrixWorkspace &ws, const size_t fgd_first, - const size_t fgd_last) { + static API::MatrixWorkspace_sptr + extractNonMonitorSpectrum(API::MatrixWorkspace_sptr &ws) { + auto alg = API::AlgorithmManager::Instance().createUnmanaged( + "ExtractSingleSpectrum"); + alg->initialize(); + alg->setChild(true); + alg->setProperty("InputWorkspace", ws); + alg->setPropertyValue("OutputWorkspace", "_unused_for_child"); + alg->setProperty("WorkspaceIndex", 0); + alg->execute(); + return alg->getProperty("OutputWorkspace"); + } + + static const LogValues + sumInQBentSampleDominatedLargeSecondSlitAngularSpread(const double s3_fwhm, + const double da) { + constexpr double om_fwhm{0.1}; + constexpr double s2_fwhm{2.1 * om_fwhm}; + return LogValues(om_fwhm, s2_fwhm, s3_fwhm, da); + } + + static const LogValues + sumInQBentSampleDominateSmallSecondSlitAngularSpread(const double s3_fwhm, + const double da) { + constexpr double om_fwhm{0.1}; + constexpr double s2_fwhm{1.9 * om_fwhm}; + return LogValues(om_fwhm, s2_fwhm, s3_fwhm, da); + } + + static const LogValues sumInQBeamDivergenceDominated(const double s3_fwhm, + const double da) { + constexpr double om_fwhm{-0.1}; + constexpr double s2_fwhm{1.1 * DET_RESOLUTION / DET_DIST}; + return LogValues(om_fwhm, s2_fwhm, s3_fwhm, da); + } + + static const LogValues sumInQDetectorResolutionDominated(const double s3_fwhm, + const double da) { + constexpr double om_fwhm{-0.1}; + constexpr double s2_fwhm{0.9 * DET_RESOLUTION / DET_DIST}; + return LogValues(om_fwhm, s2_fwhm, s3_fwhm, da); + } + + static double err_ray_temp(const std::vector<int> &foreground, + const double l2, const double angle_bragg) { using namespace boost::math; - std::vector<double> angd; - const auto &spectrumInfo = ws.spectrumInfo(); - for (size_t i = fgd_first; i <= fgd_last; ++i) { - if (spectrumInfo.isMonitor(i)) { - continue; - } - const auto &ys = ws.y(i); - const auto sum = std::accumulate(ys.cbegin(), ys.cend(), 0.0); - angd.emplace_back(sum); - } - const auto temp = [&angd]() { - double sum{0.0}; - for (size_t i = 0; i < angd.size(); ++i) { - sum += static_cast<double>(i) * angd[i]; - } - return sum; - }(); - const auto total_angd = std::accumulate(angd.cbegin(), angd.cend(), 0.0); - const auto pref = temp / total_angd + static_cast<double>(fgd_first); - const auto angd_cen = pref - static_cast<double>(fgd_first); - const auto tt = [&angd, &angd_cen]() { - double sum{0.0}; - for (size_t i = 0; i < angd.size(); ++i) { - sum += angd[i] * pow<2>(angd_cen - static_cast<double>(i)); - } - return sum; - }(); - return 2. * std::sqrt(2. * std::log(2.)) * PIXEL_SIZE * - std::sqrt(tt / total_angd); + const auto width = foreground.back() - foreground.front() + 1; + return 0.68 * + std::sqrt((pow<2>(width * PIXEL_SIZE) + pow<2>(SLIT2_SIZE)) / + pow<2>(l2)) / + angle_bragg; } - double err_ray(const double l2, const double angle_bragg, - const std::string &sumType, const bool polarized, - const double om_fwhm) { + static double err_ray_SumInLambda(const double angle_bragg, + const LogValues &values) { using namespace boost::math; - const auto interslit = SLIT1_DIST - SLIT2_DIST; - const auto da = 0.68 * std::sqrt((pow<2>(SLIT1_SIZE) + pow<2>(SLIT2_SIZE)) / - pow<2>(interslit)); - const auto s2_fwhm = (0.68 * SLIT1_SIZE) / interslit; - const auto s3_fwhm = (0.68 * SLIT2_SIZE) / (SLIT2_DIST + l2); - double err_ray1; + return std::sqrt(pow<2>(values.da) + pow<2>(values.om_fwhm)) / angle_bragg; + } + + static double err_ray(const std::vector<int> &foreground, const double l2, + const double angle_bragg, const std::string &sumType, + const LogValues &values) { + using namespace boost::math; + double err; if (sumType == "SumInQ") { - if (om_fwhm > 0) { - if (s2_fwhm >= 2 * om_fwhm) { - err_ray1 = std::sqrt(pow<2>(DET_RESOLUTION / l2) + pow<2>(s3_fwhm) + - pow<2>(om_fwhm)) / - angle_bragg; + if (values.om_fwhm > 0) { + if (values.s2_fwhm >= 2 * values.om_fwhm) { + err = std::sqrt(pow<2>(DET_RESOLUTION / (SLIT2_DIST + l2)) + + pow<2>(values.s3_fwhm) + pow<2>(values.om_fwhm)) / + angle_bragg; } else { - err_ray1 = std::sqrt(pow<2>(DET_RESOLUTION / (2. * l2)) + - pow<2>(s3_fwhm) + pow<2>(s2_fwhm)) / - angle_bragg; + err = std::sqrt(pow<2>(DET_RESOLUTION / (2. * (SLIT2_DIST + l2))) + + pow<2>(values.s3_fwhm) + pow<2>(values.s2_fwhm)) / + angle_bragg; } } else { - if (s2_fwhm > DET_RESOLUTION / l2) { - err_ray1 = std::sqrt(pow<2>(DET_RESOLUTION / l2) + pow<2>(s3_fwhm)) / - angle_bragg; + if (values.s2_fwhm > DET_RESOLUTION / l2) { + err = std::sqrt(pow<2>(DET_RESOLUTION / (SLIT2_DIST + l2)) + + pow<2>(values.s3_fwhm)) / + angle_bragg; } else { - err_ray1 = - std::sqrt(pow<2>(da) + pow<2>(DET_RESOLUTION / l2)) / angle_bragg; + err = std::sqrt(pow<2>(values.da) + + pow<2>(DET_RESOLUTION / (SLIT2_DIST + l2))) / + angle_bragg; } } } else { - if (polarized) { - err_ray1 = std::sqrt(pow<2>(da)) / angle_bragg; - } else { - err_ray1 = std::sqrt(pow<2>(da) + pow<2>(om_fwhm)) / angle_bragg; - } + err = err_ray_SumInLambda(angle_bragg, values); + const auto temp = err_ray_temp(foreground, l2, angle_bragg); + err = std::min(err, temp); } - const auto err_ray_temp = - 0.68 * - std::sqrt((pow<2>(PIXEL_SIZE) + pow<2>(SLIT2_SIZE)) / pow<2>(l2)) / - angle_bragg; - return std::min(err_ray1, err_ray_temp); + return err; } - double err_res(const double lambda, const double l2) { + static double err_res(const double lambda, const double l2) { using namespace boost::math; const auto tofd = L1 + l2; const auto period = 60. / CHOPPER_SPEED; @@ -438,43 +460,7 @@ private: (2 * chop_res + det_res); } - double om_fwhm(const double l2, const double dirl2, const double dirs2w, - const double dirs3w, const double det_fwhm, - const double detdb_fwhm) { - using namespace boost::math; - const double sdr = SLIT2_DIST + l2; - const double ratio = SLIT2_SIZE / SLIT1_SIZE; - const double interslit = SLIT1_DIST - SLIT2_DIST; - const double vs = sdr + (ratio * interslit) / (1 + ratio); - const double da = 0.68 * std::sqrt(pow<2>(SLIT1_SIZE) + - pow<2>(SLIT2_SIZE) / pow<2>(interslit)); - const double da_det = std::sqrt(pow<2>(da * vs) + pow<2>(DET_RESOLUTION)); - double om_fwhm{0}; - if (std::abs(SLIT1_SIZE - dirs2w) >= 0.00004 || - std::abs(SLIT2_SIZE - dirs3w) >= 0.00004) { - if ((det_fwhm - da_det) >= 0.) { - if (std::sqrt(pow<2>(det_fwhm) - pow<2>(da_det)) >= PIXEL_SIZE) { - om_fwhm = 0.5 * std::sqrt(pow<2>(det_fwhm) - pow<2>(da_det)) / dirl2; - } else { - om_fwhm = 0; - } - } - } else { - if (pow<2>(det_fwhm) - pow<2>(detdb_fwhm) >= 0.) { - if (std::sqrt(pow<2>(det_fwhm) - pow<2>(detdb_fwhm)) >= PIXEL_SIZE) { - om_fwhm = - 0.5 * std::sqrt(pow<2>(det_fwhm) - pow<2>(detdb_fwhm)) / dirl2; - } else { - om_fwhm = 0.; - } - } else { - om_fwhm = 0.; - } - } - return om_fwhm; - } - - double width_res(const double lambda, const double l2) { + static double width_res(const double lambda, const double l2) { using namespace boost::math; const auto tofd = L1 + l2; const auto period = 60. / CHOPPER_SPEED; @@ -494,9 +480,12 @@ private: class ReflectometryMomentumTransferTestPerformance : public CxxTest::TestSuite { public: void setUp() override { - m_reflectedWS = makeWS(); - m_directWS = m_reflectedWS->clone(); - m_algorithm = makeAlgorithm(m_reflectedWS, m_directWS); + using Geometry::deg2rad; + constexpr int nBins{10000}; + ReflectometryMomentumTransferTest::LogValues logValues(0.1, 0.1, 0.1, 0.1); + m_reflectedWS = ReflectometryMomentumTransferTest::make_ws( + 0.7 * deg2rad, nBins, logValues); + m_algorithm = makeAlgorithm(m_reflectedWS); } void test_performance() { @@ -506,24 +495,16 @@ public: private: static API::IAlgorithm_sptr - makeAlgorithm(API::MatrixWorkspace_sptr &reflectedWS, - API::MatrixWorkspace_sptr &directWS) { - std::vector<int> foreground(2); - foreground.front() = 0; - foreground.back() = 0; + makeAlgorithm(API::MatrixWorkspace_sptr &reflectedWS) { + std::vector<int> foreground(2, 0); auto alg = boost::make_shared<Algorithms::ReflectometryMomentumTransfer>(); alg->setChild(true); alg->setRethrows(true); alg->initialize(); - alg->isInitialized(); alg->setProperty("InputWorkspace", reflectedWS); alg->setPropertyValue("OutputWorkspace", "_unused_for_child"); - alg->setProperty("ReflectedBeamWorkspace", reflectedWS); alg->setProperty("ReflectedForeground", foreground); - alg->setProperty("DirectBeamWorkspace", directWS); - alg->setProperty("DirectForeground", foreground); alg->setProperty("SummationType", "SumInLambda"); - alg->setProperty("Polarized", false); alg->setProperty("PixelSize", PIXEL_SIZE); alg->setProperty("DetectorResolution", DET_RESOLUTION); alg->setProperty("ChopperSpeed", CHOPPER_SPEED); @@ -538,50 +519,8 @@ private: return alg; } - static API::MatrixWorkspace_sptr makeWS() { - using namespace WorkspaceCreationHelper; - constexpr double startX{1000.}; - const Kernel::V3D sourcePos{0., 0., -L1}; - const Kernel::V3D &monitorPos = sourcePos; - const Kernel::V3D samplePos{ - 0., - 0., - 0., - }; - const double braggAngle{0.7}; - const auto detZ = DET_DIST * std::cos(2 * braggAngle); - const auto detY = DET_DIST * std::sin(2 * braggAngle); - const Kernel::V3D detectorPos{0., detY, detZ}; - const Kernel::V3D slit1Pos{0., 0., -SLIT1_DIST}; - const Kernel::V3D slit2Pos{0., 0., -SLIT2_DIST}; - constexpr int nBins{10000}; - auto ws = create2DWorkspaceWithReflectometryInstrument( - startX, slit1Pos, slit2Pos, SLIT1_SIZE, SLIT2_SIZE, sourcePos, - monitorPos, samplePos, detectorPos, nBins, TOF_BIN_WIDTH); - // Add slit sizes to sample logs, too. - auto &run = ws->mutableRun(); - constexpr bool overwrite{true}; - const std::string meters{"m"}; - run.addProperty("slit1.size", SLIT1_SIZE, meters, overwrite); - run.addProperty("slit2.size", SLIT2_SIZE, meters, overwrite); - auto convertUnits = - API::AlgorithmManager::Instance().createUnmanaged("ConvertUnits"); - convertUnits->initialize(); - convertUnits->setChild(true); - convertUnits->setRethrows(true); - convertUnits->setProperty("InputWorkspace", ws); - convertUnits->setPropertyValue("OutputWorkspace", "_unused_for_child"); - convertUnits->setProperty("Target", "Wavelength"); - convertUnits->setProperty("EMode", "Elastic"); - convertUnits->execute(); - API::MatrixWorkspace_sptr outWS = - convertUnits->getProperty("OutputWorkspace"); - return outWS; - } - private: API::IAlgorithm_sptr m_algorithm; - API::MatrixWorkspace_sptr m_directWS; API::MatrixWorkspace_sptr m_reflectedWS; }; diff --git a/Framework/Algorithms/test/ReflectometryReductionOne2Test.h b/Framework/Algorithms/test/ReflectometryReductionOne2Test.h index 13dbbb5314f7c2b390a41ea091aa83f2cbe47003..02d462b4f227bb5a7d8606a1f4e17b9f256ee057 100644 --- a/Framework/Algorithms/test/ReflectometryReductionOne2Test.h +++ b/Framework/Algorithms/test/ReflectometryReductionOne2Test.h @@ -70,7 +70,7 @@ public: // No direct beam normalization // No transmission correction ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "1"); + setupAlgorithm(alg, 1.5, 15.0, "2"); MatrixWorkspace_sptr outLam = runAlgorithmLam(alg); TS_ASSERT(outLam->x(0)[0] >= 1.5); @@ -84,10 +84,10 @@ public: // No monitor normalization // No direct beam normalization // No transmission correction - // Processing instructions : 1+2 + // Processing instructions : 2+3 ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "1+2"); + setupAlgorithm(alg, 1.5, 15.0, "2+3"); MatrixWorkspace_sptr outLam = runAlgorithmLam(alg); TS_ASSERT(outLam->x(0)[0] >= 1.5); @@ -102,10 +102,10 @@ public: // No monitor normalization // No direct beam normalization // No transmission correction - // Processing instructions : 1-3 + // Processing instructions : 2-4 spectra is (1-3 workspace indices) ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "1-3"); + setupAlgorithm(alg, 1.5, 15.0, "2-4"); MatrixWorkspace_sptr outLam = runAlgorithmLam(alg); TS_ASSERT(outLam->x(0)[0] >= 1.5); @@ -120,10 +120,10 @@ public: // No monitor normalization // No direct beam normalization // No transmission correction - // Processing instructions : 2,1+3 (two separate groups) + // Processing instructions : 3,2+4 (two separate groups) ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "2,1+3"); + setupAlgorithm(alg, 1.5, 15.0, "3,2+4"); // Run the algorithm. There should be 2 output histograms, one for each // input group. Note that the group order is swapped from the input order // because they are sorted by the first spectrum number in the group, @@ -142,10 +142,10 @@ public: } void test_bad_processing_instructions() { - // Processing instructions : 5+6 + // Processing instructions : 6+7 ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "5+6"); + setupAlgorithm(alg, 1.5, 15.0, "6+7"); // Must throw as spectrum 2 is not defined TS_ASSERT_THROWS_ANYTHING(alg.execute()); } @@ -158,7 +158,7 @@ public: // SummationType : SumInLambda (same as default) ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "1"); + setupAlgorithm(alg, 1.5, 15.0, "2"); alg.setProperty("SummationType", "SumInLambda"); MatrixWorkspace_sptr outLam = runAlgorithmLam(alg); @@ -177,7 +177,7 @@ public: // ReductionType : DivergentBeam (invalid) ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "1"); + setupAlgorithm(alg, 1.5, 15.0, "2"); alg.setProperty("SummationType", "SumInLambda"); alg.setProperty("ReductionType", "DivergentBeam"); TS_ASSERT_THROWS_ANYTHING(alg.execute()); @@ -195,7 +195,7 @@ public: // MonitorBackgroundWavelengthMax : Not given ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "1"); + setupAlgorithm(alg, 1.5, 15.0, "2"); alg.setProperty("I0MonitorIndex", "0"); MatrixWorkspace_sptr outLam = runAlgorithmLam(alg); @@ -213,7 +213,7 @@ public: // Monitor normalization // No direct beam normalization // No transmission correction - // Processing instructions : 2 + // Processing instructions : 3 // I0MonitorIndex: 0 // MonitorBackgroundWavelengthMin : 0.5 @@ -227,7 +227,7 @@ public: std::fill(Y.begin(), Y.begin() + 2, 1.0); ReflectometryReductionOne2 alg; - setupAlgorithmMonitorCorrection(alg, 0.0, 15.0, "2", inputWS, false); + setupAlgorithmMonitorCorrection(alg, 0.0, 15.0, "3", inputWS, false); MatrixWorkspace_sptr outLam = runAlgorithmLam(alg, 10); TS_ASSERT(outLam->x(0)[0] >= 0.0); @@ -243,7 +243,7 @@ public: // Monitor normalization // No direct beam normalization // No transmission correction - // Processing instructions : 1 + // Processing instructions : 2 // I0MonitorIndex: 0 // MonitorBackgroundWavelengthMin : 0.5 @@ -257,7 +257,7 @@ public: std::fill(Y.begin(), Y.begin() + 2, 1.0); ReflectometryReductionOne2 alg; - setupAlgorithmMonitorCorrection(alg, 0.0, 15.0, "1", inputWS, true); + setupAlgorithmMonitorCorrection(alg, 0.0, 15.0, "2", inputWS, true); MatrixWorkspace_sptr outLam = runAlgorithmLam(alg, 16); TS_ASSERT(outLam->x(0)[0] >= 0.0); @@ -271,7 +271,7 @@ public: // Transmission run is the same as input run ReflectometryReductionOne2 alg; - setupAlgorithmTransmissionCorrection(alg, 1.5, 15.0, "1", m_multiDetectorWS, + setupAlgorithmTransmissionCorrection(alg, 1.5, 15.0, "2", m_multiDetectorWS, false); MatrixWorkspace_sptr outLam = runAlgorithmLam(alg); @@ -284,7 +284,7 @@ public: // Transmission run is the same as input run ReflectometryReductionOne2 alg; - setupAlgorithmTransmissionCorrection(alg, 1.5, 15.0, "1", m_multiDetectorWS, + setupAlgorithmTransmissionCorrection(alg, 1.5, 15.0, "2", m_multiDetectorWS, true); MatrixWorkspace_sptr outLam = runAlgorithmLam(alg); @@ -296,11 +296,10 @@ public: void test_transmission_correction_with_mapped_spectra() { // Run workspace spectrum numbers are 1,2,3,4. // Transmission workspace has spectrum numbers 2,3,4,5. - // Processing instructions 2-3 in the run workspace map to - // spectra 3-4, which map to indices 1-2 in the transmission - // workspace. + // Processing instructions 3-4 in the run workspace map to + // spectra 3-4. ReflectometryReductionOne2 alg; - setupAlgorithmTransmissionCorrection(alg, 1.5, 15.0, "2-3", + setupAlgorithmTransmissionCorrection(alg, 1.5, 15.0, "3-4", m_transmissionWS, true); MatrixWorkspace_sptr outLam = runAlgorithmLam(alg); @@ -311,35 +310,61 @@ public: void test_transmission_correction_with_bad_mapped_spectra() { // Run workspace spectrum numbers are 1,2,3,4. // Transmission workspace has spectrum numbers 2,3,4,5. - // Processing instructions 0 in the run workspace maps to + // Processing instructions 1 in the run workspace maps to // spectrum 1, which doesn't exist in the transmission // workspace. ReflectometryReductionOne2 alg; - setupAlgorithmTransmissionCorrection(alg, 1.5, 15.0, "0", m_transmissionWS, + setupAlgorithmTransmissionCorrection(alg, 1.5, 15.0, "1", m_transmissionWS, true); TS_ASSERT_THROWS_ANYTHING(alg.execute()); } - void test_transmission_correction_with_different_spectra() { - // Run workspace spectrum numbers are 1,2,3,4. Transmission workspace has - // spectrum numbers 2,3,4,5. Processing instructions 2,3 are used in the - // run and transmission workspaces without any mapping i.e. spectra 3-4 in - // the run and spectra 4-5 in the transmission workspace are used. + void test_transmission_processing_instructions() { ReflectometryReductionOne2 alg; - setupAlgorithmTransmissionCorrection(alg, 1.5, 15.0, "2-3", - m_transmissionWS, true); - alg.setProperty("StrictSpectrumChecking", "0"); + setupAlgorithmTransmissionCorrection(alg, 1.5, 15.0, "3-4", + m_transmissionWS, false); + alg.setPropertyValue("TransmissionProcessingInstructions", "3-4"); + MatrixWorkspace_sptr outLam = runAlgorithmLam(alg); + + TS_ASSERT_DELTA(outLam->y(0)[0], 0.0807, 0.0001); + TS_ASSERT_DELTA(outLam->y(0)[7], 0.0802, 0.0001); + } + + void test_transmission_processing_instructions_with_bad_instructions() { + ReflectometryReductionOne2 alg; + setupAlgorithmTransmissionCorrection(alg, 1.5, 15.0, "1-2", + m_transmissionWS, false); + alg.setPropertyValue("TransmissionProcessingInstructions", "1"); + TS_ASSERT_THROWS_ANYTHING(alg.execute()); + } + + void test_transmission_processing_instructions_that_are_different() { + ReflectometryReductionOne2 alg; + setupAlgorithmTransmissionCorrection(alg, 1.5, 15.0, "3-4", + m_transmissionWS, false); + alg.setPropertyValue("TransmissionProcessingInstructions", "3"); MatrixWorkspace_sptr outLam = runAlgorithmLam(alg); - TS_ASSERT_DELTA(outLam->y(0)[0], 0.0571, 0.0001); - TS_ASSERT_DELTA(outLam->y(0)[7], 0.0571, 0.0001); + TS_ASSERT_DELTA(outLam->y(0)[0], 0.2029, 0.0001); + TS_ASSERT_DELTA(outLam->y(0)[7], 0.2009, 0.0001); + } + + void test_transmission_processing_instructions_two_runs() { + ReflectometryReductionOne2 alg; + setupAlgorithmTransmissionCorrection(alg, 1.5, 15.0, "3", m_transmissionWS, + true); + alg.setPropertyValue("TransmissionProcessingInstructions", "3"); + MatrixWorkspace_sptr outLam = runAlgorithmLam(alg); + + TS_ASSERT_DELTA(outLam->y(0)[0], 0.1009, 0.0001); + TS_ASSERT_DELTA(outLam->y(0)[7], 0.1003, 0.0001); } void test_exponential_correction() { // CorrectionAlgorithm: ExponentialCorrection ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "2"); + setupAlgorithm(alg, 1.5, 15.0, "3"); alg.setProperty("CorrectionAlgorithm", "ExponentialCorrection"); alg.setProperty("C0", 0.2); alg.setProperty("C1", 0.1); @@ -353,7 +378,7 @@ public: // CorrectionAlgorithm: PolynomialCorrection ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "2"); + setupAlgorithm(alg, 1.5, 15.0, "3"); alg.setProperty("CorrectionAlgorithm", "PolynomialCorrection"); alg.setProperty("Polynomial", "0.1,0.3,0.5"); MatrixWorkspace_sptr outLam = runAlgorithmLam(alg); @@ -370,7 +395,7 @@ public: // Processing instructions : 2 ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "2"); + setupAlgorithm(alg, 1.5, 15.0, "3"); MatrixWorkspace_sptr outQ = runAlgorithmQ(alg); // X range in outQ @@ -386,10 +411,10 @@ public: // No monitor normalization // No direct beam normalization // No transmission correction - // Processing instructions : 2,1+3 (two separate groups) + // Processing instructions : 3,2+4 (two separate groups) ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "2,1+3"); + setupAlgorithm(alg, 1.5, 15.0, "3,2+4"); // Run the algorithm. There should be 2 output histograms, one for each // input group. Note that the group order is swapped from the input order // because they are sorted by the first spectrum number in the group, @@ -417,7 +442,7 @@ public: // ReductionType : not set (invalid) ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "1"); + setupAlgorithm(alg, 1.5, 15.0, "2"); alg.setProperty("SummationType", "SumInQ"); TS_ASSERT_THROWS_ANYTHING(alg.execute()); } @@ -430,7 +455,7 @@ public: // SummationType : SumInQ // ReductionType : DivergentBeam ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "1"); + setupAlgorithm(alg, 1.5, 15.0, "2"); alg.setProperty("SummationType", "SumInQ"); alg.setProperty("ReductionType", "DivergentBeam"); alg.setProperty("ThetaIn", 25.0); @@ -453,7 +478,7 @@ public: // ReductionType : NonFlatSample ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "1"); + setupAlgorithm(alg, 1.5, 15.0, "2"); alg.setProperty("SummationType", "SumInQ"); alg.setProperty("ReductionType", "NonFlatSample"); MatrixWorkspace_sptr outLam = runAlgorithmLam(alg, 10); @@ -471,7 +496,7 @@ public: // Monitor normalization // No direct beam normalization // No transmission correction - // Processing instructions : 2 + // Processing instructions : 3 // SummationType : SumInQ // ReductionType : DivergentBeam @@ -487,7 +512,7 @@ public: std::fill(Y.begin(), Y.begin() + 2, 1.0); ReflectometryReductionOne2 alg; - setupAlgorithmMonitorCorrection(alg, 0.0, 15.0, "2", inputWS, false); + setupAlgorithmMonitorCorrection(alg, 0.0, 15.0, "3", inputWS, false); alg.setProperty("SummationType", "SumInQ"); alg.setProperty("ReductionType", "DivergentBeam"); alg.setProperty("ThetaIn", 25.0); @@ -505,7 +530,7 @@ public: // Transmission run is the same as input run ReflectometryReductionOne2 alg; - setupAlgorithmTransmissionCorrection(alg, 1.5, 15.0, "1", m_multiDetectorWS, + setupAlgorithmTransmissionCorrection(alg, 1.5, 15.0, "2", m_multiDetectorWS, false); alg.setProperty("SummationType", "SumInQ"); alg.setProperty("ReductionType", "DivergentBeam"); @@ -524,7 +549,7 @@ public: // CorrectionAlgorithm: ExponentialCorrection ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "2"); + setupAlgorithm(alg, 1.5, 15.0, "3"); alg.setProperty("SummationType", "SumInQ"); alg.setProperty("ReductionType", "DivergentBeam"); alg.setProperty("ThetaIn", 25.0); @@ -546,10 +571,10 @@ public: // No monitor normalization // No direct beam normalization // No transmission correction - // Processing instructions : 2 + // Processing instructions : 3 ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "2"); + setupAlgorithm(alg, 1.5, 15.0, "3"); alg.setProperty("SummationType", "SumInQ"); alg.setProperty("ReductionType", "DivergentBeam"); alg.setProperty("ThetaIn", 25.0); @@ -570,10 +595,10 @@ public: // No monitor normalization // No direct beam normalization // No transmission correction - // Processing instructions : 0 + // Processing instructions : 1 ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "0"); + setupAlgorithm(alg, 1.5, 15.0, "1"); alg.setProperty("InputWorkspace", m_singleDetectorWS); alg.setProperty("SummationType", "SumInQ"); alg.setProperty("ReductionType", "DivergentBeam"); @@ -593,7 +618,7 @@ public: void test_sum_in_q_exclude_partial_bins() { // Sum in Q, single detector ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "1"); + setupAlgorithm(alg, 1.5, 15.0, "2"); alg.setProperty("SummationType", "SumInQ"); alg.setProperty("ReductionType", "DivergentBeam"); alg.setProperty("ThetaIn", 25.0); @@ -611,7 +636,7 @@ public: void test_sum_in_q_exclude_partial_bins_multiple_detectors() { // Sum in Q, multiple detectors in group ReflectometryReductionOne2 alg; - setupAlgorithm(alg, 1.5, 15.0, "1-3"); + setupAlgorithm(alg, 1.5, 15.0, "2-4"); alg.setProperty("SummationType", "SumInQ"); alg.setProperty("ReductionType", "DivergentBeam"); alg.setProperty("ThetaIn", 25.0); @@ -638,7 +663,7 @@ public: alg.setProperty("InputWorkspace", inputWS); alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); - alg.setPropertyValue("ProcessingInstructions", "1+2"); + alg.setPropertyValue("ProcessingInstructions", "2+3"); alg.setPropertyValue("OutputWorkspace", "IvsQ"); alg.setPropertyValue("OutputWorkspaceWavelength", "IvsLam"); @@ -682,7 +707,7 @@ public: alg.setProperty("InputWorkspace", inputWS); alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); - alg.setPropertyValue("ProcessingInstructions", "2"); + alg.setPropertyValue("ProcessingInstructions", "3"); alg.setPropertyValue("OutputWorkspace", "IvsQ"); alg.setPropertyValue("OutputWorkspaceWavelength", "IvsLam"); @@ -721,7 +746,7 @@ public: alg.setProperty("InputWorkspace", m_multiDetectorWS); alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); - alg.setPropertyValue("ProcessingInstructions", "1+2, 3"); + alg.setPropertyValue("ProcessingInstructions", "2+3, 4"); alg.setPropertyValue("OutputWorkspace", "IvsQ"); alg.setPropertyValue("OutputWorkspaceWavelength", "IvsLam"); alg.setProperty("ThetaIn", 22.0); @@ -738,7 +763,7 @@ public: alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); alg.setProperty("Debug", false); - alg.setPropertyValue("ProcessingInstructions", "1+2"); + alg.setPropertyValue("ProcessingInstructions", "2+3"); alg.setPropertyValue("OutputWorkspace", "IvsQ"); alg.execute(); @@ -759,7 +784,7 @@ public: alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); alg.setProperty("Debug", false); - alg.setPropertyValue("ProcessingInstructions", "1+2"); + alg.setPropertyValue("ProcessingInstructions", "2+3"); alg.execute(); TS_ASSERT(AnalysisDataService::Instance().doesExist("IvsQ")); @@ -779,7 +804,7 @@ public: alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); alg.setProperty("Debug", true); - alg.setPropertyValue("ProcessingInstructions", "1+2"); + alg.setPropertyValue("ProcessingInstructions", "2+3"); alg.execute(); TS_ASSERT(AnalysisDataService::Instance().doesExist("IvsQ")); @@ -800,7 +825,7 @@ public: alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); alg.setProperty("Debug", true); - alg.setPropertyValue("ProcessingInstructions", "1+2"); + alg.setPropertyValue("ProcessingInstructions", "2+3"); alg.execute(); TS_ASSERT(AnalysisDataService::Instance().doesExist("IvsQ_1234")); diff --git a/Framework/Algorithms/test/ReflectometryReductionOneAuto2Test.h b/Framework/Algorithms/test/ReflectometryReductionOneAuto2Test.h index 1283a17d122f4c0136070947d39011ce054de290..df6eb8e43654e6d6a90a318e935f7aab8d6e541b 100644 --- a/Framework/Algorithms/test/ReflectometryReductionOneAuto2Test.h +++ b/Framework/Algorithms/test/ReflectometryReductionOneAuto2Test.h @@ -70,7 +70,7 @@ private: alg.setProperty("InputWorkspace", inter); alg.setProperty("ThetaIn", theta); alg.setProperty("CorrectionAlgorithm", "None"); - alg.setProperty("ProcessingInstructions", "3"); + alg.setProperty("ProcessingInstructions", "4"); alg.setProperty("Debug", false); } @@ -109,7 +109,7 @@ public: alg.setProperty("InputWorkspace", m_notTOF); alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 15.0); - alg.setProperty("ProcessingInstructions", "0"); + alg.setProperty("ProcessingInstructions", "1"); alg.setPropertyValue("OutputWorkspace", "IvsQ"); alg.setPropertyValue("OutputWorkspaceBinned", "IvsQ_binned"); alg.setPropertyValue("OutputWorkspaceWavelength", "IvsLam"); @@ -123,7 +123,7 @@ public: alg.setProperty("InputWorkspace", m_TOF); alg.setProperty("WavelengthMin", 15.0); alg.setProperty("WavelengthMax", 1.0); - alg.setProperty("ProcessingInstructions", "0"); + alg.setProperty("ProcessingInstructions", "1"); alg.setPropertyValue("OutputWorkspace", "IvsQ"); alg.setPropertyValue("OutputWorkspaceBinned", "IvsQ_binned"); alg.setPropertyValue("OutputWorkspaceWavelength", "IvsLam"); @@ -137,7 +137,7 @@ public: alg.setProperty("InputWorkspace", m_TOF); alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 15.0); - alg.setProperty("ProcessingInstructions", "0"); + alg.setProperty("ProcessingInstructions", "1"); alg.setProperty("MonitorBackgroundWavelengthMin", 3.0); alg.setProperty("MonitorBackgroundWavelengthMax", 0.5); alg.setPropertyValue("OutputWorkspace", "IvsQ"); @@ -153,7 +153,7 @@ public: alg.setProperty("InputWorkspace", m_TOF); alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 15.0); - alg.setProperty("ProcessingInstructions", "0"); + alg.setProperty("ProcessingInstructions", "1"); alg.setProperty("MonitorIntegrationWavelengthMin", 15.0); alg.setProperty("MonitorIntegrationWavelengthMax", 1.5); alg.setPropertyValue("OutputWorkspace", "IvsQ"); @@ -170,7 +170,7 @@ public: alg.setProperty("FirstTransmissionRun", m_notTOF); alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 15.0); - alg.setProperty("ProcessingInstructions", "0"); + alg.setProperty("ProcessingInstructions", "1"); alg.setProperty("MonitorIntegrationWavelengthMin", 1.0); alg.setProperty("MonitorIntegrationWavelengthMax", 15.0); alg.setPropertyValue("OutputWorkspace", "IvsQ"); @@ -272,7 +272,7 @@ public: alg.setProperty("OutputWorkspace", "IvsQ"); alg.setProperty("OutputWorkspaceBinned", "IvsQ_binned"); alg.setProperty("OutputWorkspaceWavelength", "IvsLam"); - alg.setProperty("ProcessingInstructions", "3"); + alg.setProperty("ProcessingInstructions", "4"); alg.execute(); MatrixWorkspace_sptr out = alg.getProperty("OutputWorkspaceBinned"); @@ -418,7 +418,7 @@ public: alg.setProperty("OutputWorkspace", "IvsQ"); alg.setProperty("OutputWorkspaceBinned", "IvsQ_binned"); alg.setProperty("OutputWorkspaceWavelength", "IvsLam"); - alg.setProperty("ProcessingInstructions", "3"); + alg.setProperty("ProcessingInstructions", "4"); alg.execute(); MatrixWorkspace_sptr corrected = alg.getProperty("OutputWorkspace"); @@ -463,7 +463,7 @@ public: alg.setProperty("OutputWorkspace", "IvsQ"); alg.setProperty("OutputWorkspaceBinned", "IvsQ_binned"); alg.setProperty("OutputWorkspaceWavelength", "IvsLam"); - alg.setProperty("ProcessingInstructions", "3"); + alg.setProperty("ProcessingInstructions", "4"); alg.execute(); MatrixWorkspace_sptr corrected = alg.getProperty("OutputWorkspace"); @@ -487,7 +487,7 @@ public: alg.setProperty("InputWorkspace", m_TOF); alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("MomentumTransferMin", 1.0); alg.setProperty("MomentumTransferMax", 10.0); alg.setProperty("MomentumTransferStep", -0.04); @@ -520,7 +520,7 @@ public: alg.setProperty("InputWorkspace", m_TOF); alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("MomentumTransferMin", 1.0); alg.setProperty("MomentumTransferMax", 10.0); alg.setProperty("MomentumTransferStep", 0.04); @@ -544,7 +544,7 @@ public: alg.setProperty("InputWorkspace", m_TOF); alg.setProperty("WavelengthMin", 1.5); alg.setProperty("WavelengthMax", 15.0); - alg.setProperty("ProcessingInstructions", "2"); + alg.setProperty("ProcessingInstructions", "3"); alg.setProperty("MomentumTransferStep", 0.04); alg.setPropertyValue("OutputWorkspace", "IvsQ"); alg.setPropertyValue("OutputWorkspaceBinned", "IvsQ_binned"); @@ -573,7 +573,7 @@ public: alg.setProperty("InputWorkspace", inter); alg.setProperty("ThetaIn", theta); alg.setProperty("CorrectionAlgorithm", "None"); - alg.setProperty("ProcessingInstructions", "3"); + alg.setProperty("ProcessingInstructions", "4"); alg.execute(); TS_ASSERT(AnalysisDataService::Instance().doesExist("IvsQ_binned_13460")); @@ -593,7 +593,7 @@ public: alg.setProperty("InputWorkspace", inter); alg.setProperty("ThetaIn", theta); alg.setProperty("CorrectionAlgorithm", "None"); - alg.setProperty("ProcessingInstructions", "3"); + alg.setProperty("ProcessingInstructions", "4"); alg.setProperty("OutputWorkspaceBinned", "IvsQ_binned"); alg.execute(); @@ -615,7 +615,7 @@ public: alg.setProperty("InputWorkspace", inter); alg.setProperty("ThetaIn", theta); alg.setProperty("CorrectionAlgorithm", "None"); - alg.setProperty("ProcessingInstructions", "3"); + alg.setProperty("ProcessingInstructions", "4"); alg.setProperty("OutputWorkspaceBinned", "IvsQ_binned"); alg.setProperty("OutputWorkspace", "IvsQ"); alg.setProperty("OutputWorkspaceWavelength", "IvsLam"); @@ -638,7 +638,7 @@ public: alg.setProperty("InputWorkspace", inter); alg.setProperty("ThetaIn", theta); alg.setProperty("CorrectionAlgorithm", "None"); - alg.setProperty("ProcessingInstructions", "3"); + alg.setProperty("ProcessingInstructions", "4"); alg.setProperty("Debug", true); alg.execute(); @@ -659,7 +659,7 @@ public: alg.setProperty("InputWorkspace", inter); alg.setProperty("ThetaIn", theta); alg.setProperty("CorrectionAlgorithm", "None"); - alg.setProperty("ProcessingInstructions", "3"); + alg.setProperty("ProcessingInstructions", "4"); alg.setProperty("Debug", false); alg.execute(); @@ -681,7 +681,7 @@ public: alg.setProperty("InputWorkspace", inter); alg.setProperty("ThetaIn", theta); alg.setProperty("CorrectionAlgorithm", "None"); - alg.setProperty("ProcessingInstructions", "3"); + alg.setProperty("ProcessingInstructions", "4"); alg.setProperty("Debug", true); alg.execute(); @@ -703,7 +703,7 @@ public: alg.setProperty("InputWorkspace", inter); alg.setProperty("ThetaIn", theta); alg.setProperty("CorrectionAlgorithm", "None"); - alg.setProperty("ProcessingInstructions", "3"); + alg.setProperty("ProcessingInstructions", "4"); alg.setProperty("Debug", false); alg.execute(); @@ -725,7 +725,7 @@ public: alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 15.0); alg.setProperty("ThetaIn", 10.0); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("MomentumTransferStep", 0.04); alg.setProperty("PolarizationAnalysis", "PA"); alg.setProperty("Pp", "0.9,0,0"); @@ -773,7 +773,7 @@ public: alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 15.0); alg.setProperty("ThetaIn", 10.0); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("MomentumTransferStep", 0.04); alg.setProperty("PolarizationAnalysis", "PNR"); alg.setProperty("Pp", "1,1,2"); @@ -798,7 +798,7 @@ public: alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 15.0); alg.setProperty("ThetaIn", 10.0); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("MomentumTransferStep", 0.04); alg.setProperty("PolarizationAnalysis", "PNR"); alg.setProperty("Pp", "1,1,2"); @@ -841,7 +841,7 @@ public: alg.setProperty("ThetaIn", 10.0); alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 15.0); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("MomentumTransferStep", 0.04); alg.setProperty("PolarizationAnalysis", "ParameterFile"); alg.setPropertyValue("OutputWorkspace", "IvsQ"); @@ -909,7 +909,7 @@ public: alg.setProperty("WavelengthMin", 0.0000000001); alg.setProperty("WavelengthMax", 15.0); alg.setProperty("ThetaIn", 10.0); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("MomentumTransferStep", 0.04); alg.setPropertyValue("OutputWorkspace", "IvsQ"); alg.setPropertyValue("OutputWorkspaceBinned", "IvsQ_binned"); @@ -973,7 +973,7 @@ public: alg.setProperty("WavelengthMin", 0.0000000001); alg.setProperty("WavelengthMax", 15.0); alg.setProperty("ThetaIn", 10.0); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("MomentumTransferStep", 0.04); alg.setProperty("PolarizationAnalysis", "PNR"); alg.setProperty("Pp", "1"); @@ -1047,7 +1047,7 @@ public: alg.setProperty("WavelengthMin", 0.0000000001); alg.setProperty("WavelengthMax", 15.0); alg.setProperty("ThetaIn", 10.0); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("MomentumTransferStep", 0.04); alg.setPropertyValue("OutputWorkspace", "IvsQ"); alg.setPropertyValue("OutputWorkspaceBinned", "IvsQ_binned"); @@ -1086,7 +1086,7 @@ public: alg.setProperty("ThetaIn", 10.0); alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 15.0); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("MomentumTransferStep", 0.04); alg.setProperty("PolarizationAnalysis", "ParameterFile"); alg.setPropertyValue("OutputWorkspace", "IvsQ"); @@ -1130,15 +1130,16 @@ public: alg.setPropertyValue("InputWorkspace", name); alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 5.0); - alg.setProperty("ProcessingInstructions", "0"); + alg.setProperty("ProcessingInstructions", "1"); alg.setProperty("MomentumTransferStep", 0.04); alg.setProperty("PolarizationAnalysis", "ParameterFile"); alg.setPropertyValue("OutputWorkspace", "IvsQ"); alg.setPropertyValue("OutputWorkspaceBinned", "IvsQ_binned"); alg.setPropertyValue("OutputWorkspaceWavelength", "IvsLam"); - TS_ASSERT_THROWS_EQUALS( - alg.execute(), std::invalid_argument & e, std::string(e.what()), - "A detector is expected at spectrum 0, found a monitor"); + TS_ASSERT_THROWS_EQUALS(alg.execute(), std::invalid_argument & e, + std::string(e.what()), + "A detector is expected at workspace index 0 (Was " + "converted from specnum), found a monitor"); } void test_I0MonitorIndex_is_detector() { @@ -1154,7 +1155,7 @@ public: alg.setProperty("MonitorBackgroundWavelengthMin", 1.0); alg.setProperty("MonitorBackgroundWavelengthMax", 5.0); alg.setPropertyValue("I0MonitorIndex", "1"); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("MomentumTransferStep", 0.04); alg.setPropertyValue("OutputWorkspace", "IvsQ"); alg.setPropertyValue("OutputWorkspaceBinned", "IvsQ_binned"); @@ -1360,7 +1361,7 @@ public: alg.setProperty("MomentumTransferStep", 0.01); alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 15.0); - alg.setProperty("ProcessingInstructions", "1+2"); + alg.setProperty("ProcessingInstructions", "2+3"); alg.execute(); MatrixWorkspace_sptr out = alg.getProperty("OutputWorkspace"); TS_ASSERT_DELTA(out->y(0)[0], 4.5, 0.000001); @@ -1390,7 +1391,7 @@ public: alg.setProperty("MomentumTransferStep", 0.01); alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 15.0); - alg.setProperty("ProcessingInstructions", "1+2"); + alg.setProperty("ProcessingInstructions", "2+3"); alg.setProperty("FirstTransmissionRun", transWS); alg.execute(); MatrixWorkspace_sptr out = alg.getProperty("OutputWorkspace"); @@ -1422,7 +1423,7 @@ public: alg.setProperty("MomentumTransferStep", 0.01); alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 15.0); - alg.setProperty("ProcessingInstructions", "1+2"); + alg.setProperty("ProcessingInstructions", "2+3"); alg.setProperty("OutputWorkspaceWavelength", "IvsLam"); alg.setProperty("OutputWorkspace", "IvsQ"); alg.setProperty("OutputWorkspaceBinned", "IvsQb"); @@ -1450,7 +1451,7 @@ public: alg.setProperty("ThetaIn", 10.0); alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 15.0); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("MomentumTransferStep", 0.04); alg.setProperty("PolarizationAnalysis", "ParameterFile"); alg.setPropertyValue("OutputWorkspace", "IvsQ"); @@ -1485,7 +1486,7 @@ public: alg.setProperty("ThetaIn", 10.0); alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 15.0); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("MomentumTransferStep", 0.04); alg.setPropertyValue("OutputWorkspace", "IvsQ"); alg.setPropertyValue("OutputWorkspaceBinned", "IvsQ_binned"); @@ -1520,7 +1521,7 @@ public: alg.setProperty("ThetaIn", 10.0); alg.setProperty("WavelengthMin", 1.0); alg.setProperty("WavelengthMax", 15.0); - alg.setProperty("ProcessingInstructions", "1"); + alg.setProperty("ProcessingInstructions", "2"); alg.setProperty("MomentumTransferStep", 0.04); alg.setPropertyValue("OutputWorkspace", "IvsQ"); alg.setPropertyValue("OutputWorkspaceBinned", "IvsQ_binned"); diff --git a/Framework/Algorithms/test/SpecularReflectionPositionCorrect2Test.h b/Framework/Algorithms/test/SpecularReflectionPositionCorrect2Test.h index 4c34c2108e8b2f13583da07298baaa579c95472e..4b69ef0bec0f13f13e80a74d08ebf62e3d16a883 100644 --- a/Framework/Algorithms/test/SpecularReflectionPositionCorrect2Test.h +++ b/Framework/Algorithms/test/SpecularReflectionPositionCorrect2Test.h @@ -9,8 +9,10 @@ #include "MantidAPI/AlgorithmManager.h" #include "MantidAPI/FrameworkManager.h" +#include "MantidAPI/SpectrumInfo.h" #include "MantidAlgorithms/SpecularReflectionPositionCorrect2.h" #include "MantidGeometry/Instrument.h" +#include "MantidGeometry/Instrument/ReferenceFrame.h" #include "MantidTestHelpers/WorkspaceCreationHelper.h" #include <cxxtest/TestSuite.h> @@ -21,16 +23,21 @@ using namespace Mantid::Geometry; class SpecularReflectionPositionCorrect2Test : public CxxTest::TestSuite { private: + MatrixWorkspace_sptr m_d17WS; + MatrixWorkspace_sptr m_figaroWS; MatrixWorkspace_sptr m_interWS; // Initialise the algorithm and set the properties - void setupAlgorithm(SpecularReflectionPositionCorrect2 &alg, - const double twoTheta, const std::string &correctionType, - const std::string &detectorName, int detectorID = 0) { + static void setupAlgorithm(SpecularReflectionPositionCorrect2 &alg, + MatrixWorkspace_sptr &inWS, const double twoTheta, + const std::string &correctionType, + const std::string &detectorName, + int detectorID = 0) { if (!alg.isInitialized()) alg.initialize(); alg.setChild(true); - alg.setProperty("InputWorkspace", m_interWS); + alg.setRethrows(true); + alg.setProperty("InputWorkspace", inWS); alg.setProperty("TwoTheta", twoTheta); if (!correctionType.empty()) alg.setProperty("DetectorCorrectionType", correctionType); @@ -42,7 +49,7 @@ private: } // Run the algorithm and do some basic checks. Returns the output workspace. - MatrixWorkspace_const_sptr + static MatrixWorkspace_const_sptr runAlgorithm(SpecularReflectionPositionCorrect2 &alg) { TS_ASSERT_THROWS_NOTHING(alg.execute()); MatrixWorkspace_sptr outWS = alg.getProperty("OutputWorkspace"); @@ -50,6 +57,61 @@ private: return outWS; } + static void linearDetectorRotationWithFacing(MatrixWorkspace_sptr &inWS, + const double twoTheta) { + SpecularReflectionPositionCorrect2 alg; + setupAlgorithm(alg, inWS, twoTheta, "RotateAroundSample", "detector"); + alg.setProperty("DetectorFacesSample", true); + auto outWS = runAlgorithm(alg); + const auto &spectrumInfoOut = outWS->spectrumInfo(); + const auto nHisto = spectrumInfoOut.size(); + TS_ASSERT_DELTA(spectrumInfoOut.l2(0), spectrumInfoOut.l2(nHisto - 1), + 1e-10) + auto instrIn = inWS->getInstrument(); + auto detIn = instrIn->getComponentByName("detector"); + const auto posIn = detIn->getPos(); + const auto l2 = posIn.norm(); + auto instrOut = outWS->getInstrument(); + auto detOut = instrOut->getComponentByName("detector"); + const auto posOut = detOut->getPos(); + TS_ASSERT_DELTA(posOut.norm(), l2, 1e-10) + const auto thetaSignDir = instrIn->getReferenceFrame()->vecThetaSign(); + const auto horizontal = thetaSignDir.X() != 0. ? true : false; + const auto a = l2 * std::sin(twoTheta * M_PI / 180); + const auto x = horizontal ? a : 0.; + const auto y = horizontal ? 0. : a; + const auto z = l2 * std::cos(twoTheta * M_PI / 180); + TS_ASSERT_DELTA(posOut.X(), x, 1e-10) + TS_ASSERT_DELTA(posOut.Y(), y, 1e-10) + TS_ASSERT_DELTA(posOut.Z(), z, 1e-10) + } + + static void linearDetectorRotationWithFacingAndLinePosition( + MatrixWorkspace_sptr &inWS, const double twoTheta, const double linePos, + const double pixelSize) { + SpecularReflectionPositionCorrect2 alg; + setupAlgorithm(alg, inWS, twoTheta, "RotateAroundSample", "detector"); + alg.setProperty("DetectorFacesSample", true); + alg.setProperty("LinePosition", linePos); + alg.setProperty("PixelSize", pixelSize); + auto outWS = runAlgorithm(alg); + const auto &spectrumInfoOut = outWS->spectrumInfo(); + const auto nHisto = spectrumInfoOut.size(); + TS_ASSERT_DELTA(spectrumInfoOut.l2(0), spectrumInfoOut.l2(nHisto - 1), + 1e-10) + auto instrIn = inWS->getInstrument(); + auto detIn = instrIn->getComponentByName("detector"); + const auto posIn = detIn->getPos(); + const auto l2 = posIn.norm(); + auto instrOut = outWS->getInstrument(); + auto detOut = instrOut->getComponentByName("detector"); + const auto posOut = detOut->getPos(); + TS_ASSERT_DELTA(posOut.norm(), l2, 1e-10) + const auto lineTwoTheta = + spectrumInfoOut.twoTheta(static_cast<size_t>(linePos)); + TS_ASSERT_DELTA(lineTwoTheta * 180. / M_PI, twoTheta, 1e-10) + } + public: // This pair of boilerplate methods prevent the suite being created statically // This means the constructor isn't called when running other tests @@ -66,8 +128,27 @@ public: auto load = AlgorithmManager::Instance().create("LoadEmptyInstrument"); load->initialize(); load->setChild(true); + load->setProperty("InstrumentName", "D17"); + load->setPropertyValue("OutputWorkspace", "out"); + load->execute(); + m_d17WS = load->getProperty("OutputWorkspace"); + // Crop monitors. + auto crop = AlgorithmManager::Instance().create("CropWorkspace"); + crop->setChild(true); + crop->setProperty("InputWorkspace", m_d17WS); + crop->setPropertyValue("OutputWorkspace", "out"); + crop->setProperty("StartWorkspaceIndex", 0); + crop->setProperty("EndWorkspaceIndex", 255); + crop->execute(); + m_d17WS = crop->getProperty("OutputWorkspace"); + load->setProperty("InstrumentName", "Figaro"); + load->execute(); + m_figaroWS = load->getProperty("OutputWorkspace"); + crop->setProperty("InputWorkspace", m_figaroWS); + crop->setPropertyValue("OutputWorkspace", "out"); + crop->execute(); + m_figaroWS = crop->getProperty("OutputWorkspace"); load->setProperty("InstrumentName", "INTER"); - load->setPropertyValue("OutputWorkspace", "inter"); load->execute(); m_interWS = load->getProperty("OutputWorkspace"); } @@ -78,28 +159,6 @@ public: TS_ASSERT(alg.isInitialized()) } - void test_theta_is_mandatory() { - SpecularReflectionPositionCorrect2 alg; - alg.initialize(); - alg.setChild(true); - alg.setProperty("InputWorkspace", m_interWS); - alg.setProperty("DetectorComponentName", "point-detector"); - alg.setPropertyValue("OutputWorkspace", "test_out"); - TS_ASSERT_THROWS(alg.execute(), std::runtime_error &); - } - - void test_theta_bad_value() { - SpecularReflectionPositionCorrect2 alg; - alg.initialize(); - alg.setChild(true); - alg.setProperty("InputWorkspace", m_interWS); - alg.setProperty("DetectorComponentName", "point-detector"); - alg.setPropertyValue("OutputWorkspace", "test_out"); - TS_ASSERT_THROWS(alg.setProperty("TwoTheta", 0.0), std::invalid_argument &); - TS_ASSERT_THROWS(alg.setProperty("TwoTheta", 90.0), - std::invalid_argument &); - } - void test_detector_component_is_mandatory() { SpecularReflectionPositionCorrect2 alg; alg.initialize(); @@ -110,7 +169,7 @@ public: TS_ASSERT_THROWS_ANYTHING(alg.execute()); } - void test_detector_component_is_valid() { + void test_detector_id_is_valid() { SpecularReflectionPositionCorrect2 alg; alg.initialize(); alg.setChild(true); @@ -121,7 +180,7 @@ public: TS_ASSERT_THROWS_ANYTHING(alg.execute()); } - void test_detector_id_is_valid() { + void test_detector_name_is_valid() { SpecularReflectionPositionCorrect2 alg; alg.initialize(); alg.setChild(true); @@ -132,23 +191,11 @@ public: TS_ASSERT_THROWS_ANYTHING(alg.execute()); } - void test_sample_component_is_valid() { - SpecularReflectionPositionCorrect2 alg; - alg.initialize(); - alg.setChild(true); - alg.setProperty("InputWorkspace", m_interWS); - alg.setProperty("DetectorComponentName", "point-detector"); - alg.setProperty("SampleComponentName", "invalid-sample-name"); - alg.setProperty("TwoTheta", 1.4); - alg.setPropertyValue("OutputWorkspace", "test_out"); - TS_ASSERT_THROWS_ANYTHING(alg.execute()); - } - void test_correct_point_detector_vertical_shift_default() { // Omit the DetectorCorrectionType property to check that a vertical shift // is done by default SpecularReflectionPositionCorrect2 alg; - setupAlgorithm(alg, 1.4, "", "point-detector"); + setupAlgorithm(alg, m_interWS, 1.4, "", "point-detector"); MatrixWorkspace_const_sptr outWS = runAlgorithm(alg); auto instrIn = m_interWS->getInstrument(); @@ -170,7 +217,7 @@ public: // Omit the DetectorCorrectionType property to check that a vertical shift // is done by default SpecularReflectionPositionCorrect2 alg; - setupAlgorithm(alg, 1.4, "", "", 4); + setupAlgorithm(alg, m_interWS, 1.4, "", "", 4); MatrixWorkspace_const_sptr outWS = runAlgorithm(alg); auto instrIn = m_interWS->getInstrument(); @@ -190,7 +237,7 @@ public: void test_correct_point_detector_rotation() { SpecularReflectionPositionCorrect2 alg; - setupAlgorithm(alg, 1.4, "RotateAroundSample", "point-detector"); + setupAlgorithm(alg, m_interWS, 1.4, "RotateAroundSample", "point-detector"); MatrixWorkspace_const_sptr outWS = runAlgorithm(alg); auto instrIn = m_interWS->getInstrument(); @@ -211,7 +258,7 @@ public: void test_correct_point_detector_by_detid_rotation() { SpecularReflectionPositionCorrect2 alg; - setupAlgorithm(alg, 1.4, "RotateAroundSample", "", 4); + setupAlgorithm(alg, m_interWS, 1.4, "RotateAroundSample", "", 4); MatrixWorkspace_const_sptr outWS = runAlgorithm(alg); auto instrIn = m_interWS->getInstrument(); @@ -232,7 +279,7 @@ public: void test_correct_linear_detector_vertical_shift() { SpecularReflectionPositionCorrect2 alg; - setupAlgorithm(alg, 1.4, "VerticalShift", "linear-detector"); + setupAlgorithm(alg, m_interWS, 1.4, "VerticalShift", "linear-detector"); MatrixWorkspace_const_sptr outWS = runAlgorithm(alg); auto instrIn = m_interWS->getInstrument(); @@ -252,7 +299,8 @@ public: void test_correct_linear_detector_rotation() { SpecularReflectionPositionCorrect2 alg; - setupAlgorithm(alg, 1.4, "RotateAroundSample", "linear-detector"); + setupAlgorithm(alg, m_interWS, 1.4, "RotateAroundSample", + "linear-detector"); MatrixWorkspace_const_sptr outWS = runAlgorithm(alg); auto instrIn = m_interWS->getInstrument(); @@ -270,6 +318,67 @@ public: TS_ASSERT_DELTA(detOut.Z(), 3.162055, 1e-5); TS_ASSERT_DELTA(detOut.Y(), 0.07728, 1e-5); } + + void test_correct_horizontal_linear_detector_rotation_with_facing() { + constexpr double twoTheta{1.4}; + linearDetectorRotationWithFacing(m_d17WS, twoTheta); + linearDetectorRotationWithFacing(m_d17WS, -twoTheta); + } + + void test_correct_vertical_linear_detector_rotation_with_facing() { + constexpr double twoTheta{1.4}; + linearDetectorRotationWithFacing(m_figaroWS, twoTheta); + linearDetectorRotationWithFacing(m_figaroWS, -twoTheta); + } + + void test_correct_rotation_with_line_position() { + constexpr double twoTheta{1.4}; + constexpr double linePos{13.}; + constexpr double pixelSize{0.0012}; + linearDetectorRotationWithFacingAndLinePosition(m_figaroWS, twoTheta, + linePos, pixelSize); + } + + void test_correct_rotation_with_line_position_when_wsindices_run_like_d17() { + constexpr double twoTheta{1.4}; + constexpr double linePos{189.}; + constexpr double pixelSize{0.001195}; + linearDetectorRotationWithFacingAndLinePosition(m_d17WS, twoTheta, linePos, + pixelSize); + } + + void test_correct_direct_beam_calibration() { + constexpr double directLinePos{202}; + SpecularReflectionPositionCorrect2 alg; + alg.initialize(); + alg.setChild(true); + alg.setRethrows(true); + alg.setProperty("InputWorkspace", m_d17WS); + alg.setProperty("DetectorCorrectionType", "RotateAroundSample"); + alg.setProperty("DetectorComponentName", "detector"); + alg.setProperty("DetectorFacesSample", true); + alg.setProperty("DirectLinePosition", directLinePos); + alg.setProperty("PixelSize", 0.001195); + // Pretend the we have a separate direct beam workspace. + alg.setProperty("DirectLineWorkspace", m_d17WS); + alg.setPropertyValue("OutputWorkspace", "test_out"); + TS_ASSERT_THROWS_NOTHING(alg.execute()) + MatrixWorkspace_const_sptr outWS = alg.getProperty("OutputWorkspace"); + TS_ASSERT(outWS); + const auto &spectrumInfoIn = m_d17WS->spectrumInfo(); + const auto twoThetaIn = + spectrumInfoIn.twoTheta(static_cast<size_t>(directLinePos)); + auto instrOut = outWS->getInstrument(); + auto detOut = instrOut->getComponentByName("detector"); + const auto posOut = detOut->getPos(); + const auto l2 = posOut.norm(); + const auto x = l2 * std::sin(twoThetaIn); + const auto y = 0.; + const auto z = l2 * std::cos(twoThetaIn); + TS_ASSERT_DELTA(posOut.X(), x, 1e-10) + TS_ASSERT_DELTA(posOut.Y(), y, 1e-10) + TS_ASSERT_DELTA(posOut.Z(), z, 1e-10) + } }; #endif /* MANTID_ALGORITHMS_SPECULARREFLECTIONPOSITIONCORRECT2TEST_H_ */ diff --git a/Framework/Algorithms/test/Stitch1DManyTest.h b/Framework/Algorithms/test/Stitch1DManyTest.h index 90c3c4ca64d223ac47896c486670631d1e63c21f..56148a5e3453a3177f9990605d5855cf85149611 100644 --- a/Framework/Algorithms/test/Stitch1DManyTest.h +++ b/Framework/Algorithms/test/Stitch1DManyTest.h @@ -370,6 +370,10 @@ public: TS_ASSERT_EQUALS(stitched->y(0).rawData(), stitched2->y(0).rawData()); TS_ASSERT_EQUALS(stitched->e(0).rawData(), stitched2->e(0).rawData()); + // Check workspaces in ADS + auto wsInADS = AnalysisDataService::Instance().getObjectNames(); + // In ADS: ws1, ws2 + TS_ASSERT_EQUALS(wsInADS.size(), 2) // Remove workspaces from ADS AnalysisDataService::Instance().clear(); } @@ -425,6 +429,10 @@ public: TS_ASSERT_DELTA(scales.front(), 0.9090, 0.0001); TS_ASSERT_DELTA(scales.back(), 0.6666, 0.0001); + // Check workspaces in ADS + auto wsInADS = AnalysisDataService::Instance().getObjectNames(); + // In ADS: ws1, ws2, ws3 + TS_ASSERT_EQUALS(wsInADS.size(), 3) // Remove workspaces from ADS AnalysisDataService::Instance().clear(); } @@ -443,6 +451,12 @@ public: alg.setProperty("OutputWorkspace", "outws"); TS_ASSERT_THROWS_NOTHING(alg.execute()); TS_ASSERT(alg.isExecuted()); + // Check workspaces in ADS + auto wsInADS = AnalysisDataService::Instance().getObjectNames(); + // In ADS: ws1, ws2, ws3 + TS_ASSERT_EQUALS(wsInADS.size(), 3) + // Remove workspaces from ADS + AnalysisDataService::Instance().clear(); } void test_three_workspaces_single_scale_factor_given() { @@ -494,6 +508,9 @@ public: TS_ASSERT_EQUALS(scales[0], 0.5); TS_ASSERT_EQUALS(scales[1], 0.5); + auto wsInADS = AnalysisDataService::Instance().getObjectNames(); + // In ADS: ws1, ws2, ws3 + TS_ASSERT_EQUALS(wsInADS.size(), 3) // Remove workspaces from ADS AnalysisDataService::Instance().clear(); } @@ -548,6 +565,10 @@ public: TS_ASSERT_EQUALS(scales[0], 0.5); TS_ASSERT_EQUALS(scales[1], 0.7); + // Check workspaces in ADS + auto wsInADS = AnalysisDataService::Instance().getObjectNames(); + // In ADS: ws1, ws2, ws3 + TS_ASSERT_EQUALS(wsInADS.size(), 3) // Remove workspaces from ADS AnalysisDataService::Instance().clear(); } @@ -569,7 +590,6 @@ public: alg.setProperty("EndOverlaps", "1.1"); alg.setProperty("OutputWorkspace", "outws"); TS_ASSERT_THROWS(alg.execute(), std::runtime_error); - AnalysisDataService::Instance().clear(); } @@ -635,6 +655,12 @@ public: TS_ASSERT_DELTA(scales.front(), 0.9090, 0.0001); TS_ASSERT_DELTA(scales.back(), 0.6666, 0.0001); + // Check workspaces in ADS + auto wsInADS = AnalysisDataService::Instance().getObjectNames(); + // In ADS: group1, group2, group3, ws1, ws2, ws3 and + TS_ASSERT_EQUALS(wsInADS.size(), 8) + TS_ASSERT_EQUALS(wsInADS[3], "outws") + TS_ASSERT_EQUALS(wsInADS[4], "outws_ws1_ws2_ws3") // Clear the ADS AnalysisDataService::Instance().clear(); } @@ -651,9 +677,8 @@ public: createUniformWorkspace(0.8, 0.1, 1.1, 2.1, "ws3"); createUniformWorkspace(0.8, 0.1, 1.6, 2.6, "ws4"); doGroupWorkspaces("ws3, ws4", "group2"); - // ws1 will be stitched with ws3 - // ws2 will be stitched with ws4 + // will produce a group outws containing outws_ws1_ws3, outws_ws2_ws4 Stitch1DMany alg; alg.setChild(true); alg.initialize(); @@ -720,6 +745,13 @@ public: TS_ASSERT_DELTA(scales.front(), 0.9090, 0.0001); TS_ASSERT_DELTA(scales.back(), 0.9375, 0.0001); + // Check workspaces in ADS + auto wsInADS = AnalysisDataService::Instance().getObjectNames(); + // In ADS: group1, group2, ws1, ws2, ws3, ws4, and + TS_ASSERT_EQUALS(wsInADS.size(), 9) + TS_ASSERT_EQUALS(wsInADS[2], "outws") + TS_ASSERT_EQUALS(wsInADS[3], "outws_ws1_ws3") + TS_ASSERT_EQUALS(wsInADS[4], "outws_ws2_ws4") // Clear the ADS AnalysisDataService::Instance().clear(); } @@ -736,9 +768,8 @@ public: createUniformWorkspace(0.8, 0.1, 1.1, 2.1, "ws3"); createUniformWorkspace(0.8, 0.1, 1.6, 2.6, "ws4"); doGroupWorkspaces("ws3, ws4", "group2"); - // ws1 will be stitched with ws3 - // ws2 will be stitched with ws4 + // Will produce a group outws containing outws_ws1_ws3, outws_ws2_ws4 Stitch1DMany alg; alg.setChild(true); alg.initialize(); @@ -811,6 +842,13 @@ public: TS_ASSERT_DELTA(scales.front(), 0.5000, 0.0001); TS_ASSERT_DELTA(scales.back(), 0.5000, 0.0001); + // Check workspaces in ADS + auto wsInADS = AnalysisDataService::Instance().getObjectNames(); + // In ADS: group1, group2, ws1, ws2, ws3, ws4 and + TS_ASSERT_EQUALS(wsInADS.size(), 9) + TS_ASSERT_EQUALS(wsInADS[2], "outws") + TS_ASSERT_EQUALS(wsInADS[3], "outws_ws1_ws3") + TS_ASSERT_EQUALS(wsInADS[4], "outws_ws2_ws4") // Clear the ADS AnalysisDataService::Instance().clear(); } @@ -831,9 +869,9 @@ public: createUniformWorkspace(1.6, 0.1, 1.5, 2.5, "ws5"); createUniformWorkspace(1.6, 0.1, 1.6, 3.0, "ws6"); doGroupWorkspaces("ws5, ws6", "group3"); - // ws1 will be stitched with ws3 and ws5 - // ws2 will be stitched with ws4 and ws6 + // Will produce a group outws containing outws_ws1_ws3_ws5, + // outws_ws2_ws4_ws6 Stitch1DMany alg; alg.setChild(true); alg.initialize(); @@ -915,6 +953,13 @@ public: TS_ASSERT_DELTA(scales[2], 0.5, 0.0001); TS_ASSERT_DELTA(scales[3], 0.7, 0.0001); + // Check workspaces in ADS + auto wsInADS = AnalysisDataService::Instance().getObjectNames(); + // In ADS: group1, group2, grou3, ws1, ws2, ws3, ws4, ws5, ws6 and + TS_ASSERT_EQUALS(wsInADS.size(), 12) + TS_ASSERT_EQUALS(wsInADS[3], "outws") + TS_ASSERT_EQUALS(wsInADS[4], "outws_ws1_ws3_ws5") + TS_ASSERT_EQUALS(wsInADS[5], "outws_ws2_ws4_ws6") // Clear the ADS AnalysisDataService::Instance().clear(); } @@ -935,9 +980,9 @@ public: createUniformWorkspace(1.6, 0.1, 1.5, 2.5, "ws5"); createUniformWorkspace(1.6, 0.1, 1.6, 3.0, "ws6"); doGroupWorkspaces("ws5, ws6", "group3"); - // ws1 will be stitched with ws3 and ws5 - // ws2 will be stitched with ws4 and ws6 + // Will produce a group outws containing outws_ws1_ws3_ws5 and + // outws_ws2_ws4_ws6 Stitch1DMany alg; alg.setChild(true); alg.initialize(); @@ -1022,7 +1067,110 @@ public: TS_ASSERT_DELTA(scales[1], 0.6249, 0.0001); TS_ASSERT_DELTA(scales[2], 0.9375, 0.0001); TS_ASSERT_DELTA(scales[3], 0.6249, 0.0001); + // Check workspaces in ADS + auto wsInADS = AnalysisDataService::Instance().getObjectNames(); + // In ADS: group1, group2, grou3, ws1, ws2, ws3, ws4, ws5, ws6 and + TS_ASSERT_EQUALS(wsInADS.size(), 12) + TS_ASSERT_EQUALS(wsInADS[3], "outws") + TS_ASSERT_EQUALS(wsInADS[4], "outws_ws1_ws3_ws5") + TS_ASSERT_EQUALS(wsInADS[5], "outws_ws2_ws4_ws6") + // Clear the ADS + AnalysisDataService::Instance().clear(); + } + + void test_groups_containing_single_workspaces_scale_factor_from_period() { + // Three groups with one matrix workspaces each. + // Each matrix workspace has two spectra. + + // First group + createUniformWorkspace(0.1, 0.1, 1., 2., "ws1"); + doGroupWorkspaces("ws1", "group1"); + // Second group + createUniformWorkspace(0.8, 0.1, 1.1, 2.1, "ws3"); + doGroupWorkspaces("ws3", "group2"); + // Third group + createUniformWorkspace(1.6, 0.1, 1.5, 2.5, "ws5"); + doGroupWorkspaces("ws5", "group3"); + // ws1 will be stitched with ws3 and ws5 + // Perid 2 is out of range, must be one like tested below + Stitch1DMany alg0; + alg0.setChild(true); + alg0.initialize(); + alg0.setRethrows(true); + TS_ASSERT_THROWS_NOTHING( + alg0.setProperty("InputWorkspaces", "group1, group2, group3")) + TS_ASSERT_THROWS_NOTHING(alg0.setProperty("Params", "0.1, 0.1, 2.6")) + TS_ASSERT_THROWS_NOTHING(alg0.setProperty("StartOverlaps", "0.8, 1.6")) + TS_ASSERT_THROWS_NOTHING(alg0.setProperty("EndOverlaps", "1.1, 1.9")) + TS_ASSERT_THROWS_NOTHING(alg0.setProperty("UseManualScaleFactors", "1")) + TS_ASSERT_THROWS_NOTHING(alg0.setProperty("ScaleFactorFromPeriod", 2)) + TS_ASSERT_THROWS_NOTHING(alg0.setProperty("OutputWorkspace", "outws")) + TS_ASSERT_THROWS(alg0.execute(), std::runtime_error); + TS_ASSERT(!alg0.isExecuted()) + + // Will produce a group outws containing a single workspace named + // outws_ws1_ws3_ws5 + Stitch1DMany alg; + alg.setChild(true); + alg.initialize(); + alg.setRethrows(true); + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("InputWorkspaces", "group1, group2, group3")) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("Params", "0.1, 0.1, 2.6")) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("StartOverlaps", "0.8, 1.6")) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("EndOverlaps", "1.1, 1.9")) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("UseManualScaleFactors", "1")) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("ScaleFactorFromPeriod", 1)) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("OutputWorkspace", "outws")) + TS_ASSERT_THROWS_NOTHING(alg.execute()) + TS_ASSERT(alg.isExecuted()) + + // By keeping ManualScaleFactors empty (default value) it allows workspaces + // in other periods to be scaled by scale factors from a specific period. + // Periods 0 and 2 workspaces will be scaled by scale factors from period 1. + + // Test output ws + Workspace_sptr outws = alg.getProperty("OutputWorkspace"); + TS_ASSERT(outws) + auto group = boost::dynamic_pointer_cast<WorkspaceGroup>(outws); + TS_ASSERT_EQUALS(group->getNumberOfEntries(), 1) + + // First item in the output group + auto stitched = + boost::dynamic_pointer_cast<MatrixWorkspace>(group->getItem(0)); + TS_ASSERT_EQUALS(stitched->getNumberHistograms(), 2) + TS_ASSERT_EQUALS(stitched->blocksize(), 25) + // First spectrum, Y values + TS_ASSERT_DELTA(stitched->y(0)[0], 0.99999, 1.e-5) + TS_ASSERT_DELTA(stitched->y(0)[9], 0.99999, 1.e-5) + TS_ASSERT_DELTA(stitched->y(0)[16], 0.74860, 1.e-5) + TS_ASSERT_DELTA(stitched->y(0)[24], 0.66666, 1.e-5) + // Second spectrum, Y values + TS_ASSERT_DELTA(stitched->y(1)[0], 2., 1.e-5) + TS_ASSERT_DELTA(stitched->y(1)[9], 1.95132, 1.e-5) + TS_ASSERT_DELTA(stitched->y(1)[16], 1.28787, 1.e-5) + TS_ASSERT_DELTA(stitched->y(1)[24], 1.11111, 1.e-5) + // First spectrum, E values + TS_ASSERT_DELTA(stitched->e(0)[0], 1., 1.e-5) + TS_ASSERT_DELTA(stitched->e(0)[9], 0.69006, 1.e-5) + TS_ASSERT_DELTA(stitched->e(0)[16], 0.47271, 1.e-5) + TS_ASSERT_DELTA(stitched->e(0)[24], 0.54433, 1.e-5) + // Second spectrum, E values + TS_ASSERT_DELTA(stitched->e(1)[0], 1.414213, 1.e-5) + TS_ASSERT_DELTA(stitched->e(1)[9], 0.963952, 1.e-5) + TS_ASSERT_DELTA(stitched->e(1)[16], 0.62003, 1.e-5) + TS_ASSERT_DELTA(stitched->e(1)[24], 0.702728, 1.e-5) + + // Test out scale factors + std::vector<double> scales = alg.getProperty("OutScaleFactors"); + TS_ASSERT_EQUALS(scales.size(), 2); + TS_ASSERT_DELTA(scales[0], 0.90909, 1.e-5) + TS_ASSERT_DELTA(scales[1], 0.44444, 1.e-5) + // Check workspaces in ADS + auto wsInADS = AnalysisDataService::Instance().getObjectNames(); + // In ADS: group1, group2, group3, ws1, ws3, ws5 + TS_ASSERT_EQUALS(wsInADS.size(), 8) // Clear the ADS AnalysisDataService::Instance().clear(); } @@ -1055,6 +1203,11 @@ public: TS_ASSERT_EQUALS(histNames[1], "CreateWorkspace"); TS_ASSERT_EQUALS(histNames[2], "Stitch1DMany"); + // Check workspaces in ADS + auto wsInADS = AnalysisDataService::Instance().getObjectNames(); + // In ADS: outws, ws1, ws2 + TS_ASSERT_EQUALS(wsInADS.size(), 3) + TS_ASSERT_EQUALS(wsInADS[0], "outws") // Remove workspaces from ADS AnalysisDataService::Instance().clear(); } @@ -1098,7 +1251,7 @@ public: // Test the algorithm histories std::vector<std::string> histNames = getHistory(stitched); - TS_ASSERT_EQUALS(histNames.size(), 8) + TS_ASSERT_EQUALS(histNames.size(), 7) TS_ASSERT_EQUALS(histNames[0], "CreateWorkspace"); TS_ASSERT_EQUALS(histNames[1], "CreateWorkspace"); TS_ASSERT_EQUALS(histNames[2], "GroupWorkspaces"); @@ -1106,8 +1259,11 @@ public: TS_ASSERT_EQUALS(histNames[4], "CreateWorkspace"); TS_ASSERT_EQUALS(histNames[5], "GroupWorkspaces"); TS_ASSERT_EQUALS(histNames[6], "Stitch1DMany"); - TS_ASSERT_EQUALS(histNames[7], "Stitch1DMany"); + // Check workspaces in ADS + auto wsInADS = AnalysisDataService::Instance().getObjectNames(); + // In ADS: group1, group2, outws_ws1_ws3, outws_ws2_ws4, ws1, ws2, ws3, ws4 + TS_ASSERT_EQUALS(wsInADS.size(), 9) // Remove workspaces from ADS AnalysisDataService::Instance().clear(); } @@ -1166,6 +1322,15 @@ public: TS_ASSERT_EQUALS(histNames[8], "GroupWorkspaces"); TS_ASSERT_EQUALS(histNames[9], "Stitch1DMany"); + // Check workspaces in ADS + auto wsInADS = AnalysisDataService::Instance().getObjectNames(); + // In ADS: group1, group2, group3, + // ws1, ws2, ws3, ws4, ws5, ws6 and + TS_ASSERT_EQUALS(wsInADS.size(), 12) + TS_ASSERT_EQUALS(wsInADS[3], "outws") + TS_ASSERT_EQUALS(wsInADS[4], "outws_ws1_ws3_ws5") + TS_ASSERT_EQUALS(wsInADS[5], "outws_ws2_ws4_ws6") + TS_ASSERT_EQUALS(wsInADS.size(), 12) // Clear the ADS AnalysisDataService::Instance().clear(); } @@ -1219,6 +1384,10 @@ public: TS_ASSERT_EQUALS(stitched->y(0).rawData(), y_values); const std::vector<double> dx_values{2., 1.34, 1., 1.4, 3.589, 3.1}; TS_ASSERT_EQUALS(stitched->dx(0).rawData(), dx_values); + // Check workspaces in ADS + auto wsInADS = AnalysisDataService::Instance().getObjectNames(); + // In ADS: ws1, ws2 + TS_ASSERT_EQUALS(wsInADS.size(), 2) Mantid::API::AnalysisDataService::Instance().clear(); } }; diff --git a/Framework/Beamline/inc/MantidBeamline/ComponentInfo.h b/Framework/Beamline/inc/MantidBeamline/ComponentInfo.h index 6980ecf0aca6f9a99f691416c8da037a45e8b4ed..e8673c79f259f374dfaad48750896d040da33fc1 100644 --- a/Framework/Beamline/inc/MantidBeamline/ComponentInfo.h +++ b/Framework/Beamline/inc/MantidBeamline/ComponentInfo.h @@ -50,9 +50,8 @@ private: const int64_t m_sourceIndex = -1; const int64_t m_sampleIndex = -1; DetectorInfo *m_detectorInfo; // Geometry::DetectorInfo is the owner. - size_t m_scanCounts = 1; - Kernel::cow_ptr<std::vector<std::pair<int64_t, int64_t>>> m_scanIntervals{ - nullptr}; + /// The default initialisation is a single interval, i.e. no scan + std::vector<std::pair<int64_t, int64_t>> m_scanIntervals{{0, 1}}; /// For (component index, time index) -> linear index conversions Kernel::cow_ptr<std::vector<std::vector<size_t>>> m_indexMap{nullptr}; /// For linear index -> (detector index, time index) conversions @@ -61,12 +60,12 @@ private: size_t linearIndex(const std::pair<size_t, size_t> &index) const; void initScanIntervals(); void checkNoTimeDependence() const; - std::vector<bool> buildMergeIndicesSync(const ComponentInfo &other) const; + std::vector<bool> buildMergeIndices(const ComponentInfo &other) const; void checkSizes(const ComponentInfo &other) const; void initIndices(); void checkIdenticalIntervals(const ComponentInfo &other, - const size_t linearIndexOther, - const size_t linearIndexThis) const; + const std::pair<size_t, size_t> indexOther, + const std::pair<size_t, size_t> indexThis) const; void checkSpecialIndices(size_t componentIndex) const; size_t nonDetectorSize() const; /// Copy constructor is private because of the way DetectorInfo stored @@ -143,11 +142,10 @@ public: const Eigen::Vector3d &scaleFactor); ComponentType componentType(const size_t componentIndex) const; - size_t scanCount(const size_t index) const; + size_t scanCount() const; size_t scanSize() const; bool isScanning() const; - std::pair<int64_t, int64_t> - scanInterval(const std::pair<size_t, size_t> &index) const; + const std::vector<std::pair<int64_t, int64_t>> &scanIntervals() const; void setScanInterval(const std::pair<int64_t, int64_t> &interval); void merge(const ComponentInfo &other); diff --git a/Framework/Beamline/inc/MantidBeamline/DetectorInfo.h b/Framework/Beamline/inc/MantidBeamline/DetectorInfo.h index 09eb7571a97227a6a665494e8da29208b07a76af..195c63623f08b4c5f39307310a091357bb4b020b 100644 --- a/Framework/Beamline/inc/MantidBeamline/DetectorInfo.h +++ b/Framework/Beamline/inc/MantidBeamline/DetectorInfo.h @@ -59,9 +59,7 @@ public: bool isEquivalent(const DetectorInfo &other) const; size_t size() const; - size_t scanSize() const; bool isScanning() const; - bool isSyncScan() const; bool isMonitor(const size_t index) const; bool isMonitor(const std::pair<size_t, size_t> &index) const; @@ -80,32 +78,29 @@ public: void setRotation(const std::pair<size_t, size_t> &index, const Eigen::Quaterniond &rotation); - size_t scanCount(const size_t index) const; - std::pair<int64_t, int64_t> - scanInterval(const std::pair<size_t, size_t> &index) const; - void setScanInterval(const size_t index, - const std::pair<int64_t, int64_t> &interval); - void setScanInterval(const std::pair<int64_t, int64_t> &interval); + size_t scanCount() const; + const std::vector<std::pair<int64_t, int64_t>> scanIntervals() const; - void merge(const DetectorInfo &other); void setComponentInfo(ComponentInfo *componentInfo); bool hasComponentInfo() const; double l1() const; Eigen::Vector3d sourcePosition() const; Eigen::Vector3d samplePosition() const; + /** The `merge()` operation was made private in `DetectorInfo`, and only + * accessible through `ComponentInfo` (via this `friend` declaration) + * because we need to avoid merging `DetectorInfo` without merging + * `ComponentInfo`, since that would effectively let us create a non-sync + * scan. Otherwise we cannot provide scanning-related methods in + * `ComponentInfo` without component index. + */ + friend class ComponentInfo; + private: size_t linearIndex(const std::pair<size_t, size_t> &index) const; void checkNoTimeDependence() const; - void initScanCounts(); - void initScanIntervals(); - void initIndices(); - std::vector<bool> buildMergeIndices(const DetectorInfo &other) const; - std::vector<bool> buildMergeSyncScanIndices(const DetectorInfo &other) const; void checkSizes(const DetectorInfo &other) const; - void checkIdenticalIntervals(const DetectorInfo &other, const size_t index1, - const size_t index2) const; - bool m_isSyncScan{true}; + void merge(const DetectorInfo &other, const std::vector<bool> &merge); Kernel::cow_ptr<std::vector<bool>> m_isMonitor{nullptr}; Kernel::cow_ptr<std::vector<bool>> m_isMasked{nullptr}; @@ -114,13 +109,6 @@ private: Eigen::aligned_allocator<Eigen::Quaterniond>>> m_rotations{nullptr}; - Kernel::cow_ptr<std::vector<size_t>> m_scanCounts{nullptr}; - Kernel::cow_ptr<std::vector<std::pair<int64_t, int64_t>>> m_scanIntervals{ - nullptr}; - /// For (detector index, time index) -> linear index conversions - Kernel::cow_ptr<std::vector<std::vector<size_t>>> m_indexMap{nullptr}; - /// For linear index -> (detector index, time index) conversions - Kernel::cow_ptr<std::vector<std::pair<size_t, size_t>>> m_indices{nullptr}; ComponentInfo *m_componentInfo = nullptr; // Geometry::ComponentInfo owner }; @@ -219,9 +207,8 @@ DetectorInfo::linearIndex(const std::pair<size_t, size_t> &index) const { // so even in the time dependent case no translation is necessary. if (index.second == 0) return index.first; - if (m_isSyncScan) + else return index.first + size() * index.second; - return (*m_indexMap)[index.first][index.second]; } } // namespace Beamline diff --git a/Framework/Beamline/src/ComponentInfo.cpp b/Framework/Beamline/src/ComponentInfo.cpp index cbdf868ceda0ab58591dadfc8e05de2820b978e4..d643b88fabe5c8092f6b92d635d8831839335431 100644 --- a/Framework/Beamline/src/ComponentInfo.cpp +++ b/Framework/Beamline/src/ComponentInfo.cpp @@ -470,6 +470,11 @@ void ComponentInfo::setDetectorInfo(DetectorInfo *detectorInfo) { "input of same size as size of DetectorInfo"); } m_detectorInfo = detectorInfo; + /* We need to check here whether m_detectorInfo actually exists, since in the + * case of cloneWithoutDetectorInfo(), the detectorInfo is a null pointer. + */ + if (m_detectorInfo) + m_detectorInfo->setComponentInfo(this); } bool ComponentInfo::hasSource() const { return m_sourceIndex >= 0; } @@ -567,25 +572,8 @@ ComponentType ComponentInfo::componentType(const size_t componentIndex) const { } } -/** - * Get the number of scans for component index - * @param index : Component Index - * @return Number of scans for component index - */ -size_t ComponentInfo::scanCount(const size_t index) const { - if (m_detectorInfo && isDetector(index)) - return m_detectorInfo->scanCount(index); - else { - return m_scanCounts; - } -} - -size_t ComponentInfo::scanSize() const { - const auto detectorScanSize = m_detectorInfo ? m_detectorInfo->scanSize() : 0; - if (!m_positions) - return 0 + detectorScanSize; - return m_positions->size() + detectorScanSize; -} +/// Get the number of scans +size_t ComponentInfo::scanCount() const { return m_scanIntervals.size(); } bool ComponentInfo::isScanning() const { if (m_detectorInfo && m_detectorInfo->isScanning()) @@ -604,18 +592,10 @@ void ComponentInfo::checkNoTimeDependence() const { "beamline has time-dependent (moving) components."); } -/** - * Retrieve the scan interval for a component at a time index - * @param index component index, time index pair - * @return offset interval times since epoch - */ -std::pair<int64_t, int64_t> -ComponentInfo::scanInterval(const std::pair<size_t, size_t> &index) const { - if (m_detectorInfo && isDetector(index.first)) - return m_detectorInfo->scanInterval(index); - if (!m_scanIntervals) - return {0, 0}; - return (*m_scanIntervals)[index.second]; +/// Get the scan intervals +const std::vector<std::pair<int64_t, int64_t>> & +ComponentInfo::scanIntervals() const { + return m_scanIntervals; } void ComponentInfo::checkSpecialIndices(size_t componentIndex) const { @@ -634,17 +614,11 @@ void ComponentInfo::setScanInterval( // Enforces setting scan intervals BEFORE time indexed positions and rotations checkNoTimeDependence(); checkScanInterval(interval); - if (!m_scanIntervals) { - initScanIntervals(); - } - m_scanIntervals.access()[0] = interval; - if (m_detectorInfo) { - m_detectorInfo->setScanInterval(interval); - } + m_scanIntervals[0] = interval; } /** -Merges the contents of other ComponentInfo into this. The assumption is that +Merges the contents of other `ComponentInfo` into this. The assumption is that this has no time dependence prior to this operation. * * Scan intervals in both other and this must be set. Intervals must be @@ -656,49 +630,48 @@ this has no time dependence prior to this operation. * of time indices added from `other` is preserved. If the interval for a time * index in `other` is identical to a corresponding interval in `this`, it is * ignored, i.e., no time index is added. + * + * This function also conducts the merging of the `DetectorInfo` to ensute that + * there is no asynchronicity in the scans. **/ void ComponentInfo::merge(const ComponentInfo &other) { - checkNoTimeDependence(); - const auto &toMerge = buildMergeIndicesSync(other); - for (size_t timeIndex = 0; timeIndex < other.m_scanIntervals->size(); + const auto &toMerge = buildMergeIndices(other); + // Merging the detectorInfo has to be done before we update scanIntervals + m_detectorInfo->merge(*other.m_detectorInfo, toMerge); + for (size_t timeIndex = 0; timeIndex < other.m_scanIntervals.size(); ++timeIndex) { if (!toMerge[timeIndex]) continue; - auto &scanIntervals = m_scanIntervals.access(); auto &positions = m_positions.access(); auto &rotations = m_rotations.access(); - scanIntervals.push_back((*other.m_scanIntervals)[timeIndex]); + m_scanIntervals.push_back(other.m_scanIntervals[timeIndex]); const size_t indexStart = other.linearIndex({0, timeIndex}); size_t indexEnd = indexStart + nonDetectorSize(); positions.insert(positions.end(), other.m_positions->begin() + indexStart, other.m_positions->begin() + indexEnd); rotations.insert(rotations.end(), other.m_rotations->begin() + indexStart, other.m_rotations->begin() + indexEnd); - ++m_scanCounts; } - - m_detectorInfo->merge(*other.m_detectorInfo); } std::vector<bool> -ComponentInfo::buildMergeIndicesSync(const ComponentInfo &other) const { +ComponentInfo::buildMergeIndices(const ComponentInfo &other) const { checkSizes(other); - std::vector<bool> merge(other.m_scanIntervals->size(), true); - - for (size_t t1 = 0; t1 < other.m_scanIntervals->size(); ++t1) { - for (size_t t2 = 0; t2 < m_scanIntervals->size(); ++t2) { - const auto &interval1 = (*other.m_scanIntervals)[t1]; - const auto &interval2 = (*m_scanIntervals)[t2]; + std::vector<bool> merge(other.m_scanIntervals.size(), true); + for (size_t t1 = 0; t1 < other.m_scanIntervals.size(); ++t1) { + for (size_t t2 = 0; t2 < m_scanIntervals.size(); ++t2) { + const auto interval1 = other.m_scanIntervals[t1]; + const auto interval2 = m_scanIntervals[t2]; if (interval1 == interval2) { - for (size_t compIndex = 0; compIndex < nonDetectorSize(); ++compIndex) { - const size_t linearIndex1 = other.linearIndex({compIndex, t1}); - const size_t linearIndex2 = linearIndex({compIndex, t2}); - checkIdenticalIntervals(other, linearIndex1, linearIndex2); + for (size_t compIndex = 0; compIndex < size(); ++compIndex) { + checkIdenticalIntervals(other, + std::pair<size_t, size_t>(compIndex, t1), + std::pair<size_t, size_t>(compIndex, t2)); } merge[t1] = false; } else if ((interval1.first < interval2.second) && (interval1.second > interval2.first)) { - failMerge("sync scan intervals overlap but not identical"); + failMerge("scan intervals overlap but not identical"); } } } @@ -708,17 +681,14 @@ ComponentInfo::buildMergeIndicesSync(const ComponentInfo &other) const { void ComponentInfo::checkSizes(const ComponentInfo &other) const { if (size() != other.size()) failMerge("size mismatch"); - if (!m_scanIntervals || !other.m_scanIntervals) - failMerge("scan intervals not defined"); } void ComponentInfo::checkIdenticalIntervals( - const ComponentInfo &other, const size_t linearIndexOther, - const size_t linearIndexThis) const { - if ((*m_positions)[linearIndexThis] != (*other.m_positions)[linearIndexOther]) + const ComponentInfo &other, const std::pair<size_t, size_t> indexOther, + const std::pair<size_t, size_t> indexThis) const { + if (this->position(indexThis) != other.position(indexOther)) failMerge("matching scan interval but positions differ"); - if ((*m_rotations)[linearIndexThis].coeffs() != - (*other.m_rotations)[linearIndexOther].coeffs()) + if (this->rotation(indexThis).coeffs() != other.rotation(indexOther).coeffs()) failMerge("matching scan interval but rotations differ"); } @@ -736,11 +706,5 @@ size_t ComponentInfo::nonDetectorSize() const { return 0; } -void ComponentInfo::initScanIntervals() { - checkNoTimeDependence(); - m_scanIntervals = Kernel::make_cow<std::vector<std::pair<int64_t, int64_t>>>( - 1, std::pair<int64_t, int64_t>{0, 1}); -} - } // namespace Beamline } // namespace Mantid diff --git a/Framework/Beamline/src/DetectorInfo.cpp b/Framework/Beamline/src/DetectorInfo.cpp index d1d696b882f8ae23fc153bca7250980d87f7bff7..12b455055fb7ca517c4a0ff28c3dd0b35ccb74bb 100644 --- a/Framework/Beamline/src/DetectorInfo.cpp +++ b/Framework/Beamline/src/DetectorInfo.cpp @@ -65,14 +65,10 @@ bool DetectorInfo::isEquivalent(const DetectorInfo &other) const { if (!(m_isMasked == other.m_isMasked) && (*m_isMasked != *other.m_isMasked)) return false; - // Scanning related fields. Not testing m_scanCounts and m_indexMap since + // Scanning related fields. Not testing m_indexMap since // those just are internally derived from m_indices. - if (m_scanIntervals && other.m_scanIntervals && - !(m_scanIntervals == other.m_scanIntervals) && - (*m_scanIntervals != *other.m_scanIntervals)) - return false; - if (m_indices && other.m_indices && !(m_indices == other.m_indices) && - (*m_indices != *other.m_indices)) + if (this->hasComponentInfo() && + (this->scanIntervals() != other.scanIntervals())) return false; // Positions: Absolute difference matter, so comparison is not relative. @@ -105,23 +101,6 @@ bool DetectorInfo::isEquivalent(const DetectorInfo &other) const { return true; } -/** Returns the number of sum of the scan intervals for every detector in the - *instrument. - * - * If a detector is moving, i.e., has more than one associated position, every - *position is counted. */ -size_t DetectorInfo::scanSize() const { - if (!m_positions) - return 0; - return m_positions->size(); -} - -/** - * Returns true if all of the detectors all have the same scan interval. Will - * return false if DetectorInfo is not scanning. - */ -bool DetectorInfo::isSyncScan() const { return isScanning() && m_isSyncScan; } - /// Returns true if the detector with given detector index is a monitor. bool DetectorInfo::isMonitor(const size_t index) const { // No check for time dependence since monitor flags are not time dependent. @@ -167,92 +146,22 @@ void DetectorInfo::setMasked(const std::pair<size_t, size_t> &index, m_isMasked.access()[linearIndex(index)] = masked; } -/// Returns the scan count of the detector with given detector index. -size_t DetectorInfo::scanCount(const size_t index) const { - if (!m_scanCounts) - return 1; - if (m_isSyncScan) - return (*m_scanCounts)[0]; - return (*m_scanCounts)[index]; -} +/// Returns the scan count of the detector, reading it from m_componentInfo +size_t DetectorInfo::scanCount() const { return m_componentInfo->scanCount(); } /** Returns the scan interval of the detector with given index. * * The interval start and end values would typically correspond to nanoseconds * since 1990, as in Types::Core::DateAndTime. */ -std::pair<int64_t, int64_t> -DetectorInfo::scanInterval(const std::pair<size_t, size_t> &index) const { - if (!m_scanIntervals) - return {0, 0}; - if (m_isSyncScan) - return (*m_scanIntervals)[index.second]; - return (*m_scanIntervals)[linearIndex(index)]; -} - -namespace { -void checkScanInterval(const std::pair<int64_t, int64_t> &interval) { - if (interval.first >= interval.second) - throw std::runtime_error( - "DetectorInfo: cannot set scan interval with start >= end"); -} -} // namespace - -/** Set the scan interval of the detector with given detector index. - * - * The interval start and end values would typically correspond to nanoseconds - * since 1990, as in Types::Core::DateAndTime. Note that it is currently not - *possible - * to modify scan intervals for a DetectorInfo with time-dependent detectors, - * i.e., time intervals must be set with this method before merging individual - * scans. */ -void DetectorInfo::setScanInterval( - const size_t index, const std::pair<int64_t, int64_t> &interval) { - // Time intervals must be set up before adding time sensitive - // positions/rotations hence check below. - checkNoTimeDependence(); - checkScanInterval(interval); - if (!m_scanIntervals) - initScanIntervals(); - if (m_isSyncScan) - throw std::runtime_error("DetectorInfo has been initialized with a " - "synchonous scan, cannot set scan interval for " - "individual detector."); - m_scanIntervals.access()[index] = interval; -} - -/** Set the scan interval for all detectors. - * - * Prefer this over setting intervals for individual detectors since it enables - * internal performance optimization. See also overload for other details. */ -void DetectorInfo::setScanInterval( - const std::pair<int64_t, int64_t> &interval) { - checkNoTimeDependence(); - checkScanInterval(interval); - if (!m_scanIntervals) { - m_scanIntervals = - Kernel::make_cow<std::vector<std::pair<int64_t, int64_t>>>( - 1, std::pair<int64_t, int64_t>{0, 1}); - } - if (!m_isSyncScan) { - throw std::runtime_error( - "DetectorInfo has been initialized with a " - "asynchonous scan, cannot set synchronous scan interval."); - } - m_scanIntervals.access()[0] = interval; +const std::vector<std::pair<int64_t, int64_t>> +DetectorInfo::scanIntervals() const { + return m_componentInfo->scanIntervals(); } namespace { void failMerge(const std::string &what) { throw std::runtime_error(std::string("Cannot merge DetectorInfo: ") + what); } - -std::pair<size_t, size_t> -getIndex(const Kernel::cow_ptr<std::vector<std::pair<size_t, size_t>>> &indices, - const size_t index) { - if (!indices) - return {index, 0}; - return (*indices)[index]; -} } // namespace /** Merges the contents of other into this. @@ -265,54 +174,38 @@ getIndex(const Kernel::cow_ptr<std::vector<std::pair<size_t, size_t>>> &indices, * incremented by the scan count of that detector in `this`. The relative order * of time indices added from `other` is preserved. If the interval for a time * index in `other` is identical to a corresponding interval in `this`, it is - * ignored, i.e., no time index is added. */ -void DetectorInfo::merge(const DetectorInfo &other) { - if (!m_scanCounts) - initScanCounts(); - if (m_isSyncScan) { - const auto &merge = buildMergeSyncScanIndices(other); - for (size_t timeIndex = 0; timeIndex < other.m_scanIntervals->size(); - ++timeIndex) { - if (!merge[timeIndex]) - continue; - auto &scanIntervals = m_scanIntervals.access(); - auto &isMasked = m_isMasked.access(); - auto &positions = m_positions.access(); - auto &rotations = m_rotations.access(); - m_scanCounts.access()[0]++; - scanIntervals.push_back((*other.m_scanIntervals)[timeIndex]); - const size_t indexStart = other.linearIndex({0, timeIndex}); - size_t indexEnd = indexStart + size(); - isMasked.insert(isMasked.end(), other.m_isMasked->begin() + indexStart, - other.m_isMasked->begin() + indexEnd); - positions.insert(positions.end(), other.m_positions->begin() + indexStart, - other.m_positions->begin() + indexEnd); - rotations.insert(rotations.end(), other.m_rotations->begin() + indexStart, - other.m_rotations->begin() + indexEnd); - } - return; - } - const auto &merge = buildMergeIndices(other); - if (!m_indexMap) - initIndices(); - // Temporary to accumulate scan counts (need original for index offset). - auto scanCounts(m_scanCounts); - for (size_t linearIndex = 0; linearIndex < other.m_positions->size(); - ++linearIndex) { - if (!merge[linearIndex]) + * ignored, i.e., no time index is added. + * + * The `merge()` operation is private in `DetectorInfo`, and only + * accessible through `ComponentInfo` (via a `friend` declaration) + * because we need to avoid merging `DetectorInfo` without merging + * `ComponentInfo`, since that would effectively let us create a non-sync + * scan. Otherwise we cannot provide scanning-related methods in + * `ComponentInfo` without component index. + * + * The `scanIntervals` are stored in `ComponentInfo` only, to make sure all + * components have the same ones. The `bool` vector `merge` dictating whether + * a given interval should be merged or not was built by `ComponentInfo` and + * passed on to this function. + */ +void DetectorInfo::merge(const DetectorInfo &other, + const std::vector<bool> &merge) { + checkSizes(other); + for (size_t timeIndex = 0; timeIndex < other.scanCount(); ++timeIndex) { + if (!merge[timeIndex]) continue; - auto newIndex = getIndex(other.m_indices, linearIndex); - const size_t detIndex = newIndex.first; - newIndex.second += scanCount(detIndex); - scanCounts.access()[detIndex]++; - m_indexMap.access()[detIndex].push_back((*m_indices).size()); - m_indices.access().push_back(newIndex); - m_isMasked.access().push_back((*other.m_isMasked)[linearIndex]); - m_positions.access().push_back((*other.m_positions)[linearIndex]); - m_rotations.access().push_back((*other.m_rotations)[linearIndex]); - m_scanIntervals.access().push_back((*other.m_scanIntervals)[linearIndex]); + auto &isMasked = m_isMasked.access(); + auto &positions = m_positions.access(); + auto &rotations = m_rotations.access(); + const size_t indexStart = other.linearIndex({0, timeIndex}); + size_t indexEnd = indexStart + size(); + isMasked.insert(isMasked.end(), other.m_isMasked->begin() + indexStart, + other.m_isMasked->begin() + indexEnd); + positions.insert(positions.end(), other.m_positions->begin() + indexStart, + other.m_positions->begin() + indexEnd); + rotations.insert(rotations.end(), other.m_rotations->begin() + indexStart, + other.m_rotations->begin() + indexEnd); } - m_scanCounts = std::move(scanCounts); } void DetectorInfo::setComponentInfo(ComponentInfo *componentInfo) { @@ -350,111 +243,14 @@ Eigen::Vector3d DetectorInfo::samplePosition() const { return m_componentInfo->samplePosition(); } -void DetectorInfo::initScanCounts() { - checkNoTimeDependence(); - if (m_isSyncScan) - m_scanCounts = Kernel::make_cow<std::vector<size_t>>(1, 1); - else - m_scanCounts = Kernel::make_cow<std::vector<size_t>>(size(), 1); -} - -void DetectorInfo::initScanIntervals() { - checkNoTimeDependence(); - m_isSyncScan = false; - m_scanIntervals = Kernel::make_cow<std::vector<std::pair<int64_t, int64_t>>>( - size(), std::pair<int64_t, int64_t>{0, 1}); -} - -void DetectorInfo::initIndices() { - checkNoTimeDependence(); - m_indexMap = Kernel::make_cow<std::vector<std::vector<size_t>>>(); - m_indices = Kernel::make_cow<std::vector<std::pair<size_t, size_t>>>(); - auto &indexMap = m_indexMap.access(); - auto &indices = m_indices.access(); - indexMap.reserve(size()); - indices.reserve(size()); - // No time dependence, so both the detector index and the linear index are i. - for (size_t i = 0; i < size(); ++i) { - indexMap.emplace_back(1, i); - indices.emplace_back(i, 0); - } -} - -// Indices returned here are the list of linear indexes not to merge -std::vector<bool> -DetectorInfo::buildMergeIndices(const DetectorInfo &other) const { - checkSizes(other); - std::vector<bool> merge(other.m_positions->size(), true); - - for (size_t linearIndex1 = 0; linearIndex1 < other.m_positions->size(); - ++linearIndex1) { - const size_t detIndex = getIndex(other.m_indices, linearIndex1).first; - const auto &interval1 = (*other.m_scanIntervals)[linearIndex1]; - for (size_t timeIndex = 0; timeIndex < scanCount(detIndex); ++timeIndex) { - const auto linearIndex2 = linearIndex({detIndex, timeIndex}); - const auto &interval2 = (*m_scanIntervals)[linearIndex2]; - if (interval1 == interval2) { - checkIdenticalIntervals(other, linearIndex1, linearIndex2); - merge[linearIndex1] = false; - } else if ((interval1.first < interval2.second) && - (interval1.second > interval2.first)) { - failMerge("scan intervals overlap but not identical"); - } - } - } - return merge; -} - -// Indices returned here are the list of time indexes not to merge -std::vector<bool> -DetectorInfo::buildMergeSyncScanIndices(const DetectorInfo &other) const { - checkSizes(other); - std::vector<bool> merge(other.m_scanIntervals->size(), true); - - for (size_t t1 = 0; t1 < other.m_scanIntervals->size(); ++t1) { - for (size_t t2 = 0; t2 < m_scanIntervals->size(); ++t2) { - const auto &interval1 = (*other.m_scanIntervals)[t1]; - const auto &interval2 = (*m_scanIntervals)[t2]; - if (interval1 == interval2) { - for (size_t detIndex = 0; detIndex < size(); ++detIndex) { - const size_t linearIndex1 = other.linearIndex({detIndex, t1}); - const size_t linearIndex2 = linearIndex({detIndex, t2}); - checkIdenticalIntervals(other, linearIndex1, linearIndex2); - } - merge[t1] = false; - } else if ((interval1.first < interval2.second) && - (interval1.second > interval2.first)) { - failMerge("sync scan intervals overlap but not identical"); - } - } - } - return merge; -} - void DetectorInfo::checkSizes(const DetectorInfo &other) const { if (size() != other.size()) failMerge("size mismatch"); - if (!m_scanIntervals || !other.m_scanIntervals) - failMerge("scan intervals not defined"); - if (m_isSyncScan != other.m_isSyncScan) - failMerge("both or none of the scans must be synchronous"); if (!(m_isMonitor == other.m_isMonitor) && (*m_isMonitor != *other.m_isMonitor)) failMerge("monitor flags mismatch"); // TODO If we make masking time-independent we need to check masking here. } -void DetectorInfo::checkIdenticalIntervals(const DetectorInfo &other, - const size_t linearIndexOther, - const size_t linearIndexThis) const { - if ((*m_isMasked)[linearIndexThis] != (*other.m_isMasked)[linearIndexOther]) - failMerge("matching scan interval but mask flags differ"); - if ((*m_positions)[linearIndexThis] != (*other.m_positions)[linearIndexOther]) - failMerge("matching scan interval but positions differ"); - if ((*m_rotations)[linearIndexThis].coeffs() != - (*other.m_rotations)[linearIndexOther].coeffs()) - failMerge("matching scan interval but rotations differ"); -} - } // namespace Beamline } // namespace Mantid diff --git a/Framework/Beamline/test/ComponentInfoTest.h b/Framework/Beamline/test/ComponentInfoTest.h index 00eb9bc2c2131b386dbdcaa0f45166728b743264..8b1ce7c2ed4add3ee2a8b850d8c94c1e1baa6c0f 100644 --- a/Framework/Beamline/test/ComponentInfoTest.h +++ b/Framework/Beamline/test/ComponentInfoTest.h @@ -92,6 +92,17 @@ makeFlatTree(PosVec detPositions, RotVec detRotations) { return std::make_tuple(componentInfo, detectorInfo); } +std::tuple<boost::shared_ptr<ComponentInfo>, boost::shared_ptr<DetectorInfo>> +makeFlatTreeWithMonitor(PosVec detPositions, RotVec detRotations, + const std::vector<size_t> &monitorIndices) { + auto flatTree = makeFlatTree(detPositions, detRotations); + auto detectorInfo = boost::make_shared<DetectorInfo>( + detPositions, detRotations, monitorIndices); + auto compInfo = std::get<0>(flatTree); + compInfo->setDetectorInfo(detectorInfo.get()); + return std::make_tuple(compInfo, detectorInfo); +} + std::tuple<boost::shared_ptr<ComponentInfo>, PosVec, RotVec, PosVec, RotVec, boost::shared_ptr<DetectorInfo>> makeTreeExampleAndReturnGeometricArguments() { @@ -247,7 +258,6 @@ cloneInfos(const std::tuple<boost::shared_ptr<ComponentInfo>, std::get<0>(in)->cloneWithoutDetectorInfo()); auto detInfo = boost::make_shared<DetectorInfo>(*std::get<1>(in)); compInfo->setDetectorInfo(detInfo.get()); - detInfo->setComponentInfo(compInfo.get()); return std::make_tuple(compInfo, detInfo); } @@ -851,7 +861,7 @@ public: void test_scan_count_no_scanning() { ComponentInfo info; - TS_ASSERT_EQUALS(info.scanCount(0), 1); + TS_ASSERT_EQUALS(info.scanCount(), 1); } void test_unmerged_is_not_scanning() { @@ -887,7 +897,6 @@ public: void test_setRotation_single_scan_updates_positions_correctly() { auto allOutputs = makeTreeExampleAndReturnGeometricArguments(); - // Resulting ComponentInfo ComponentInfo &info = *std::get<0>(allOutputs); const std::pair<size_t, size_t> rootIndex{4, 0}; const std::pair<size_t, size_t> detectorIndex{1, 0}; @@ -895,6 +904,17 @@ public: detectorIndex); } + void test_setScanInterval() { + auto infos = makeTreeExample(); + auto &compInfo = *std::get<0>(infos); + std::pair<int64_t, int64_t> interval(1, 2); + compInfo.setScanInterval(interval); + TS_ASSERT_EQUALS(compInfo.scanIntervals()[0], interval); + interval = {1, 3}; + compInfo.setScanInterval(interval); + TS_ASSERT_EQUALS(compInfo.scanIntervals()[0], interval); + } + void test_setScanInterval_failures() { auto infos = makeTreeExample(); auto &compInfo = *std::get<0>(infos); @@ -916,60 +936,40 @@ public: auto &b = *std::get<0>(infos2); a.setScanInterval({0, 1}); b.setScanInterval({0, 1}); - b.setScanInterval({0, 1}); TS_ASSERT_THROWS_EQUALS(a.merge(b), const std::runtime_error &e, std::string(e.what()), "Cannot merge ComponentInfo: size mismatch"); } - void test_merge_fail_no_intervals() { - auto infos1 = makeFlatTree(PosVec(1), RotVec(1)); - auto infos2 = makeFlatTree(PosVec(1), RotVec(1)); - auto infos3 = makeFlatTree(PosVec(1), RotVec(1)); - auto &a = *std::get<0>(infos1); - auto &b = *std::get<0>(infos2); - auto &c = *std::get<0>(infos3); - TS_ASSERT_THROWS_EQUALS( - a.merge(b), const std::runtime_error &e, std::string(e.what()), - "Cannot merge ComponentInfo: scan intervals not defined"); - c.setScanInterval({0, 1}); - TS_ASSERT_THROWS_EQUALS( - a.merge(c), const std::runtime_error &e, std::string(e.what()), - "Cannot merge ComponentInfo: scan intervals not defined"); - a.setScanInterval({0, 1}); - TS_ASSERT_THROWS_EQUALS( - a.merge(b), const std::runtime_error &e, std::string(e.what()), - "Cannot merge ComponentInfo: scan intervals not defined"); - } - void test_merge_identical() { - auto infos1 = makeFlatTree(PosVec(1, Eigen::Vector3d(0, 0, 0)), - RotVec(1, Eigen::Quaterniond(Eigen::AngleAxisd( - 0, Eigen::Vector3d::UnitY())))); + auto pos = Eigen::Vector3d(0, 1, 2); + auto rot = + Eigen::Quaterniond(Eigen::AngleAxisd(0, Eigen::Vector3d::UnitY())); + auto infos1 = makeFlatTree(PosVec(1, pos), RotVec(1, rot)); ComponentInfo &a = *std::get<0>(infos1); a.setScanInterval({0, 10}); - auto infos2 = makeFlatTree(PosVec(1, Eigen::Vector3d(0, 0, 0)), - RotVec(1, Eigen::Quaterniond(Eigen::AngleAxisd( - 0, Eigen::Vector3d::UnitY())))); + auto infos2 = makeFlatTree(PosVec(1, pos), RotVec(1, rot)); ComponentInfo &b = *std::get<0>(infos2); b.setScanInterval({0, 10}); - TSM_ASSERT_EQUALS("Scan size should be 1", b.scanCount(0), 1); + TSM_ASSERT_EQUALS("Scan size should be 1", b.scanCount(), 1); b.merge(a); TS_ASSERT_THROWS_NOTHING(b.merge(a)); TSM_ASSERT_EQUALS("Intervals identical. Scan size should not grow", - b.scanCount(0), 1) + b.scanCount(), 1) } void test_merge_identical_interval_when_positions_differ() { - auto infos1 = makeFlatTree(PosVec(1), RotVec(1)); + auto pos = Eigen::Vector3d(0, 1, -1); + auto rot = + Eigen::Quaterniond(Eigen::AngleAxisd(1, Eigen::Vector3d::UnitX())); + auto infos1 = makeFlatTree(PosVec(1, pos), RotVec(1, rot)); ComponentInfo &a = *std::get<0>(infos1); a.setScanInterval({0, 1}); Eigen::Vector3d pos1(1, 0, 0); Eigen::Vector3d pos2(2, 0, 0); - auto rootIndex = a.root(); - a.setPosition(rootIndex, pos1); + a.setPosition(a.root(), pos1); auto infos2 = cloneInfos(infos1); ComponentInfo &b = *std::get<0>(infos2); // Sanity check @@ -977,26 +977,31 @@ public: auto infos3 = cloneInfos(infos1); ComponentInfo &c = *std::get<0>(infos3); - c.setPosition(rootIndex, pos2); + c.setPosition(c.root(), pos2); TS_ASSERT_THROWS_EQUALS(c.merge(a), const std::runtime_error &e, std::string(e.what()), "Cannot merge ComponentInfo: " "matching scan interval but " "positions differ"); - c.setPosition(rootIndex, pos1); + c.setPosition(c.root(), pos1); TS_ASSERT_THROWS_NOTHING(c.merge(a)); } void test_merge_identical_interval_when_rotations_differ() { - auto infos1 = makeFlatTree(PosVec(1), RotVec(1)); + auto pos = Eigen::Vector3d(0, 1, 0); + auto rot = + Eigen::Quaterniond(Eigen::AngleAxisd(2, Eigen::Vector3d::UnitZ())); + auto infos1 = makeFlatTree(PosVec(1, pos), RotVec(1, rot)); ComponentInfo &a = *std::get<0>(infos1); a.setScanInterval({0, 1}); Eigen::Quaterniond rot1( Eigen::AngleAxisd(30.0, Eigen::Vector3d{1, 2, 3}.normalized())); Eigen::Quaterniond rot2( Eigen::AngleAxisd(31.0, Eigen::Vector3d{1, 2, 3}.normalized())); - auto rootIndex = a.root(); - a.setRotation(rootIndex, rot1); + auto rootIndexA = a.root(); + a.setRotation(rootIndexA, rot1); + a.setPosition(rootIndexA, Eigen::Vector3d{1, 1, 1}); + a.setPosition(0, Eigen::Vector3d{2, 3, 4}); auto infos2 = cloneInfos(infos1); ComponentInfo &b = *std::get<0>(infos2); // Sanity check @@ -1004,7 +1009,10 @@ public: auto infos3 = cloneInfos(infos1); ComponentInfo &c = *std::get<0>(infos3); - c.setRotation(rootIndex, rot2); + auto rootIndexC = c.root(); + c.setRotation(rootIndexC, rot2); + c.setPosition(rootIndexC, Eigen::Vector3d{1, 1, 1}); + c.setPosition(0, Eigen::Vector3d{2, 3, 4}); TS_ASSERT_THROWS_EQUALS(c.merge(a), const std::runtime_error &e, std::string(e.what()), "Cannot merge ComponentInfo: " @@ -1012,6 +1020,96 @@ public: "rotations differ"); } + void test_merge_fail_identical_interval_but_component_positions_differ() { + auto pos0 = Eigen::Vector3d(1, 1, 1); + auto rot0 = + Eigen::Quaterniond(Eigen::AngleAxisd(0, Eigen::Vector3d::UnitY())); + auto infos1 = makeFlatTree(PosVec(1, pos0), RotVec(1, rot0)); + // Now make a strange situation where the components have different + // positions but detector positions are the same + auto pos1 = Eigen::Vector3d{1, 0, 0}; + auto pos2 = Eigen::Vector3d{1, 0, 3}; + ComponentInfo &a = *std::get<0>(infos1); + a.setScanInterval({0, 1}); + a.setPosition(a.root(), pos1); + a.setPosition(0, pos1); + auto infos2 = makeFlatTree(PosVec(1, pos0), RotVec(1, rot0)); + ComponentInfo &b = *std::get<0>(infos2); + b.setScanInterval({0, 1}); + b.setPosition(b.root(), pos2); + b.setPosition(0, pos1); // same as a's detector position + TS_ASSERT_THROWS_EQUALS(b.merge(a), const std::runtime_error &e, + std::string(e.what()), + "Cannot merge ComponentInfo: " + "matching scan interval but " + "positions differ"); + } + + void test_merge_fail_identical_interval_when_component_rotations_differ() { + auto pos0 = Eigen::Vector3d(1, 1, 1); + auto rot0 = + Eigen::Quaterniond(Eigen::AngleAxisd(0, Eigen::Vector3d::UnitY())); + auto infos1 = makeFlatTree(PosVec(1, pos0), RotVec(1, rot0)); + // Now make a strange situation where the components have different + // positions but detector rotations are the same + auto pos = Eigen::Vector3d{1, 0, 0}; + auto rot1 = Eigen::Quaterniond{ + Eigen::AngleAxisd(5.0, Eigen::Vector3d{-1, 2, -3}.normalized())}; + auto rot2 = Eigen::Quaterniond{ + Eigen::AngleAxisd(5.0, Eigen::Vector3d{-1, 2, -4}.normalized())}; + ComponentInfo &a = *std::get<0>(infos1); + a.setScanInterval({0, 1}); + a.setRotation(a.root(), rot1); + a.setPosition(a.root(), pos); + a.setPosition(0, pos); + auto infos2 = cloneInfos(infos1); + ComponentInfo &b = *std::get<0>(infos2); + b.setRotation(b.root(), rot2); + b.setPosition(b.root(), pos); + b.setPosition(0, pos); + b.setRotation(0, rot1); // same as a's detector rotation + TS_ASSERT_THROWS_EQUALS(b.merge(a), const std::runtime_error &e, + std::string(e.what()), + "Cannot merge ComponentInfo: " + "matching scan interval but " + "rotations differ"); + } + + void test_merge_fail_monitor_mismatch() { + auto pos = Eigen::Vector3d{1, 1, 1}; + PosVec posVec({pos, pos}); + auto rot = Eigen::Quaterniond{ + Eigen::AngleAxisd(30.0, Eigen::Vector3d{1, 2, 3}.normalized())}; + RotVec rotVec({rot, rot}); + auto infos1 = makeFlatTree(posVec, rotVec); + auto infos2 = makeFlatTreeWithMonitor(posVec, rotVec, {1}); + ComponentInfo &a = *std::get<0>(infos1); + ComponentInfo &b = *std::get<0>(infos2); + a.setScanInterval({0, 1}); + b.setScanInterval({0, 1}); + TS_ASSERT_THROWS_EQUALS( + a.merge(b), const std::runtime_error &e, std::string(e.what()), + "Cannot merge DetectorInfo: monitor flags mismatch"); + } + + void test_merge_identical_interval_with_monitor() { + auto pos = Eigen::Vector3d{1, 1, 1}; + PosVec posVec({pos, pos}); + auto rot = Eigen::Quaterniond{ + Eigen::AngleAxisd(30.0, Eigen::Vector3d{1, 2, 3}.normalized())}; + RotVec rotVec({rot, rot}); + auto infos1 = makeFlatTreeWithMonitor(posVec, rotVec, {1}); + auto infos2 = makeFlatTreeWithMonitor(posVec, rotVec, {1}); + ComponentInfo &a = *std::get<0>(infos1); + ComponentInfo &b = *std::get<0>(infos2); + DetectorInfo &c = *std::get<1>(infos1); + DetectorInfo &d = *std::get<1>(infos2); + a.setScanInterval({0, 1}); + b.setScanInterval({0, 1}); + TS_ASSERT_THROWS_NOTHING(a.merge(b)); + TS_ASSERT(c.isEquivalent(d)); + } + void test_merge_fail_partial_overlap() { auto infos1 = makeFlatTree(PosVec(1), RotVec(1)); ComponentInfo &a = *std::get<0>(infos1); @@ -1022,17 +1120,17 @@ public: b.setScanInterval({-1, 5}); TS_ASSERT_THROWS_EQUALS(b.merge(a), const std::runtime_error &e, std::string(e.what()), - "Cannot merge ComponentInfo: sync scan intervals " + "Cannot merge ComponentInfo: scan intervals " "overlap but not identical"); b.setScanInterval({1, 5}); TS_ASSERT_THROWS_EQUALS(b.merge(a), const std::runtime_error &e, std::string(e.what()), - "Cannot merge ComponentInfo: sync scan intervals " + "Cannot merge ComponentInfo: scan intervals " "overlap but not identical"); b.setScanInterval({1, 11}); TS_ASSERT_THROWS_EQUALS(b.merge(a), const std::runtime_error &e, std::string(e.what()), - "Cannot merge ComponentInfo: sync scan intervals " + "Cannot merge ComponentInfo: scan intervals " "overlap but not identical"); } @@ -1040,7 +1138,6 @@ public: auto infos1 = makeFlatTree(PosVec(1), RotVec(1)); auto infos2 = makeFlatTree(PosVec(1), RotVec(1)); ComponentInfo &a = *std::get<0>(infos1); - ComponentInfo &b = *std::get<0>(infos2); Eigen::Vector3d pos1(1, 0, 0); Eigen::Vector3d pos2(2, 0, 0); @@ -1053,23 +1150,24 @@ public: a.merge(b); // Execute the merge TS_ASSERT(a.isScanning()); TS_ASSERT_EQUALS(a.size(), 2); - TS_ASSERT_EQUALS(a.scanSize(), a.size() + b.size()); - TS_ASSERT_EQUALS(a.scanCount(0), 2); + TS_ASSERT_EQUALS(a.scanCount(), 2); // Note that the order is not guaranteed, currently these are just in the // order in which the are merged. auto index1 = std::pair<size_t, size_t>(0 /*static index*/, 0 /*time index*/); auto index2 = std::pair<size_t, size_t>(0 /*static index*/, 1 /*time index*/); - TS_ASSERT_EQUALS(a.scanInterval(index1), interval1); - TS_ASSERT_EQUALS(a.scanInterval(index2), interval2); + TS_ASSERT_EQUALS(a.scanIntervals()[index1.second], interval1); + TS_ASSERT_EQUALS(a.scanIntervals()[index2.second], interval2); TS_ASSERT_EQUALS(a.position(index1), pos1); TS_ASSERT_EQUALS(a.position(index2), pos2); // Test Detector info is synched internally const DetectorInfo &mergeDetectorInfo = *std::get<1>(infos1); - TS_ASSERT_EQUALS(mergeDetectorInfo.scanCount(0), 2); - TS_ASSERT_EQUALS(mergeDetectorInfo.scanInterval(index1), interval1); - TS_ASSERT_EQUALS(mergeDetectorInfo.scanInterval(index2), interval2); + TS_ASSERT_EQUALS(mergeDetectorInfo.scanCount(), 2); + TS_ASSERT_EQUALS(mergeDetectorInfo.scanIntervals()[index1.second], + interval1); + TS_ASSERT_EQUALS(mergeDetectorInfo.scanIntervals()[index2.second], + interval2); TS_ASSERT_EQUALS(mergeDetectorInfo.position(index1), pos1); TS_ASSERT_EQUALS(mergeDetectorInfo.position(index2), pos2); } @@ -1078,7 +1176,6 @@ public: auto infos1 = makeFlatTree(PosVec(1), RotVec(1)); auto infos2 = makeFlatTree(PosVec(1), RotVec(1)); ComponentInfo &a = *std::get<0>(infos1); - ComponentInfo &b = *std::get<0>(infos2); const auto detPosA = a.position(0); const auto detPosB = b.position(0); @@ -1095,24 +1192,23 @@ public: a.merge(b); // Execute the merge TS_ASSERT(a.isScanning()); TS_ASSERT_EQUALS(a.size(), 2); - TS_ASSERT_EQUALS(a.scanSize(), 2 * 2); - TS_ASSERT_EQUALS(a.scanCount(a.root()), 2); + TS_ASSERT_EQUALS(a.scanCount(), 2); // Note that the order is not guaranteed, currently these are just in the // order in which the are merged. auto index1 = std::pair<size_t, size_t>(a.root() /*static index*/, 0 /*time index*/); auto index2 = std::pair<size_t, size_t>(a.root() /*static index*/, 1 /*time index*/); - TS_ASSERT_EQUALS(a.scanInterval(index1), interval1); - TS_ASSERT_EQUALS(a.scanInterval(index2), interval2); + TS_ASSERT_EQUALS(a.scanIntervals()[index1.second], interval1); + TS_ASSERT_EQUALS(a.scanIntervals()[index2.second], interval2); TS_ASSERT_EQUALS(a.position(index1), pos1); TS_ASSERT_EQUALS(a.position(index2), pos2); // Test Detector info is synched internally const DetectorInfo &mergeDetectorInfo = *std::get<1>(infos1); - TS_ASSERT_EQUALS(mergeDetectorInfo.scanCount(0), 1 * 2); - TS_ASSERT_EQUALS(mergeDetectorInfo.scanInterval({0, 0}), interval1); - TS_ASSERT_EQUALS(mergeDetectorInfo.scanInterval({0, 1}), interval2); + TS_ASSERT_EQUALS(mergeDetectorInfo.scanCount(), 1 * 2); + TS_ASSERT_EQUALS(mergeDetectorInfo.scanIntervals()[0], interval1); + TS_ASSERT_EQUALS(mergeDetectorInfo.scanIntervals()[1], interval2); // Check that the child detectors have been positioned according to the // correct offsets const auto rootOffsetA = pos1 - rootPosA; @@ -1126,7 +1222,6 @@ public: auto infos1 = makeFlatTree(PosVec(1, detPos), RotVec(1)); auto infos2 = makeFlatTree(PosVec(1, detPos), RotVec(1)); ComponentInfo &a = *std::get<0>(infos1); - ComponentInfo &b = *std::get<0>(infos2); Eigen::Quaterniond rot1( Eigen::AngleAxisd(M_PI / 2, Eigen::Vector3d::UnitY())); @@ -1141,23 +1236,22 @@ public: a.merge(b); // Execute the merge TS_ASSERT(a.isScanning()); TS_ASSERT_EQUALS(a.size(), 2); - TS_ASSERT_EQUALS(a.scanSize(), 2 * 2); - TS_ASSERT_EQUALS(a.scanCount(a.root()), 2); + TS_ASSERT_EQUALS(a.scanCount(), 2); // Note that the order is not guaranteed, currently these are just in the // order in which the are merged. auto index1 = std::pair<size_t, size_t>(a.root() /*static index*/, 0 /*time index*/); auto index2 = std::pair<size_t, size_t>(a.root() /*static index*/, 1 /*time index*/); - TS_ASSERT_EQUALS(a.scanInterval(index1), interval1); - TS_ASSERT_EQUALS(a.scanInterval(index2), interval2); + TS_ASSERT_EQUALS(a.scanIntervals()[index1.second], interval1); + TS_ASSERT_EQUALS(a.scanIntervals()[index2.second], interval2); TS_ASSERT(a.rotation(index1).isApprox(rot1)); TS_ASSERT(a.rotation(index2).isApprox(rot2)); // Test Detector info is synched internally const DetectorInfo &mergeDetectorInfo = *std::get<1>(infos1); - TS_ASSERT_EQUALS(mergeDetectorInfo.scanCount(0), 1 * 2); - TS_ASSERT_EQUALS(mergeDetectorInfo.scanInterval({0, 0}), interval1); + TS_ASSERT_EQUALS(mergeDetectorInfo.scanCount(), 1 * 2); + TS_ASSERT_EQUALS(mergeDetectorInfo.scanIntervals()[0], interval1); // Check detectors moved correctly as a result of root rotation // Detector at x=1,y=0,z=0 rotated around root at x=0,y=0,z=0 with rotation // vector y=1, 90 degrees @@ -1192,8 +1286,7 @@ public: a.merge(b); // Merge again TS_ASSERT(a.isScanning()); TS_ASSERT_EQUALS(a.size(), 2); - TS_ASSERT_EQUALS(a.scanSize(), 2 * 3); - TS_ASSERT_EQUALS(a.scanCount(a.root()), 3); + TS_ASSERT_EQUALS(a.scanCount(), 3); // Note that the order is not guaranteed, currently these are just in the // order in which the are merged. auto index1 = @@ -1202,19 +1295,86 @@ public: std::pair<size_t, size_t>(a.root() /*static index*/, 1 /*time index*/); auto index3 = std::pair<size_t, size_t>(a.root() /*static index*/, 2 /*time index*/); - TS_ASSERT_EQUALS(a.scanInterval(index1), interval1); - TS_ASSERT_EQUALS(a.scanInterval(index2), interval2); - TS_ASSERT_EQUALS(a.scanInterval(index3), interval3); + TS_ASSERT_EQUALS(a.scanIntervals()[index1.second], interval1); + TS_ASSERT_EQUALS(a.scanIntervals()[index2.second], interval2); + TS_ASSERT_EQUALS(a.scanIntervals()[index3.second], interval3); TS_ASSERT_EQUALS(a.position(index1), pos1); TS_ASSERT_EQUALS(a.position(index2), pos2); TS_ASSERT_EQUALS(a.position(index3), pos3); // Test Detector info is synched internally const DetectorInfo &mergeDetectorInfo = *std::get<1>(infos1); - TS_ASSERT_EQUALS(mergeDetectorInfo.scanCount(0), 1 * 3); - TS_ASSERT_EQUALS(mergeDetectorInfo.scanInterval({0, 0}), interval1); - TS_ASSERT_EQUALS(mergeDetectorInfo.scanInterval({0, 1}), interval2); - TS_ASSERT_EQUALS(mergeDetectorInfo.scanInterval({0, 2}), interval3); + TS_ASSERT_EQUALS(mergeDetectorInfo.scanCount(), 1 * 3); + TS_ASSERT_EQUALS(mergeDetectorInfo.scanIntervals()[0], interval1); + TS_ASSERT_EQUALS(mergeDetectorInfo.scanIntervals()[1], interval2); + TS_ASSERT_EQUALS(mergeDetectorInfo.scanIntervals()[2], interval3); + } + + void test_merge_idempotent() { + // Test that A + B + B = A + B + PosVec pos = {Eigen::Vector3d{0, 0, 0}, Eigen::Vector3d{1, 1, 1}}; + RotVec rot = {Eigen::Quaterniond{Eigen::AngleAxisd( + 20.0, Eigen::Vector3d{1, 2, 3}.normalized())}, + Eigen::Quaterniond{Eigen::AngleAxisd( + 30.0, Eigen::Vector3d{1, 2, 3}.normalized())}}; + auto infos1 = makeFlatTree(pos, rot); + auto infos2 = makeFlatTree(pos, rot); + auto infos3 = makeFlatTree(pos, rot); + auto infos4 = makeFlatTree(pos, rot); + ComponentInfo &a = *std::get<0>(infos1); + ComponentInfo &b = *std::get<0>(infos2); + ComponentInfo &c = *std::get<0>(infos3); + ComponentInfo &d = *std::get<0>(infos4); + DetectorInfo &e = *std::get<1>(infos1); + DetectorInfo &f = *std::get<1>(infos3); + DetectorInfo &g = *std::get<1>(infos4); + a.setScanInterval({0, 1}); + b.setScanInterval({1, 2}); + c.setScanInterval({0, 1}); + d.setScanInterval({0, 1}); + TS_ASSERT_THROWS_NOTHING(c.merge(b)); + TS_ASSERT_THROWS_NOTHING(a.merge(b)); + TS_ASSERT_THROWS_NOTHING(a.merge(b)); + TS_ASSERT(e.isEquivalent(f)); + // Make sure the merged components are actually different from a tree that + // has not gone through any merge operations. + TS_ASSERT(!e.isEquivalent(g)); + } + + void test_merge_multiple_associative() { + // Test that (A + B) + C == A + (B + C) + // This is implied by the ordering guaranteed by merge(). + auto infos1 = makeFlatTree(PosVec({Eigen::Vector3d{1, 0, 0}}), + RotVec({Eigen::Quaterniond::Identity()})); + auto infos2 = makeFlatTree(PosVec({Eigen::Vector3d{2, 0, 0}}), + RotVec({Eigen::Quaterniond::Identity()})); + auto infos3 = makeFlatTree(PosVec({Eigen::Vector3d{3, 0, 0}}), + RotVec({Eigen::Quaterniond::Identity()})); + auto infos4 = makeFlatTree(PosVec({Eigen::Vector3d{1, 0, 0}}), + RotVec({Eigen::Quaterniond::Identity()})); + auto infos5 = makeFlatTree(PosVec({Eigen::Vector3d{1, 0, 0}}), + RotVec({Eigen::Quaterniond::Identity()})); + ComponentInfo &a1 = *std::get<0>(infos1); + ComponentInfo &b = *std::get<0>(infos2); + ComponentInfo &c = *std::get<0>(infos3); + ComponentInfo &a2 = *std::get<0>(infos4); + ComponentInfo &a3 = *std::get<0>(infos5); + DetectorInfo &d = *std::get<1>(infos1); + DetectorInfo &e = *std::get<1>(infos4); + DetectorInfo &f = *std::get<1>(infos5); + a1.setScanInterval({0, 1}); + b.setScanInterval({1, 2}); + c.setScanInterval({2, 3}); + a2.setScanInterval({0, 1}); + a3.setScanInterval({0, 1}); + TS_ASSERT_THROWS_NOTHING(a1.merge(b)); + TS_ASSERT_THROWS_NOTHING(a1.merge(c)); + TS_ASSERT_THROWS_NOTHING(b.merge(c)); + TS_ASSERT_THROWS_NOTHING(a2.merge(b)); + TS_ASSERT(d.isEquivalent(e)); + // Make sure the merged components are actually different from a tree that + // has not gone through any merge operations. + TS_ASSERT(!d.isEquivalent(f)); } }; #endif /* MANTID_BEAMLINE_COMPONENTINFOTEST_H_ */ diff --git a/Framework/Beamline/test/DetectorInfoTest.h b/Framework/Beamline/test/DetectorInfoTest.h index 36081808abd24a5b5f201bcd3810f4ec29cb287d..562e00c102a92806a5b68ae5788191f7fdfc7ee0 100644 --- a/Framework/Beamline/test/DetectorInfoTest.h +++ b/Framework/Beamline/test/DetectorInfoTest.h @@ -30,7 +30,6 @@ public: std::unique_ptr<DetectorInfo> detInfo; TS_ASSERT_THROWS_NOTHING(detInfo = Kernel::make_unique<DetectorInfo>()); TS_ASSERT_EQUALS(detInfo->size(), 0); - TS_ASSERT_EQUALS(detInfo->scanSize(), 0); TS_ASSERT(!detInfo->isScanning()); TS_ASSERT(!detInfo->hasComponentInfo()); @@ -47,7 +46,6 @@ public: TS_ASSERT_THROWS_NOTHING( info = Kernel::make_unique<DetectorInfo>(PosVec(3), RotVec(3), mons)); TS_ASSERT_EQUALS(info->size(), 3); - TS_ASSERT_EQUALS(info->scanSize(), 3); TS_ASSERT_THROWS_NOTHING(DetectorInfo(PosVec(3), RotVec(3), {})); TS_ASSERT_THROWS_NOTHING(DetectorInfo(PosVec(3), RotVec(3), {0})); TS_ASSERT_THROWS_NOTHING(DetectorInfo(PosVec(3), RotVec(3), {0, 1, 2})); @@ -278,482 +276,18 @@ public: } void test_scanCount() { - DetectorInfo info(PosVec(1), RotVec(1)); - TS_ASSERT_EQUALS(info.scanCount(0), 1); - } - - void test_scanInterval() { - DetectorInfo info(PosVec(1), RotVec(1)); - TS_ASSERT_EQUALS(info.scanInterval({0, 0}), - (std::pair<int64_t, int64_t>(0, 0))); - } - - void test_setScanInterval() { - DetectorInfo info(PosVec(1), RotVec(1)); - info.setScanInterval(0, {1, 2}); - TS_ASSERT_EQUALS(info.scanInterval({0, 0}), - (std::pair<int64_t, int64_t>(1, 2))); - info.setScanInterval(0, {1, 3}); - TS_ASSERT_EQUALS(info.scanInterval({0, 0}), - (std::pair<int64_t, int64_t>(1, 3))); - } - - void test_setScanInterval_sync() { - DetectorInfo info(PosVec(2), RotVec(2)); - std::pair<int64_t, int64_t> interval(1, 2); - info.setScanInterval(interval); - TS_ASSERT_EQUALS(info.scanInterval({0, 0}), interval); - TS_ASSERT_EQUALS(info.scanInterval({1, 0}), interval); - interval = {1, 3}; - info.setScanInterval(interval); - TS_ASSERT_EQUALS(info.scanInterval({0, 0}), interval); - TS_ASSERT_EQUALS(info.scanInterval({1, 0}), interval); - } - - void test_setScanInterval_failures() { - DetectorInfo info(PosVec(1), RotVec(1)); - TS_ASSERT_THROWS_EQUALS( - info.setScanInterval(0, {1, 1}), const std::runtime_error &e, - std::string(e.what()), - "DetectorInfo: cannot set scan interval with start >= end"); - TS_ASSERT_THROWS_EQUALS( - info.setScanInterval(0, {2, 1}), const std::runtime_error &e, - std::string(e.what()), - "DetectorInfo: cannot set scan interval with start >= end"); - } - - void test_setScanInterval_sync_failures() { - DetectorInfo info(PosVec(1), RotVec(1)); - TS_ASSERT_THROWS_EQUALS( - info.setScanInterval({1, 1}), const std::runtime_error &e, - std::string(e.what()), - "DetectorInfo: cannot set scan interval with start >= end"); - TS_ASSERT_THROWS_EQUALS( - info.setScanInterval({2, 1}), const std::runtime_error &e, - std::string(e.what()), - "DetectorInfo: cannot set scan interval with start >= end"); - } - - void test_setScanInterval_sync_async_fail() { - DetectorInfo info(PosVec(1), RotVec(1)); - info.setScanInterval({1, 2}); - TS_ASSERT_THROWS_EQUALS(info.setScanInterval(0, {1, 2}), - const std::runtime_error &e, std::string(e.what()), - "DetectorInfo has been initialized with a " - "synchonous scan, cannot set scan interval for " - "individual detector."); - } - - void test_setScanInterval_async_sync_fail() { - DetectorInfo info(PosVec(1), RotVec(1)); - info.setScanInterval(0, {1, 2}); - TS_ASSERT_THROWS_EQUALS(info.setScanInterval({1, 2}), - const std::runtime_error &e, std::string(e.what()), - "DetectorInfo has been initialized with a " - "asynchonous scan, cannot set synchronous scan " - "interval."); - } - - void test_merge_fail_size() { - DetectorInfo a(PosVec(1), RotVec(1)); - DetectorInfo b(PosVec(2), RotVec(2)); - a.setScanInterval(0, {0, 1}); - b.setScanInterval(0, {0, 1}); - b.setScanInterval(1, {0, 1}); - TS_ASSERT_THROWS_EQUALS(a.merge(b), const std::runtime_error &e, - std::string(e.what()), - "Cannot merge DetectorInfo: size mismatch"); - } - - void test_merge_fail_no_intervals() { - DetectorInfo a(PosVec(1), RotVec(1)); - DetectorInfo b(PosVec(1), RotVec(1)); - DetectorInfo c(PosVec(1), RotVec(1)); - TS_ASSERT_THROWS_EQUALS( - a.merge(b), const std::runtime_error &e, std::string(e.what()), - "Cannot merge DetectorInfo: scan intervals not defined"); - c.setScanInterval(0, {0, 1}); - TS_ASSERT_THROWS_EQUALS( - a.merge(c), const std::runtime_error &e, std::string(e.what()), - "Cannot merge DetectorInfo: scan intervals not defined"); - a.setScanInterval(0, {0, 1}); - TS_ASSERT_THROWS_EQUALS( - a.merge(b), const std::runtime_error &e, std::string(e.what()), - "Cannot merge DetectorInfo: scan intervals not defined"); - } - - void test_merge_fail_sync_async_mismatch() { - DetectorInfo a(PosVec(1), RotVec(1)); - DetectorInfo b(PosVec(1), RotVec(1)); - a.setScanInterval(0, {0, 1}); - b.setScanInterval({0, 1}); - TS_ASSERT_THROWS_EQUALS(a.merge(b), const std::runtime_error &e, - std::string(e.what()), - "Cannot merge DetectorInfo: " - "both or none of the scans " - "must be synchronous"); - TS_ASSERT_THROWS_EQUALS(b.merge(a), const std::runtime_error &e, - std::string(e.what()), - "Cannot merge DetectorInfo: " - "both or none of the scans " - "must be synchronous"); - } - - void test_merge_fail_monitor_mismatch() { - DetectorInfo a(PosVec(2), RotVec(2)); - DetectorInfo b(PosVec(2), RotVec(2), {1}); - a.setScanInterval(0, {0, 1}); - a.setScanInterval(1, {0, 1}); - b.setScanInterval(0, {0, 1}); - b.setScanInterval(1, {0, 1}); - TS_ASSERT_THROWS_EQUALS( - a.merge(b), const std::runtime_error &e, std::string(e.what()), - "Cannot merge DetectorInfo: monitor flags mismatch"); - } - - void test_merge_identical_sync() { - DetectorInfo a(PosVec(2), RotVec(2)); - a.setScanInterval({0, 10}); - auto b(a); - TS_ASSERT_THROWS_NOTHING(b.merge(a)); - } - - void test_merge_fail_overlap_sync() { - DetectorInfo a(PosVec(2), RotVec(2)); - a.setScanInterval({0, 10}); - auto b(a); - b = a; - b.setScanInterval({-1, 5}); - TS_ASSERT_THROWS_EQUALS(b.merge(a), const std::runtime_error &e, - std::string(e.what()), - "Cannot merge DetectorInfo: " - "sync scan intervals " - "overlap but not identical"); - b.setScanInterval({1, 5}); - TS_ASSERT_THROWS_EQUALS(b.merge(a), const std::runtime_error &e, - std::string(e.what()), - "Cannot merge DetectorInfo: " - "sync scan intervals " - "overlap but not identical"); - b.setScanInterval({1, 11}); - TS_ASSERT_THROWS_EQUALS(b.merge(a), const std::runtime_error &e, - std::string(e.what()), - "Cannot merge DetectorInfo: " - "sync scan intervals " - "overlap but not identical"); - } - - void do_test_merge_identical_interval_failures(DetectorInfo &a) { - Eigen::Vector3d pos1(1, 0, 0); - Eigen::Vector3d pos2(2, 0, 0); - Eigen::Quaterniond rot1( - Eigen::AngleAxisd(30.0, Eigen::Vector3d{1, 2, 3}.normalized())); - Eigen::Quaterniond rot2( - Eigen::AngleAxisd(31.0, Eigen::Vector3d{1, 2, 3}.normalized())); - a.setMasked(0, true); - a.setPosition(0, pos1); - a.setRotation(0, rot1); - auto b(a); - TS_ASSERT_THROWS_NOTHING(b.merge(a)); - - b = a; - b.setMasked(0, false); - TS_ASSERT_THROWS_EQUALS(b.merge(a), const std::runtime_error &e, - std::string(e.what()), - "Cannot merge DetectorInfo: " - "matching scan interval but " - "mask flags differ"); - b.setMasked(0, true); - TS_ASSERT_THROWS_NOTHING(b.merge(a)); - - b = a; - b.setPosition(0, pos2); - TS_ASSERT_THROWS_EQUALS(b.merge(a), const std::runtime_error &e, - std::string(e.what()), - "Cannot merge DetectorInfo: " - "matching scan interval but " - "positions differ"); - b.setPosition(0, pos1); - TS_ASSERT_THROWS_NOTHING(b.merge(a)); - - b = a; - b.setRotation(0, rot2); - TS_ASSERT_THROWS_EQUALS(b.merge(a), const std::runtime_error &e, - std::string(e.what()), - "Cannot merge DetectorInfo: " - "matching scan interval but " - "rotations differ"); - b.setRotation(0, rot1); - TS_ASSERT_THROWS_NOTHING(b.merge(a)); - } - - void test_merge_identical_interval_failures_async() { - DetectorInfo a(PosVec(1), RotVec(1)); - a.setScanInterval(0, {0, 1}); - do_test_merge_identical_interval_failures(a); - } - - void test_merge_identical_interval_failures_sync() { - DetectorInfo a(PosVec(1), RotVec(1)); - a.setScanInterval({0, 1}); - do_test_merge_identical_interval_failures(a); - } - - void test_merge_identical_interval_async() { - DetectorInfo a(PosVec(1), RotVec(1)); - a.setScanInterval(0, {0, 1}); - const auto b(a); - TS_ASSERT_THROWS_NOTHING(a.merge(b)); - TS_ASSERT(a.isEquivalent(b)); - } - - void test_merge_identical_interval_sync() { - DetectorInfo a(PosVec(2), RotVec(2)); - a.setScanInterval({0, 10}); - auto b(a); - TS_ASSERT_THROWS_NOTHING(b.merge(a)); - } - - void test_merge_identical_interval_with_monitor() { - DetectorInfo a(PosVec(2), RotVec(2), {1}); - a.setScanInterval(0, {0, 1}); - a.setScanInterval(1, {0, 1}); - const auto b(a); - TS_ASSERT_THROWS_NOTHING(a.merge(b)); - TS_ASSERT(a.isEquivalent(b)); - } - - void test_merge_fail_partial_overlap() { - DetectorInfo a(PosVec(2), RotVec(2)); - a.setScanInterval(0, {0, 10}); - a.setScanInterval(1, {0, 10}); - auto b(a); - TS_ASSERT_THROWS_NOTHING(b.merge(a)); - b = a; - b.setScanInterval(1, {-1, 5}); - TS_ASSERT_THROWS_EQUALS( - b.merge(a), const std::runtime_error &e, std::string(e.what()), - "Cannot merge DetectorInfo: scan intervals overlap but not identical"); - b.setScanInterval(1, {1, 5}); - TS_ASSERT_THROWS_EQUALS( - b.merge(a), const std::runtime_error &e, std::string(e.what()), - "Cannot merge DetectorInfo: scan intervals overlap but not identical"); - b.setScanInterval(1, {1, 11}); - TS_ASSERT_THROWS_EQUALS( - b.merge(a), const std::runtime_error &e, std::string(e.what()), - "Cannot merge DetectorInfo: scan intervals overlap but not identical"); - } - - void test_merge() { - DetectorInfo a(PosVec(2), RotVec(2), {1}); - // Monitor at index 1, set up for identical interval - std::pair<int64_t, int64_t> monitorInterval(0, 2); - a.setScanInterval(1, monitorInterval); - auto b(a); - Eigen::Vector3d pos1(1, 0, 0); - Eigen::Vector3d pos2(2, 0, 0); - a.setPosition(0, pos1); - b.setPosition(0, pos2); - std::pair<int64_t, int64_t> interval1(0, 1); - std::pair<int64_t, int64_t> interval2(1, 2); - a.setScanInterval(0, interval1); - b.setScanInterval(0, interval2); - TS_ASSERT_THROWS_NOTHING(a.merge(b)); - TS_ASSERT(a.isScanning()); - TS_ASSERT(!a.isSyncScan()); - TS_ASSERT(!a.isEquivalent(b)); - TS_ASSERT_EQUALS(a.size(), 2); - TS_ASSERT_EQUALS(a.scanSize(), 3); - TS_ASSERT_EQUALS(a.scanCount(0), 2); - // Note that the order is not guaranteed, currently these are just in the - // order in which the are merged. - TS_ASSERT_EQUALS(a.scanInterval({0, 0}), interval1); - TS_ASSERT_EQUALS(a.scanInterval({0, 1}), interval2); - TS_ASSERT_EQUALS(a.position({0, 0}), pos1); - TS_ASSERT_EQUALS(a.position({0, 1}), pos2); - // Monitor is not scanning - TS_ASSERT_EQUALS(a.scanCount(1), 1); - } - - void test_merge_sync() { - DetectorInfo a(PosVec(2), RotVec(2), {1}); - auto b(a); - Eigen::Vector3d pos1(1, 0, 0); - Eigen::Vector3d pos2(2, 0, 0); - a.setPosition(0, pos1); - b.setPosition(0, pos2); - std::pair<int64_t, int64_t> interval1(0, 1); - std::pair<int64_t, int64_t> interval2(1, 2); - a.setScanInterval(interval1); - b.setScanInterval(interval2); - TS_ASSERT_THROWS_NOTHING(a.merge(b)); - TS_ASSERT(a.isScanning()); - TS_ASSERT(a.isSyncScan()); - TS_ASSERT(!a.isEquivalent(b)); - TS_ASSERT_EQUALS(a.size(), 2); - TS_ASSERT_EQUALS(a.scanSize(), 4); - TS_ASSERT_EQUALS(a.scanCount(0), 2); - TS_ASSERT_EQUALS(a.scanCount(1), 2); - // Note that the order is not guaranteed, currently these are just in the - // order in which the are merged. - TS_ASSERT_EQUALS(a.scanInterval({0, 0}), interval1); - TS_ASSERT_EQUALS(a.scanInterval({1, 0}), interval1); - TS_ASSERT_EQUALS(a.scanInterval({0, 1}), interval2); - TS_ASSERT_EQUALS(a.scanInterval({1, 1}), interval2); - TS_ASSERT_EQUALS(a.position({0, 0}), pos1); - TS_ASSERT_EQUALS(a.position({0, 1}), pos2); - } - - void test_merge_idempotent() { - // Test that A + B + B = A + B - DetectorInfo a(PosVec(2), RotVec(2), {1}); - // Monitor at index 1, set up for identical interval - std::pair<int64_t, int64_t> monitorInterval(0, 2); - a.setScanInterval(1, monitorInterval); - a.setPosition(1, {0, 0, 0}); - auto b(a); - Eigen::Vector3d pos1(1, 0, 0); - Eigen::Vector3d pos2(2, 0, 0); - a.setPosition(0, pos1); - b.setPosition(0, pos2); - std::pair<int64_t, int64_t> interval1(0, 1); - std::pair<int64_t, int64_t> interval2(1, 2); - a.setScanInterval(0, interval1); - b.setScanInterval(0, interval2); - - TS_ASSERT_THROWS_NOTHING(a.merge(b)); - auto a0(a); - TS_ASSERT_THROWS_NOTHING(a.merge(b)); - TS_ASSERT(a.isEquivalent(a0)); - } - - void test_merge_multiple() { - DetectorInfo a(PosVec(2), RotVec(2), {1}); - // Monitor at index 1, set up for identical interval - std::pair<int64_t, int64_t> monitorInterval(0, 3); - a.setScanInterval(1, monitorInterval); - auto b(a); - auto c(a); - Eigen::Vector3d pos1(1, 0, 0); - Eigen::Vector3d pos2(2, 0, 0); - Eigen::Vector3d pos3(3, 0, 0); - a.setPosition(0, pos1); - b.setPosition(0, pos2); - c.setPosition(0, pos3); - std::pair<int64_t, int64_t> interval1(0, 1); - std::pair<int64_t, int64_t> interval2(1, 2); - std::pair<int64_t, int64_t> interval3(2, 3); - a.setScanInterval(0, interval1); - b.setScanInterval(0, interval2); - c.setScanInterval(0, interval3); - TS_ASSERT_THROWS_NOTHING(a.merge(b)); - TS_ASSERT_THROWS_NOTHING(a.merge(c)); - TS_ASSERT(a.isScanning()); - TS_ASSERT(!a.isSyncScan()); - TS_ASSERT(!a.isEquivalent(b)); - TS_ASSERT(!a.isEquivalent(c)); - TS_ASSERT_EQUALS(a.size(), 2); - TS_ASSERT_EQUALS(a.scanSize(), 4); - TS_ASSERT_EQUALS(a.scanCount(0), 3); - TS_ASSERT_EQUALS(a.scanInterval({0, 0}), interval1); - TS_ASSERT_EQUALS(a.scanInterval({0, 1}), interval2); - TS_ASSERT_EQUALS(a.scanInterval({0, 2}), interval3); - TS_ASSERT_EQUALS(a.position({0, 0}), pos1); - TS_ASSERT_EQUALS(a.position({0, 1}), pos2); - TS_ASSERT_EQUALS(a.position({0, 2}), pos3); - // Monitor is not scanning - TS_ASSERT_EQUALS(a.scanCount(1), 1); - } - - void test_merge_multiple_sync() { - DetectorInfo a(PosVec(2), RotVec(2), {1}); - auto b(a); - auto c(a); - Eigen::Vector3d pos1(1, 0, 0); - Eigen::Vector3d pos2(2, 0, 0); - Eigen::Vector3d pos3(3, 0, 0); - a.setPosition(0, pos1); - b.setPosition(0, pos2); - c.setPosition(0, pos3); - std::pair<int64_t, int64_t> interval1(0, 1); - std::pair<int64_t, int64_t> interval2(1, 2); - std::pair<int64_t, int64_t> interval3(2, 3); - a.setScanInterval(interval1); - b.setScanInterval(interval2); - c.setScanInterval(interval3); - TS_ASSERT_THROWS_NOTHING(a.merge(b)); - TS_ASSERT_THROWS_NOTHING(a.merge(c)); - TS_ASSERT(a.isScanning()); - TS_ASSERT(a.isSyncScan()); - TS_ASSERT(!a.isEquivalent(b)); - TS_ASSERT(!a.isEquivalent(c)); - TS_ASSERT_EQUALS(a.size(), 2); - TS_ASSERT_EQUALS(a.scanSize(), 6); - TS_ASSERT_EQUALS(a.scanCount(0), 3); - TS_ASSERT_EQUALS(a.scanCount(1), 3); - TS_ASSERT_EQUALS(a.scanInterval({0, 0}), interval1); - TS_ASSERT_EQUALS(a.scanInterval({1, 0}), interval1); - TS_ASSERT_EQUALS(a.scanInterval({0, 1}), interval2); - TS_ASSERT_EQUALS(a.scanInterval({1, 1}), interval2); - TS_ASSERT_EQUALS(a.scanInterval({0, 2}), interval3); - TS_ASSERT_EQUALS(a.scanInterval({1, 2}), interval3); - TS_ASSERT_EQUALS(a.position({0, 0}), pos1); - TS_ASSERT_EQUALS(a.position({0, 1}), pos2); - TS_ASSERT_EQUALS(a.position({0, 2}), pos3); - } - - void test_merge_multiple_associative() { - // Test that (A + B) + C == A + (B + C) - // This is implied by the ordering guaranteed by merge(). - DetectorInfo a1(PosVec(1), RotVec(1)); - a1.setRotation(0, Eigen::Quaterniond::Identity()); - auto b(a1); - auto c(a1); - Eigen::Vector3d pos1(1, 0, 0); - Eigen::Vector3d pos2(2, 0, 0); - Eigen::Vector3d pos3(3, 0, 0); - a1.setPosition(0, pos1); - b.setPosition(0, pos2); - c.setPosition(0, pos3); - std::pair<int64_t, int64_t> interval1(0, 1); - std::pair<int64_t, int64_t> interval2(1, 2); - std::pair<int64_t, int64_t> interval3(2, 3); - a1.setScanInterval(0, interval1); - b.setScanInterval(0, interval2); - c.setScanInterval(0, interval3); - auto a2(a1); - TS_ASSERT_THROWS_NOTHING(a1.merge(b)); - TS_ASSERT_THROWS_NOTHING(a1.merge(c)); - TS_ASSERT_THROWS_NOTHING(b.merge(c)); - TS_ASSERT_THROWS_NOTHING(a2.merge(b)); - TS_ASSERT(a1.isEquivalent(a2)); + DetectorInfo detInfo; + Mantid::Beamline::ComponentInfo compInfo; + detInfo.setComponentInfo(&compInfo); + TS_ASSERT_EQUALS(detInfo.scanCount(), 1); } - void test_merge_multiple_associative_sync() { - // Test that (A + B) + C == A + (B + C) - // This is implied by the ordering guaranteed by merge(). - DetectorInfo a1(PosVec(1), RotVec(1)); - a1.setRotation(0, Eigen::Quaterniond::Identity()); - auto b(a1); - auto c(a1); - Eigen::Vector3d pos1(1, 0, 0); - Eigen::Vector3d pos2(2, 0, 0); - Eigen::Vector3d pos3(3, 0, 0); - a1.setPosition(0, pos1); - b.setPosition(0, pos2); - c.setPosition(0, pos3); - std::pair<int64_t, int64_t> interval1(0, 1); - std::pair<int64_t, int64_t> interval2(1, 2); - std::pair<int64_t, int64_t> interval3(2, 3); - a1.setScanInterval(interval1); - b.setScanInterval(interval2); - c.setScanInterval(interval3); - auto a2(a1); - TS_ASSERT_THROWS_NOTHING(a1.merge(b)); - TS_ASSERT_THROWS_NOTHING(a1.merge(c)); - TS_ASSERT_THROWS_NOTHING(b.merge(c)); - TS_ASSERT_THROWS_NOTHING(a2.merge(b)); - TS_ASSERT(a1.isEquivalent(a2)); + void test_scanIntervals() { + DetectorInfo detInfo; + Mantid::Beamline::ComponentInfo compInfo; + detInfo.setComponentInfo(&compInfo); + TS_ASSERT_EQUALS(detInfo.scanIntervals(), + (std::vector<std::pair<int64_t, int64_t>>{{0, 1}})); } }; diff --git a/Framework/CMakeLists.txt b/Framework/CMakeLists.txt index 868342058a46480b71443ddbdac7234535fb4a95..827cc846716fada58add043a8cca2167865a063f 100644 --- a/Framework/CMakeLists.txt +++ b/Framework/CMakeLists.txt @@ -121,6 +121,7 @@ add_subdirectory (Nexus) add_subdirectory (DataHandling) add_subdirectory (Algorithms) add_subdirectory (WorkflowAlgorithms) +add_subdirectory (Catalog) add_subdirectory (CurveFitting) add_subdirectory (Crystal) add_subdirectory (ICat) @@ -151,7 +152,7 @@ add_subdirectory (ScriptRepository) set ( FRAMEWORK_LIBS Kernel HistogramData Indexing Beamline Geometry API DataObjects PythonInterface DataHandling Nexus NexusGeometry Algorithms CurveFitting ICat - Crystal MDAlgorithms WorkflowAlgorithms + Catalog Crystal MDAlgorithms WorkflowAlgorithms LiveData RemoteAlgorithms RemoteJobManagers SINQ Muon ) diff --git a/Framework/Catalog/CMakeLists.txt b/Framework/Catalog/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..6c3fc0b07d4bf838d34b0a4b7adbf3008eac4e12 --- /dev/null +++ b/Framework/Catalog/CMakeLists.txt @@ -0,0 +1,63 @@ +set ( SRC_FILES + src/ONCat.cpp + src/ONCatEntity.cpp + src/OAuth.cpp +) + +set ( INC_FILES + inc/MantidCatalog/DllConfig.h + inc/MantidCatalog/Exception.h + inc/MantidCatalog/ONCat.h + inc/MantidCatalog/ONCatEntity.h + inc/MantidCatalog/OAuth.h +) + +set ( TEST_FILES + ONCatTest.h + ONCatEntityTest.h + OAuthTest.h +) + +if (COVERALLS) + foreach( loop_var ${SRC_FILES} ${INC_FILES}) + set_property(GLOBAL APPEND PROPERTY COVERAGE_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/${loop_var}") + endforeach(loop_var) +endif() + +if(UNITY_BUILD) + include(UnityBuild) + enable_unity_build(Catalog SRC_FILES SRC_UNITY_IGNORE_FILES 10) +endif(UNITY_BUILD) + +# Add ssl dependency +include_directories ( ${OPENSSL_INCLUDE_DIR} ) +add_definitions ( -DWITH_OPENSSL -DWITH_NONAMESPACES ) + +# Add the target for this directory +add_library ( Catalog ${SRC_FILES} ${INC_FILES}) +# Set the name of the generated library +set_target_properties ( Catalog PROPERTIES OUTPUT_NAME MantidCatalog + COMPILE_DEFINITIONS IN_MANTID_CATALOG +) + +if (OSX_VERSION VERSION_GREATER 10.8) + set_target_properties(Catalog PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS") +elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) + set_target_properties(Catalog PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}") +endif () + +# Add to the 'Framework' group in VS +set_property ( TARGET Catalog PROPERTY FOLDER "MantidFramework" ) + +include_directories ( inc ) + +target_link_libraries ( Catalog LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} ${MANTIDLIBS} ${OPENSSL_LIBRARIES} ${JSONCPP_LIBRARIES}) + +# Add the unit tests directory +add_subdirectory ( test ) + +########################################################################### +# Installation settings +########################################################################### + +install ( TARGETS Catalog ${SYSTEM_PACKAGE_TARGET} DESTINATION ${PLUGINS_DIR} ) diff --git a/Framework/Catalog/inc/MantidCatalog/DllConfig.h b/Framework/Catalog/inc/MantidCatalog/DllConfig.h new file mode 100644 index 0000000000000000000000000000000000000000..80eb1c93dae35a6d4d68e44b55cc820c15bddc83 --- /dev/null +++ b/Framework/Catalog/inc/MantidCatalog/DllConfig.h @@ -0,0 +1,24 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2012 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_CATALOG_DLLCONFIG_H_ +#define MANTID_CATALOG_DLLCONFIG_H_ + +/** + * This file contains the DLLExport/DLLImport linkage configuration for the + * Catalog library + */ +#include "MantidKernel/System.h" + +#ifdef IN_MANTID_CATALOG +#define MANTID_CATALOG_DLL DLLExport +#define EXTERN_MANTID_CATALOG +#else +#define MANTID_CATALOG_DLL DLLImport +#define EXTERN_MANTID_CATALOG EXTERN_IMPORT +#endif /* IN_MANTID_CATALOG */ + +#endif // MANTID_CATALOG_DLLCONFIG_H_ diff --git a/Framework/Catalog/inc/MantidCatalog/Exception.h b/Framework/Catalog/inc/MantidCatalog/Exception.h new file mode 100644 index 0000000000000000000000000000000000000000..c109e0eb4a595c83dd8a2106bd5e5b895d39b63b --- /dev/null +++ b/Framework/Catalog/inc/MantidCatalog/Exception.h @@ -0,0 +1,61 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2012 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_CATALOG_EXCEPTION_H_ +#define MANTID_CATALOG_EXCEPTION_H_ + +#include <stdexcept> + +namespace Mantid { +namespace Catalog { +namespace Exception { + +class CatalogError : public std::runtime_error { +public: + explicit CatalogError(const std::string &message) + : std::runtime_error(message) {} +}; + +class InvalidCredentialsError final : public CatalogError { +public: + explicit InvalidCredentialsError(const std::string &message) + : CatalogError(message) {} +}; + +class TokenRejectedError final : public CatalogError { +public: + explicit TokenRejectedError(const std::string &message) + : CatalogError(message) {} +}; + +class TokenParsingError final : public CatalogError { +public: + explicit TokenParsingError(const std::string &message) + : CatalogError(message) {} +}; + +class InvalidRefreshTokenError final : public CatalogError { +public: + explicit InvalidRefreshTokenError(const std::string &message) + : CatalogError(message) {} +}; + +class MalformedRepresentationError final : public CatalogError { +public: + explicit MalformedRepresentationError(const std::string &message) + : CatalogError(message) {} +}; + +class ContentError : public CatalogError { +public: + explicit ContentError(const std::string &message) : CatalogError(message) {} +}; + +} // namespace Exception +} // namespace Catalog +} // namespace Mantid + +#endif // MANTID_CATALOG_EXCEPTION_H_ diff --git a/Framework/Catalog/inc/MantidCatalog/OAuth.h b/Framework/Catalog/inc/MantidCatalog/OAuth.h new file mode 100644 index 0000000000000000000000000000000000000000..15d2b84a1144f1e72acbd8588f258ba73b57155d --- /dev/null +++ b/Framework/Catalog/inc/MantidCatalog/OAuth.h @@ -0,0 +1,91 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2012 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_CATALOG_OAUTH_H_ +#define MANTID_CATALOG_OAUTH_H_ + +#include "MantidCatalog/DllConfig.h" +#include "MantidKernel/DateAndTime.h" +#include "MantidKernel/make_unique.h" + +#include <boost/optional.hpp> + +namespace Mantid { +namespace Catalog { +namespace OAuth { + +using Types::Core::DateAndTime; + +/** + * Classes providing basic Client Credentials / Resource Owner Credentials + * OAuth functionality. + * + * To be used by other cataloging classes and so it should not be necessary + * to use this directly anywhere else. + */ + +enum class OAuthFlow : uint8_t { + CLIENT_CREDENTIALS, + RESOURCE_OWNER_CREDENTIALS, + NONE, +}; + +class MANTID_CATALOG_DLL OAuthToken { +public: + OAuthToken() = delete; + OAuthToken(const std::string &tokenType, int expiresIn, + const std::string &accessToken, const std::string &scope, + const boost::optional<std::string> &refreshToken); + ~OAuthToken(); + + std::string tokenType() const; + int expiresIn() const; + std::string accessToken() const; + std::string scope() const; + boost::optional<std::string> refreshToken() const; + + bool isExpired() const; + bool isExpired(const DateAndTime ¤tTime) const; + + static OAuthToken fromJSONStream(std::istream &tokenStringStream); + +private: + DateAndTime m_expiresAt; + + std::string m_tokenType; + int m_expiresIn; + std::string m_accessToken; + std::string m_scope; + boost::optional<std::string> m_refreshToken; +}; + +class MANTID_CATALOG_DLL IOAuthTokenStore { +public: + virtual void setToken(const boost::optional<OAuthToken> &token) = 0; + virtual boost::optional<OAuthToken> getToken() = 0; + virtual ~IOAuthTokenStore() = default; +}; + +class MANTID_CATALOG_DLL ConfigServiceTokenStore : public IOAuthTokenStore { +public: + ConfigServiceTokenStore() = default; + ConfigServiceTokenStore & + operator=(const ConfigServiceTokenStore &other) = default; + ~ConfigServiceTokenStore() override; + + void setToken(const boost::optional<OAuthToken> &token) override; + boost::optional<OAuthToken> getToken() override; +}; + +using IOAuthTokenStore_uptr = std::unique_ptr<IOAuthTokenStore>; +using IOAuthTokenStore_sptr = std::shared_ptr<IOAuthTokenStore>; +using ConfigServiceTokenStore_uptr = std::unique_ptr<ConfigServiceTokenStore>; + +} // namespace OAuth +} // namespace Catalog +} // namespace Mantid + +#endif /* MANTID_CATALOG_OAUTH_H_ */ diff --git a/Framework/Catalog/inc/MantidCatalog/ONCat.h b/Framework/Catalog/inc/MantidCatalog/ONCat.h new file mode 100644 index 0000000000000000000000000000000000000000..8167a39a003b87aabc716a145f6f258c5f8bc8b5 --- /dev/null +++ b/Framework/Catalog/inc/MantidCatalog/ONCat.h @@ -0,0 +1,149 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2012 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_CATALOG_ONCAT_H_ +#define MANTID_CATALOG_ONCAT_H_ + +#include "MantidCatalog/DllConfig.h" +#include "MantidCatalog/OAuth.h" +#include "MantidCatalog/ONCatEntity.h" +#include "MantidKernel/DateAndTime.h" + +#include <memory> +#include <vector> + +#include <boost/optional.hpp> + +namespace Mantid { + +namespace Kernel { +class InternetHelper; +} + +namespace Catalog { +namespace ONCat { + +using Mantid::Catalog::OAuth::IOAuthTokenStore_sptr; +using Mantid::Catalog::OAuth::IOAuthTokenStore_uptr; +using Mantid::Catalog::OAuth::OAuthFlow; +using Types::Core::DateAndTime; + +// Here we use a vector of pairs rather than a map because we would like +// the ability to set a parameter with a given name more than once -- +// this denotes an arrayed parameter. +using QueryParameter = std::pair<std::string, std::string>; +using QueryParameters = std::vector<QueryParameter>; + +/** + * The main class to be used when interacting with ONCat from C++. It can + * be used to retrieve "entities" from REST-like "resources". Please refer + * to the API documentation at https://oncat.ornl.gov/ for more information + * about each resource. + * + * Rather than use constructors, the helper method ONCat::fromMantidSettings() + * is strongly recommended. This will create an ONCat object taking into + * account the settings configured in the currently-running instance of Mantid. + * + * Creation of an ONCat object can be done as follows: + * + * auto oncat = ONCat::fromMantidSettings(); + * + * Or, when *unauthenticated* access is preferred, as follows: + * + * auto oncat = ONCat::fromMantidSettings(false); + * + * Once you have that, logging in either assumes that a client ID and + * client secret have been added to the Mantid.local.properties file (this + * essentially allows machine-to-machine authentication for a use case like + * auto-reduction, and there is no explicit "login" step), or that you are able + * to pronpt a user for their ORNL XCAMS / UCAMS username and password, or that + * you will only be accessing resources in an unauthenticated manner. If an + * explicit login step is necessary, it should look something like this: + * + * oncat.login("some_user", "a_password"); + * + * From then on, basic usage is as per the following example: + * + * // Get a list of the experiments for NOMAD, specifying the fields + * // we are interested in as a "projection". + * const auto nomadExperiments = oncat.list("api", "experiments", { + * QueryParameter("facility", "SNS"), + * QueryParameter("instrument", "NOM"), + * QueryParameter("projection", "name"), + * QueryParameter("projection", "size") + * }); + * + * // Print out the IPTS numbers of each one. + * for (const auto & experiment : nomadExperiments) { + * std::cout + * << *experiment.get<std::string>("name") << " has " + * << *experiment.get<int>("size") << " ingested datafiles."; + * } + * + * For logged-in users, no further credential prompting should be required + * as part of the standard workflow, although you should be prepared for + * an authenticated user to have their tokens invalidated *eventually*, as + * refresh tokens will *not* last forever (and may eventually be set to expire + * every 12 hours or so). Once tokens are expired, any call to the API will + * fail, and an error will be written to the log asking the user to login + # again. + * + * @author Peter Parker + * @date 2018 +*/ +class MANTID_CATALOG_DLL ONCat { +public: + static ONCat fromMantidSettings(bool authenticate = true); + + ONCat() = delete; + ONCat(const ONCat &other); + ~ONCat(); + + bool isUserLoggedIn() const; + + void login(const std::string &username, const std::string &password); + void logout(); + + ONCatEntity retrieve(const std::string &resourceNamespace, + const std::string &resource, + const std::string &identifier, + const QueryParameters &queryParameters); + std::vector<ONCatEntity> list(const std::string &resourceNamespace, + const std::string &resource, + const QueryParameters &queryParameters); + + ////////////////////////////////////////////////////////////////////// + // Exposed publicly for testing purposes only. + ////////////////////////////////////////////////////////////////////// + ONCat(const std::string &url); + ONCat(const std::string &url, IOAuthTokenStore_uptr tokenStore, + OAuthFlow flow, const boost::optional<std::string> &clientId, + const boost::optional<std::string> &clientSecret = boost::none); + void refreshTokenIfNeeded(); + void refreshTokenIfNeeded(const DateAndTime ¤tTime); + void setInternetHelper( + const std::shared_ptr<Mantid::Kernel::InternetHelper> &internetHelper); + ////////////////////////////////////////////////////////////////////// + +private: + void sendAPIRequest(const std::string &uri, + const QueryParameters &queryParameters, + std::ostream &response); + + std::string m_url; + IOAuthTokenStore_sptr m_tokenStore; + boost::optional<std::string> m_clientId; + boost::optional<std::string> m_clientSecret; + + OAuthFlow m_flow; + std::shared_ptr<Mantid::Kernel::InternetHelper> m_internetHelper; +}; + +} // namespace ONCat +} // namespace Catalog +} // namespace Mantid + +#endif /* MANTID_CATALOG_ONCAT_H_ */ diff --git a/Framework/Catalog/inc/MantidCatalog/ONCatEntity.h b/Framework/Catalog/inc/MantidCatalog/ONCatEntity.h new file mode 100644 index 0000000000000000000000000000000000000000..37592c953ad95b50fe79783c5fa2ee7d8cc4ddac --- /dev/null +++ b/Framework/Catalog/inc/MantidCatalog/ONCatEntity.h @@ -0,0 +1,134 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2012 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_CATALOG_ONCATENTITY_H_ +#define MANTID_CATALOG_ONCATENTITY_H_ + +#include "MantidCatalog/DllConfig.h" +#include "MantidCatalog/Exception.h" +#include "MantidKernel/make_unique.h" + +#include <string> +#include <vector> + +#include <boost/optional.hpp> +#include <json/json.h> + +namespace Mantid { +namespace Catalog { +namespace ONCat { + +using Content = Json::Value; +using Content_uptr = std::unique_ptr<Content>; +using Mantid::Catalog::Exception::ContentError; + +/** + * A class to encapsulate the "entity" responses received from the ONCat API. + * + * An ONCatEntity object (or a vector of objects) can be constructed when + * given an istream, which is assumed to contain JSON information as defined + * in the API documentation at https://oncat.ornl.gov/#/build. + * + * Note that there are only two fields shared across all API entity types: + * "id" and "type". Further, all other fields can be optionally disabled + * through the use of "projections", and a certain subset of fields may even + * be completely missing for a given file because of the dynamic nature of + * metadata resulting from Data Acquisition software changes. + * + * For this reason, all other metadata will be retrieved in a way that + * forces you to deal with the case where the field in question is not there. + * There are two ways of doing this: the first is to specify a default value + * to be used when a value is not present, and the second is to check for a + * result on a boost::optional. + * + * However, if your projection is such that you *know* a field will be present + * (note that most fields on API resources will always be returned as long + * as they are requested as part of a projection, for example the "location" + * field of the Datafile resource), then feel free to assume it will be + * there and resolve the boost::optional without checking for a result. + * + * @author Peter Parker + * @date 2018 + */ +class MANTID_CATALOG_DLL ONCatEntity { +public: + ONCatEntity() = delete; + ONCatEntity(const ONCatEntity &); + ~ONCatEntity(); + + // These are the only two fields the ONCat API guarantees will be there + // across *all* entity types. + std::string id() const; + std::string type() const; + + // For all other fields, you can either supply a default value for when a + // value does not exist ... + template <typename T> T get(const std::string &path, T defaultValue) const { + try { + return getNestedContentValueAsType<T>(*m_content, path); + } catch (ContentError &) { + return defaultValue; + } + } + + // ... or, write conditional logic around boost's optional results. + template <typename T> boost::optional<T> get(const std::string &path) const { + try { + return boost::make_optional( + getNestedContentValueAsType<T>(*m_content, path)); + } catch (ContentError &) { + return boost::none; + } + } + + std::string toString() const; + + static ONCatEntity fromJSONStream(std::istream &streamContent); + static std::vector<ONCatEntity> + vectorFromJSONStream(std::istream &streamContent); + +private: + ONCatEntity(const std::string &id, const std::string &type, + Content_uptr content); + + template <typename T> + T getNestedContentValueAsType(const Content &content, + const std::string &path) const; + + Content getNestedContent(const Content &content, + const std::string &path) const; + + std::string m_id; + std::string m_type; + Content_uptr m_content; +}; + +template <> +MANTID_CATALOG_DLL std::string +ONCatEntity::getNestedContentValueAsType(const Content &content, + const std::string &path) const; +template <> +MANTID_CATALOG_DLL int +ONCatEntity::getNestedContentValueAsType(const Content &content, + const std::string &path) const; +template <> +MANTID_CATALOG_DLL float +ONCatEntity::getNestedContentValueAsType(const Content &content, + const std::string &path) const; +template <> +MANTID_CATALOG_DLL double +ONCatEntity::getNestedContentValueAsType(const Content &content, + const std::string &path) const; +template <> +MANTID_CATALOG_DLL bool +ONCatEntity::getNestedContentValueAsType(const Content &content, + const std::string &path) const; + +} // namespace ONCat +} // namespace Catalog +} // namespace Mantid + +#endif /* MANTID_CATALOG_ONCATENTITY_H_ */ diff --git a/Framework/Catalog/src/OAuth.cpp b/Framework/Catalog/src/OAuth.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a2eeaedd1d6e4ab9f7ac87eebddabeb6fac4407e --- /dev/null +++ b/Framework/Catalog/src/OAuth.cpp @@ -0,0 +1,163 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2012 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidCatalog/OAuth.h" +#include "MantidCatalog/Exception.h" +#include "MantidKernel/ConfigService.h" + +#include <sstream> + +#include <Poco/Net/HTMLForm.h> +#include <Poco/Net/HTTPResponse.h> + +#include <json/json.h> + +namespace Mantid { +namespace Catalog { +namespace OAuth { + +using Mantid::Catalog::Exception::TokenParsingError; + +//---------------------------------------------------------------------- +// OAuthToken +//---------------------------------------------------------------------- + +OAuthToken::OAuthToken(const std::string &tokenType, int expiresIn, + const std::string &accessToken, const std::string &scope, + const boost::optional<std::string> &refreshToken) + : m_expiresAt(DateAndTime::getCurrentTime() + + static_cast<double>(expiresIn)), + m_tokenType(tokenType), m_expiresIn(expiresIn), + m_accessToken(accessToken), m_scope(scope), m_refreshToken(refreshToken) { +} + +OAuthToken::~OAuthToken() {} + +OAuthToken OAuthToken::fromJSONStream(std::istream &tokenStringStream) { + try { + Json::Value full_token; + tokenStringStream >> full_token; + + const auto tokenType = full_token["token_type"].asString(); + const auto expiresIn = + static_cast<unsigned int>(full_token["expires_in"].asUInt()); + const auto accessToken = full_token["access_token"].asString(); + const auto scope = full_token["scope"].asString(); + + const auto parsedRefreshToken = full_token["refresh_token"].asString(); + const boost::optional<std::string> refreshToken = + parsedRefreshToken == "" + ? boost::none + : boost::optional<std::string>(parsedRefreshToken); + + return OAuthToken(tokenType, expiresIn, accessToken, scope, refreshToken); + } catch (...) { + throw TokenParsingError("Unable to parse authentication token!"); + } +} + +bool OAuthToken::isExpired() const { + return isExpired(DateAndTime::getCurrentTime()); +} + +bool OAuthToken::isExpired(const DateAndTime ¤tTime) const { + return currentTime > m_expiresAt; +} + +std::string OAuthToken::tokenType() const { return m_tokenType; } + +int OAuthToken::expiresIn() const { return m_expiresIn; } + +std::string OAuthToken::accessToken() const { return m_accessToken; } + +std::string OAuthToken::scope() const { return m_scope; } + +boost::optional<std::string> OAuthToken::refreshToken() const { + return m_refreshToken; +} + +//---------------------------------------------------------------------- +// ConfigServiceTokenStore +//---------------------------------------------------------------------- + +namespace { +static const std::string CONFIG_PATH_BASE = "catalog.oncat.token."; +} + +ConfigServiceTokenStore::~ConfigServiceTokenStore() { + try { + // Here we attempt to persist our OAuth token to disk before the + // up-until-now only-in-memory token store is destroyed. + // + // I don't believe this is a great solution. Some things to + // consider: + // + // * We have to save the *entire* contents of the config. This may + // not be desirable. + // * Ideally we would persist on every token set. + auto &config = Mantid::Kernel::ConfigService::Instance(); + config.saveConfig(config.getUserFilename()); + } catch (...) { + // It's not the end of the world if there was an error persisting + // the token (the worst that could happen is a user has to login + // again), but it *is* the end of the world if we seg fault. + } +} + +void ConfigServiceTokenStore::setToken( + const boost::optional<OAuthToken> &token) { + auto &config = Mantid::Kernel::ConfigService::Instance(); + + if (token) { + config.setString(CONFIG_PATH_BASE + "tokenType", token->tokenType()); + config.setString(CONFIG_PATH_BASE + "expiresIn", + std::to_string(token->expiresIn())); + config.setString(CONFIG_PATH_BASE + "accessToken", token->accessToken()); + config.setString(CONFIG_PATH_BASE + "scope", token->scope()); + config.setString(CONFIG_PATH_BASE + "refreshToken", + token->refreshToken() ? *token->refreshToken() + : std::string("")); + } else { + config.setString(CONFIG_PATH_BASE + "tokenType", ""); + config.setString(CONFIG_PATH_BASE + "expiresIn", ""); + config.setString(CONFIG_PATH_BASE + "accessToken", ""); + config.setString(CONFIG_PATH_BASE + "scope", ""); + config.setString(CONFIG_PATH_BASE + "refreshToken", ""); + } +} + +boost::optional<OAuthToken> ConfigServiceTokenStore::getToken() { + auto &config = Mantid::Kernel::ConfigService::Instance(); + + const auto tokenType = config.getString(CONFIG_PATH_BASE + "tokenType"); + const auto expiresIn = config.getString(CONFIG_PATH_BASE + "expiresIn"); + const auto accessToken = config.getString(CONFIG_PATH_BASE + "accessToken"); + const auto scope = config.getString(CONFIG_PATH_BASE + "scope"); + const auto refreshToken = config.getString(CONFIG_PATH_BASE + "refreshToken"); + + // A partially written-out token is useless and is therefore + // effectively the same as a token not having been written out at + // all. So, it's all or nothing (excluding the refresh token of + // course, which is not present for all OAuth flows). + if (tokenType == "" || expiresIn == "" || accessToken == "" || scope == "") { + return boost::none; + } + + try { + return boost::make_optional( + OAuthToken(tokenType, std::stoi(expiresIn), accessToken, scope, + boost::make_optional(refreshToken != "", refreshToken))); + } catch (std::invalid_argument &) { + // Catching any std::stoi failures silently -- a malformed token is + // useless and may as well not be there. + } + + return boost::none; +} + +} // namespace OAuth +} // namespace Catalog +} // namespace Mantid diff --git a/Framework/Catalog/src/ONCat.cpp b/Framework/Catalog/src/ONCat.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1fd54583b742afbc9cfe2e7d7bbca27ef315e76a --- /dev/null +++ b/Framework/Catalog/src/ONCat.cpp @@ -0,0 +1,468 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2012 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidCatalog/ONCat.h" +#include "MantidCatalog/Exception.h" +#include "MantidCatalog/OAuth.h" +#include "MantidKernel/ConfigService.h" +#include "MantidKernel/Exception.h" +#include "MantidKernel/InternetHelper.h" +#include "MantidKernel/Logger.h" +#include "MantidKernel/make_unique.h" + +#include <algorithm> +#include <iostream> +#include <sstream> + +#include <boost/algorithm/string/join.hpp> + +#include <Poco/Net/HTMLForm.h> +#include <Poco/Net/HTTPResponse.h> + +namespace Mantid { +namespace Catalog { +namespace ONCat { + +using Poco::Net::HTTPResponse; + +using Mantid::Catalog::Exception::CatalogError; +using Mantid::Catalog::Exception::InvalidCredentialsError; +using Mantid::Catalog::Exception::InvalidRefreshTokenError; +using Mantid::Catalog::Exception::TokenRejectedError; +using Mantid::Catalog::OAuth::ConfigServiceTokenStore; +using Mantid::Catalog::OAuth::OAuthToken; +using Mantid::Catalog::ONCat::ONCatEntity; +using Mantid::Kernel::Exception::InternetError; + +namespace { +Mantid::Kernel::Logger g_log("ONCat"); + +static const std::string CONFIG_PATH_BASE = "catalog.oncat."; +// It could be argued that this should be read in from Facilities.xml or +// similar, but I will put this off for now as it is unclear how to +// reconcile ONCat's functionality with the current <soapendpoint> / +// <externaldownload> tags in the XML. +static const std::string DEFAULT_ONCAT_URL = "https://oncat.ornl.gov"; +static const std::string DEFAULT_CLIENT_ID = + "d16ea847-41ce-4b30-9167-40298588e755"; +} // namespace + +/** + * Constructs an ONCat object based on various settings gathered from + * the ConfigService. + * + * The resulting object will work with resources that require no + * authentication at all, or assuming authentication is to be done in one of + * two possible modes: + * + * 1 - User Login Mode (Default) + * ----------------------------- + * + * Users must log in with their UCAMS / XCAMS credentials before calls + * to the ONCat API can be made. This mode should work "out of the box" + * (requires no changes to config files), and is the default mode of + * operation when authenticating. User access to API information is governed + * by the same LDAP instance that controls file system access, so users should + * only see the experiment data they are allowed to see. + * + * This mode uses the "Resource Owner Credentials" OAuth flow. + * + * 2 - Machine-to-Machine Mode + * --------------------------- + * + * No user login is necessary in this case, but for this mode to be enabled a + * client ID and secret must exist in the ConfigService. Recommended practice + * would be to add the following two entries to the Mantid.local.properties + * file on the machine to be given access, using the credentials issued by the + * ONCat administrator: + * + * catalog.oncat.client_id = "[CLIENT ID]" + * catalog.oncat.client_secret = "[CLIENT SECRET]" + * + * API read access is completely unrestricted in this mode, and so it is + * intended for autoreduction use cases or similar. + * + * This mode uses the "Client Credentials" OAuth flow. + * + * @return The constructed ONCat object. + */ +ONCat ONCat::fromMantidSettings(bool authenticate) { + if (!authenticate) { + return ONCat(DEFAULT_ONCAT_URL, nullptr, OAuthFlow::NONE, boost::none, + boost::none); + } + + auto &config = Mantid::Kernel::ConfigService::Instance(); + const auto client_id = config.getString(CONFIG_PATH_BASE + "client_id"); + const auto client_secret = + config.getString(CONFIG_PATH_BASE + "client_secret"); + const bool hasClientCredentials = client_id != "" && client_secret != ""; + + if (hasClientCredentials) { + g_log.debug() << "Found client credentials in Mantid.local.properties. " + << "No user login required." << std::endl; + } else { + g_log.debug() + << "Could not find client credentials in Mantid.local.properties. " + << "Falling back to default -- user login required." << std::endl; + } + + return ONCat(DEFAULT_ONCAT_URL, + Mantid::Kernel::make_unique<ConfigServiceTokenStore>(), + hasClientCredentials ? OAuthFlow::CLIENT_CREDENTIALS + : OAuthFlow::RESOURCE_OWNER_CREDENTIALS, + hasClientCredentials ? client_id : DEFAULT_CLIENT_ID, + boost::make_optional(hasClientCredentials, client_secret)); +} + +ONCat::ONCat(const std::string &url, IOAuthTokenStore_uptr tokenStore, + OAuthFlow flow, const boost::optional<std::string> &clientId, + const boost::optional<std::string> &clientSecret) + : m_url(url), m_tokenStore(std::move(tokenStore)), m_clientId(clientId), + m_clientSecret(clientSecret), m_flow(flow), + m_internetHelper(new Mantid::Kernel::InternetHelper()) {} + +ONCat::ONCat(const ONCat &other) + : m_url(other.m_url), m_tokenStore(other.m_tokenStore), + m_clientId(other.m_clientId), m_clientSecret(other.m_clientSecret), + m_flow(other.m_flow), m_internetHelper(other.m_internetHelper) {} + +ONCat::~ONCat() {} + +/** + * Whether or not a user is currently logged in. (Not relevant when + * using machine-to-machine authentication as part of the Client + * Credentials flow, and not required when accessing unauthenticated + * parts of the API.) + * + * Something to bear in mind is that the term "logged in" is used quite + * loosely here. In an OAuth context it roughly equates to, "there is a + * token stored locally", which is not quite the same thing. This may + * sound strange, but consider the following: + * + * - Tokens expire after a given amount of time, at which point they can + * be "refreshed". A successful token refresh happens behind the + * scenes without the user even knowing it took place. + * + * - While it is possible to tell when a token needs to be refreshed, + * token refreshes are not always successful. If they fail then the + * client must prompt the user to enter their credentials again. + * + * - There is no way for the client to know whether or not the refresh + * will be successful ahead of time (i.e., whether a token has been + * revoked server-side), since the OAuth spec provides no mechanism to + * check the validity of a refresh token. + * + * - Tokens can be revoked at any time with absolutely no notice as part + * of standard OAuth practice. Also, only a limited number of tokens + * can exist for each unique client / user combination at any one + * time. + * + * Hopefully it is clear that working with OAuth client-side requires + * you to use an almost-Pythonic "ask for forgiveness rather than for + * permission" strategy -- i.e., code as if locally-stored tokens can be + * refreshed, but be ready to prompt the user for their credentials if + * the refresh fails. + * + * Some useful links with related information: + * + * - http://qr.ae/TUTke2 (quora.com) + * - https://stackoverflow.com/a/30826806/778572 + * + * @param true if a user is "logged in", else false. + */ +bool ONCat::isUserLoggedIn() const { + if (m_flow == OAuthFlow::NONE || m_flow == OAuthFlow::CLIENT_CREDENTIALS) { + return false; + } + + return m_tokenStore->getToken().is_initialized(); +} + +void ONCat::logout() { + // Currently, ONCat OAuth does *not* allow clients to revoke tokens + // that are no longer needed (though this is defined in the OAtuh + // spec). A "logout", then, is simply throwing away whatever token we + // previously stored client-side. + if (m_tokenStore) { + m_tokenStore->setToken(boost::none); + g_log.debug() << "Logging out." << std::endl; + } +} + +/** + * Log in as part of the Resource Ownder Credentials flow so that + * authenticated resources may be accessed on behalf of a user. + * + * @param username : The XCAMS / UCAMS ID of the user. + * @param password : The XCAMS / UCAMS password of the user. + * + * @exception Mantid::Catalog::Exception::InvalidCredentialsError : + * Thrown when the given credentials are not valid. + */ +void ONCat::login(const std::string &username, const std::string &password) { + if (m_flow != OAuthFlow::RESOURCE_OWNER_CREDENTIALS) { + g_log.warning() + << "Unexpected usage detected! " + << "Logging in with user credentials in not required (and is not " + << "supported) unless resource owner credentials are being used." + << std::endl; + return; + } + + Poco::Net::HTMLForm form(Poco::Net::HTMLForm::ENCODING_MULTIPART); + form.set("username", username); + form.set("password", password); + form.set("client_id", m_clientId.get()); + if (m_clientSecret) { + form.set("client_secret", m_clientSecret.get()); + } + form.set("grant_type", "password"); + + m_internetHelper->setBody(form); + + try { + std::stringstream ss; + + const int statusCode = + m_internetHelper->sendRequest(m_url + "/oauth/token", ss); + + if (statusCode == HTTPResponse::HTTP_OK) { + m_tokenStore->setToken(OAuthToken::fromJSONStream(ss)); + } + + g_log.debug() << "Login was successful!" << std::endl; + ; + } catch (InternetError &ie) { + if (ie.errorCode() == HTTPResponse::HTTP_UNAUTHORIZED) { + throw InvalidCredentialsError( + "Invalid UCAMS / XCAMS credentials used for ONCat login."); + } + throw CatalogError(ie.what()); + } +} + +/** + * Retrieve a single entity from the given resource (in the given namespace) of + * ONCat's API. + * + * Please see https://oncat.ornl.gov/#/build for more information about the + * currently-available resources, and what query parameters they allow. + * + * @param identifier : + * The ID or name that uniquely identifies the entity. + * @param resourceNamespace : + * The "namespace" of the resource. The most common, "core" resources all + * belong to the "api" namespace. + * @param resource : + * The name of the resource to retrieve the entity from. I.e., "Datafile" + * entities can be retrieved from the "datafiles" resource. + * @param queryParameters : + * The name-value-pair query-string parameters. + * + * @return The response from the API in the form on an ONCatEntity object. + * + * @exception Mantid::Catalog::Exception::CatalogError + */ +ONCatEntity ONCat::retrieve(const std::string &resourceNamespace, + const std::string &resource, + const std::string &identifier, + const QueryParameters &queryParameters) { + const auto uri = + m_url + "/" + resourceNamespace + "/" + resource + "/" + identifier; + std::stringstream ss; + + sendAPIRequest(uri, queryParameters, ss); + + return ONCatEntity::fromJSONStream(ss); +} + +/** + * Retrieve a collection of entities from the given resource (in the given + * namespace) of ONCat's API. + * + * Please see retrieve documentation for more info. + */ +std::vector<ONCatEntity> ONCat::list(const std::string &resourceNamespace, + const std::string &resource, + const QueryParameters &queryParameters) { + const auto uri = m_url + "/" + resourceNamespace + "/" + resource; + std::stringstream ss; + + sendAPIRequest(uri, queryParameters, ss); + + return ONCatEntity::vectorFromJSONStream(ss); +} + +/** + * Refresh the current token if it has expired (and if it actually exists). + * + * To be called behind-the-scenes before each API query, so that we know + * our tokens are up-to-date before being used. + * + * @exception Mantid::Catalog::Exception::InvalidCredentialsError : + * Thrown when the provider decides the current token cannot be refreshed. + */ +void ONCat::refreshTokenIfNeeded() { + refreshTokenIfNeeded(DateAndTime::getCurrentTime()); +} + +/** + * See overloaded method. + * + * @param currentTime : Used in testing to specify a different time. + * + * @exception Mantid::Catalog::Exception::InvalidRefreshTokenError : + * Thrown when the provider decides the current token cannot be refreshed. + */ +void ONCat::refreshTokenIfNeeded(const DateAndTime ¤tTime) { + if (m_flow == OAuthFlow::NONE) { + return; + } + + const auto currentToken = m_tokenStore->getToken(); + + if (m_flow == OAuthFlow::CLIENT_CREDENTIALS) { + if (currentToken && !currentToken->isExpired(currentTime)) { + return; + } + + Poco::Net::HTMLForm form(Poco::Net::HTMLForm::ENCODING_MULTIPART); + form.set("client_id", m_clientId.get()); + if (m_clientSecret) { + form.set("client_secret", m_clientSecret.get()); + } + form.set("grant_type", "client_credentials"); + + m_internetHelper->reset(); + m_internetHelper->setBody(form); + + try { + std::stringstream ss; + + const int statusCode = + m_internetHelper->sendRequest(m_url + "/oauth/token", ss); + + if (statusCode == HTTPResponse::HTTP_OK) { + m_tokenStore->setToken(OAuthToken::fromJSONStream(ss)); + } + g_log.debug() << "Token successfully refreshed." << std::endl; + } catch (InternetError &ie) { + throw CatalogError(ie.what()); + } + } else if (m_flow == OAuthFlow::RESOURCE_OWNER_CREDENTIALS) { + if (!currentToken.is_initialized()) { + return; + } + if (!currentToken->isExpired(currentTime)) { + return; + } + const auto currentRefreshToken = currentToken->refreshToken(); + if (!currentRefreshToken) { + return; + } + + Poco::Net::HTMLForm form(Poco::Net::HTMLForm::ENCODING_MULTIPART); + form.set("client_id", m_clientId.get()); + if (m_clientSecret) { + form.set("client_secret", m_clientSecret.get()); + } + form.set("grant_type", "refresh_token"); + form.set("refresh_token", currentRefreshToken.get()); + + m_internetHelper->reset(); + m_internetHelper->setBody(form); + + try { + std::stringstream ss; + + const int statusCode = + m_internetHelper->sendRequest(m_url + "/oauth/token", ss); + + if (statusCode == HTTPResponse::HTTP_OK) { + m_tokenStore->setToken(OAuthToken::fromJSONStream(ss)); + } + g_log.debug() << "Token successfully refreshed." << std::endl; + } catch (InternetError &ie) { + if (ie.errorCode() == HTTPResponse::HTTP_UNAUTHORIZED) { + // As per OAuth spec, when a refresh token is no longer valid, we + // can consider ourselves logged out. + logout(); + throw InvalidRefreshTokenError( + "You have been logged out. Please login again."); + } + throw CatalogError(ie.what()); + } + } +} + +void ONCat::setInternetHelper( + const std::shared_ptr<Mantid::Kernel::InternetHelper> &internetHelper) { + m_internetHelper = internetHelper; +} + +void ONCat::sendAPIRequest(const std::string &uri, + const QueryParameters &queryParameters, + std::ostream &response) { + refreshTokenIfNeeded(); + + m_internetHelper->clearHeaders(); + m_internetHelper->setMethod("GET"); + + if (m_flow != OAuthFlow::NONE) { + const auto tokenType = m_tokenStore->getToken()->tokenType(); + const auto accessToken = m_tokenStore->getToken()->accessToken(); + + m_internetHelper->addHeader("Authorization", tokenType + " " + accessToken); + } + + std::vector<std::string> queryStringParts(queryParameters.size()); + std::transform(queryParameters.begin(), queryParameters.end(), + queryStringParts.begin(), + [](const QueryParameter &queryParameter) -> std::string { + return queryParameter.first + "=" + queryParameter.second; + }); + const auto queryString = boost::algorithm::join(queryStringParts, "&"); + const auto url = queryString.size() == 0 ? uri : uri + "?" + queryString; + + g_log.debug() << "About to make a call to the following ONCat URL: " << url; + + try { + m_internetHelper->sendRequest(url, response); + } catch (InternetError &ie) { + if (ie.errorCode() == HTTPResponse::HTTP_UNAUTHORIZED) { + std::string errorMessage; + switch (m_flow) { + case OAuthFlow::RESOURCE_OWNER_CREDENTIALS: + errorMessage = "You have been logged out. Please login again."; + break; + case OAuthFlow::CLIENT_CREDENTIALS: + errorMessage = + "The stored OAuth token appears to be invalid. " + "There are a few cases where this might be expected, but in " + "principle this should rarely happen. " + "Please try again and if the problem persists contact the " + "ONCat administrator at oncat-support@ornl.gov."; + break; + case OAuthFlow::NONE: + assert(false); + break; + } + // The ONCat API does *not* leak information in the case where a + // resource exists but a user is not allowed access -- a 404 would + // always be returned instead. So, if we ever get a 401, it is + // because our locally-stored token is no longer valid and we + // should log out. + logout(); + throw TokenRejectedError(errorMessage); + } + throw CatalogError(ie.what()); + } +} + +} // namespace ONCat +} // namespace Catalog +} // namespace Mantid diff --git a/Framework/Catalog/src/ONCatEntity.cpp b/Framework/Catalog/src/ONCatEntity.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b7f51dfd9af1486537b0176b043d792e48f0b723 --- /dev/null +++ b/Framework/Catalog/src/ONCatEntity.cpp @@ -0,0 +1,142 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2012 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidCatalog/ONCatEntity.h" +#include "MantidCatalog/Exception.h" +#include "MantidKernel/StringTokenizer.h" + +#include <iostream> +#include <sstream> + +namespace Mantid { +namespace Catalog { +namespace ONCat { + +using Mantid::Catalog::Exception::MalformedRepresentationError; +using Mantid::Kernel::StringTokenizer; +using Mantid::Kernel::make_unique; + +ONCatEntity::ONCatEntity(const std::string &id, const std::string &type, + Content_uptr content) + : m_id(id), m_type(type), m_content(std::move(content)) {} + +ONCatEntity::ONCatEntity(const ONCatEntity &other) + : m_id(other.m_id), m_type(other.m_type), + m_content(make_unique<Content>(*other.m_content)) {} + +ONCatEntity::~ONCatEntity() {} + +std::string ONCatEntity::id() const { return m_id; } + +std::string ONCatEntity::type() const { return m_type; } + +std::string ONCatEntity::toString() const { + return m_content->toStyledString(); +} + +ONCatEntity ONCatEntity::fromJSONStream(std::istream &streamContent) { + auto content = make_unique<Content>(); + + try { + streamContent >> *content; + } catch (Json::Exception &je) { + throw MalformedRepresentationError(je.what()); + } + + const auto id = content->get("id", "").asString(); + const auto type = content->get("type", "").asString(); + + if (id == "" || type == "") { + throw MalformedRepresentationError( + "Expected \"id\" and \"type\" attributes from ONCat API, but these " + "were not found."); + } + + return ONCatEntity(id, type, std::move(content)); +} + +std::vector<ONCatEntity> +ONCatEntity::vectorFromJSONStream(std::istream &streamContent) { + auto content = make_unique<Content>(); + + try { + streamContent >> *content; + } catch (Json::Exception &je) { + throw MalformedRepresentationError(je.what()); + } + + if (!content->isArray()) { + throw MalformedRepresentationError( + "Expected JSON representation to be an array of entities."); + } + + std::vector<ONCatEntity> entities; + + for (const auto &subContent : *content) { + const auto id = subContent.get("id", "").asString(); + const auto type = subContent.get("type", "").asString(); + + if (id == "" || type == "") { + throw MalformedRepresentationError( + "Expected \"id\" and \"type\" attributes from ONCat API, but these " + "were not found."); + } + + entities.push_back(ONCatEntity(id, type, make_unique<Content>(subContent))); + } + + return entities; +} + +template <> +std::string +ONCatEntity::getNestedContentValueAsType(const Content &content, + const std::string &path) const { + return getNestedContent(content, path).asString(); +} +template <> +int ONCatEntity::getNestedContentValueAsType(const Content &content, + const std::string &path) const { + return getNestedContent(content, path).asInt(); +} +template <> +float ONCatEntity::getNestedContentValueAsType(const Content &content, + const std::string &path) const { + return getNestedContent(content, path).asFloat(); +} +template <> +double ONCatEntity::getNestedContentValueAsType(const Content &content, + const std::string &path) const { + return getNestedContent(content, path).asDouble(); +} +template <> +bool ONCatEntity::getNestedContentValueAsType(const Content &content, + const std::string &path) const { + return getNestedContent(content, path).asBool(); +} + +Content ONCatEntity::getNestedContent(const Content &content, + const std::string &path) const { + const auto pathTokens = + StringTokenizer(path, ".", Mantid::Kernel::StringTokenizer::TOK_TRIM); + + auto currentNode = content; + + // Use the path tokens to drill down through the JSON nodes. + for (auto pathToken = pathTokens.cbegin(); pathToken != pathTokens.cend(); + ++pathToken) { + if (!currentNode.isMember(*pathToken)) { + throw ContentError(""); + } + currentNode = currentNode[*pathToken]; + } + + return currentNode; +} + +} // namespace ONCat +} // namespace Catalog +} // namespace Mantid diff --git a/Framework/Catalog/test/CMakeLists.txt b/Framework/Catalog/test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..9057de7df13b00dfc79ddb5c5558e8b6b6696444 --- /dev/null +++ b/Framework/Catalog/test/CMakeLists.txt @@ -0,0 +1,20 @@ +if ( CXXTEST_FOUND ) + include_directories ( SYSTEM ${CXXTEST_INCLUDE_DIR} ) + + include_directories ( ../../TestHelpers/inc ) + # This variable is used within the cxxtest_add_test macro to build this helper class into the test executable. + # It will go out of scope at the end of this file so doesn't need un-setting + # set ( TESTHELPER_SRCS CatalogICatTestHelper.cpp + # ../../TestHelpers/src/TearDownWorld.cpp + # ) + + # The actual test suite + cxxtest_add_test ( CatalogTest ${TEST_FILES} ) + target_link_libraries( CatalogTest LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} ${MANTIDLIBS} + Catalog + ) + add_dependencies ( FrameworkTests CatalogTest ) + + # Add to the 'FrameworkTests' group in VS + set_property ( TARGET CatalogTest PROPERTY FOLDER "UnitTests" ) +endif () diff --git a/Framework/Catalog/test/OAuthTest.h b/Framework/Catalog/test/OAuthTest.h new file mode 100644 index 0000000000000000000000000000000000000000..2509f54f450536f50432e34e922e390e8b709648 --- /dev/null +++ b/Framework/Catalog/test/OAuthTest.h @@ -0,0 +1,49 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2012 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_CATALOG_OAUTHTEST_H_ +#define MANTID_CATALOG_OAUTHTEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "MantidCatalog/Exception.h" +#include "MantidCatalog/OAuth.h" +#include "MantidKernel/DateAndTime.h" + +using Mantid::Catalog::OAuth::IOAuthTokenStore; +using Mantid::Catalog::OAuth::IOAuthTokenStore_uptr; +using Mantid::Catalog::OAuth::OAuthToken; +using Mantid::Types::Core::DateAndTime; + +class OAuthTest : public CxxTest::TestSuite { +public: + // CxxTest boilerplate. + static OAuthTest *createSuite() { return new OAuthTest(); } + static void destroySuite(OAuthTest *suite) { delete suite; } + + void test_oauth_token_from_json_stream() { + std::stringstream tokenStringSteam; + tokenStringSteam << std::string( + "{\"token_type\": \"Bearer\", \"expires_in\": 3600, " + "\"access_token\": \"2KSL5aEnLvIudMHIjc7LcBWBCfxOHZ\", " + "\"scope\": \"api:read data:read settings:read\", " + "\"refresh_token\": \"eZEiz7LbgFrkL5ZHv7R4ck9gOzXexb\"}"); + const auto oauthToken = OAuthToken::fromJSONStream(tokenStringSteam); + TS_ASSERT_EQUALS(oauthToken.tokenType(), std::string("Bearer")); + TS_ASSERT_EQUALS(oauthToken.expiresIn(), 3600); + TS_ASSERT_EQUALS(oauthToken.accessToken(), + std::string("2KSL5aEnLvIudMHIjc7LcBWBCfxOHZ")); + TS_ASSERT_EQUALS(oauthToken.scope(), + std::string("api:read data:read settings:read")); + TS_ASSERT_EQUALS(oauthToken.refreshToken(), + std::string("eZEiz7LbgFrkL5ZHv7R4ck9gOzXexb")); + + TS_ASSERT(!oauthToken.isExpired()); + TS_ASSERT(oauthToken.isExpired(DateAndTime::getCurrentTime() + 3601.0)); + } +}; + +#endif /* MANTID_CATALOG_OAUTHTEST_H_ */ diff --git a/Framework/Catalog/test/ONCatEntityTest.h b/Framework/Catalog/test/ONCatEntityTest.h new file mode 100644 index 0000000000000000000000000000000000000000..265cba94f48dce9454713e16963a76aa7f47c75f --- /dev/null +++ b/Framework/Catalog/test/ONCatEntityTest.h @@ -0,0 +1,153 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2012 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_CATALOG_ONCATENTITYTEST_H_ +#define MANTID_CATALOG_ONCATENTITYTEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "MantidCatalog/Exception.h" +#include "MantidCatalog/ONCatEntity.h" + +using Mantid::Catalog::Exception::MalformedRepresentationError; +using Mantid::Catalog::ONCat::ONCatEntity; + +class ONCatEntityTest : public CxxTest::TestSuite { +public: + // CxxTest boilerplate. + static ONCatEntityTest *createSuite() { return new ONCatEntityTest(); } + static void destroySuite(ONCatEntityTest *suite) { delete suite; } + + void test_basic_attributes() { + std::string dummyRepresentation = + "{" + " \"id\": \"3fa1d522-f1b8-4134-a56b-b61f24d20510\"," + " \"type\": \"dummy\"" + "}"; + + std::stringstream ss; + ss << dummyRepresentation; + + auto dummy = ONCatEntity::fromJSONStream(ss); + + TS_ASSERT_EQUALS(dummy.id(), + std::string("3fa1d522-f1b8-4134-a56b-b61f24d20510")); + TS_ASSERT_EQUALS(dummy.type(), std::string("dummy")); + } + + void test_basic_attributes_of_entity_vector() { + std::string dummiesRepresentation = + "[" + " {" + " \"id\": \"3fa1d522-f1b8-4134-a56b-b61f24d20510\"," + " \"type\": \"dummy\"" + " }," + " {" + " \"id\": \"4b1dec2a-0f15-416d-8d23-e08901ac4634\"," + " \"type\": \"dummy\"" + " }" + "]"; + + std::stringstream ss; + ss << dummiesRepresentation; + + auto dummies = ONCatEntity::vectorFromJSONStream(ss); + + TS_ASSERT_EQUALS(dummies.size(), 2); + + TS_ASSERT_EQUALS(dummies[0].id(), + std::string("3fa1d522-f1b8-4134-a56b-b61f24d20510")); + TS_ASSERT_EQUALS(dummies[0].type(), std::string("dummy")); + + TS_ASSERT_EQUALS(dummies[1].id(), + std::string("4b1dec2a-0f15-416d-8d23-e08901ac4634")); + TS_ASSERT_EQUALS(dummies[1].type(), std::string("dummy")); + } + + void test_throws_on_malformed_json() { + std::string malformedRepresentation = + "{" + " \"id\": \"3fa1d522-f1b8-4134-a56b-b61f24d20510\"," + " \"type\": \"dummy"; + + std::stringstream ss; + ss << malformedRepresentation; + + TS_ASSERT_THROWS(ONCatEntity::fromJSONStream(ss), + MalformedRepresentationError); + } + + void test_throws_on_malformed_representation() { + std::string missingTypeRepresentation = + "{" + " \"id\": \"3fa1d522-f1b8-4134-a56b-b61f24d20510\"" + "}"; + + std::stringstream ss; + ss << missingTypeRepresentation; + + TS_ASSERT_THROWS(ONCatEntity::fromJSONStream(ss), + MalformedRepresentationError); + } + + void test_nested_values_with_various_types() { + std::string dummyRepresentation = + "{" + " \"id\": \"3fa1d522-f1b8-4134-a56b-b61f24d20510\"," + " \"type\": \"dummy\"," + " \"val\": {" + " \"a\": {" + " \"string\": \"value\"," + " \"int\": 1234," + " \"float\": 1234.5," + " \"double\": 1234.5," + " \"bool\": true" + " }" + " }" + "}"; + + std::stringstream ss; + ss << dummyRepresentation; + + auto dummy = ONCatEntity::fromJSONStream(ss); + + TS_ASSERT_EQUALS(dummy.get<std::string>("val.a.string"), + std::string("value")); + TS_ASSERT_EQUALS(dummy.get<int>("val.a.int"), 1234); + TS_ASSERT_EQUALS(dummy.get<float>("val.a.float"), 1234.5f); + TS_ASSERT_EQUALS(dummy.get<double>("val.a.double"), 1234.5); + TS_ASSERT_EQUALS(dummy.get<bool>("val.a.bool"), true); + + TS_ASSERT(!dummy.get<std::string>("a.string")); + TS_ASSERT(!dummy.get<int>("a.int")); + TS_ASSERT(!dummy.get<float>("a.float")); + TS_ASSERT(!dummy.get<double>("a.double")); + TS_ASSERT(!dummy.get<bool>("a.bool")); + } + + void test_default_values_with_various_types() { + std::string dummyRepresentation = + "{" + " \"id\": \"3fa1d522-f1b8-4134-a56b-b61f24d20510\"," + " \"type\": \"dummy\"" + " }" + "}"; + + std::stringstream ss; + ss << dummyRepresentation; + + auto dummy = ONCatEntity::fromJSONStream(ss); + + TS_ASSERT_EQUALS(dummy.get<std::string>("a.string", "val"), + std::string("val")); + TS_ASSERT_EQUALS(dummy.get<int>("a.int", 1234), 1234); + TS_ASSERT_EQUALS(dummy.get<float>("a.float", 1234.5f), 1234.5f); + TS_ASSERT_EQUALS(dummy.get<double>("a.double", 1234.5), 1234.5); + TS_ASSERT_EQUALS(dummy.get<bool>("a.bool", true), true); + } +}; + +#endif /* MANTID_CATALOG_ONCATENTITYTEST_H_ */ diff --git a/Framework/Catalog/test/ONCatTest.h b/Framework/Catalog/test/ONCatTest.h new file mode 100644 index 0000000000000000000000000000000000000000..2cdb45a7318da031347717097264caa14f8a32b4 --- /dev/null +++ b/Framework/Catalog/test/ONCatTest.h @@ -0,0 +1,441 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2012 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_CATALOG_ONCATTEST_H_ +#define MANTID_CATALOG_ONCATTEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "MantidCatalog/Exception.h" +#include "MantidCatalog/ONCat.h" +#include "MantidKernel/DateAndTime.h" +#include "MantidKernel/Exception.h" +#include "MantidKernel/InternetHelper.h" +#include "MantidKernel/make_unique.h" + +#include <map> +#include <memory> + +#include <Poco/Net/HTTPResponse.h> + +using Poco::Net::HTTPResponse; + +using Mantid::Catalog::Exception::InvalidCredentialsError; +using Mantid::Catalog::Exception::InvalidRefreshTokenError; +using Mantid::Catalog::Exception::TokenRejectedError; +using Mantid::Catalog::OAuth::ConfigServiceTokenStore; +using Mantid::Catalog::OAuth::IOAuthTokenStore; +using Mantid::Catalog::OAuth::IOAuthTokenStore_uptr; +using Mantid::Catalog::OAuth::OAuthFlow; +using Mantid::Catalog::OAuth::OAuthToken; +using Mantid::Catalog::ONCat::ONCat; +using Mantid::Catalog::ONCat::QueryParameter; +using Mantid::Kernel::Exception::InternetError; +using Mantid::Types::Core::DateAndTime; + +//---------------------------------------------------------------------- +// Helpers, Mocks and Variables +//---------------------------------------------------------------------- + +namespace { + +using MockResponseMap = std::map<std::string, std::pair<int, std::string>>; +using MockResponseCallCounts = std::map<std::string, unsigned int>; +using MockResponseCallMapping = + std::pair<const std::basic_string<char>, unsigned int>; + +class MockONCatAPI : public Mantid::Kernel::InternetHelper { +public: + MockONCatAPI() = delete; + MockONCatAPI(const MockResponseMap &responseMap) + : Mantid::Kernel::InternetHelper(), m_responseMap(responseMap), + m_responseCallCounts() { + for (const auto &mapping : responseMap) { + m_responseCallCounts[mapping.first] = 0; + } + } + ~MockONCatAPI() {} + + bool allResponsesCalledOnce() const { + return std::all_of(m_responseCallCounts.cbegin(), + m_responseCallCounts.cend(), + [](const MockResponseCallMapping &mapping) { + return mapping.second == 1; + }); + } + +protected: + int sendHTTPRequest(const std::string &url, + std::ostream &responseStream) override { + return sendHTTPSRequest(url, responseStream); + } + + int sendHTTPSRequest(const std::string &url, + std::ostream &responseStream) override { + auto mockResponse = m_responseMap.find(url); + + assert(mockResponse != m_responseMap.end()); + m_responseCallCounts[url] += 1; + + const auto statusCode = mockResponse->second.first; + const auto responseBody = mockResponse->second.second; + + // Approximate the behaviour of the actual helper class when a + // non-OK response is observed. + if (statusCode != HTTPResponse::HTTP_OK) { + throw InternetError(responseBody, statusCode); + } + + responseStream << responseBody; + return statusCode; + } + +private: + MockResponseMap m_responseMap; + MockResponseCallCounts m_responseCallCounts; +}; + +std::shared_ptr<MockONCatAPI> +make_mock_oncat_api(const MockResponseMap &responseMap) { + return std::make_shared<MockONCatAPI>(responseMap); +} + +class MockTokenStore : public IOAuthTokenStore { +public: + MockTokenStore() : m_token(boost::none) {} + + void setToken(const boost::optional<OAuthToken> &token) override { + m_token = token; + } + boost::optional<OAuthToken> getToken() override { return m_token; } + +private: + boost::optional<OAuthToken> m_token; +}; + +IOAuthTokenStore_uptr make_mock_token_store() { + return Mantid::Kernel::make_unique<MockTokenStore>(); +} + +IOAuthTokenStore_uptr make_mock_token_store_already_logged_in() { + auto tokenStore = Mantid::Kernel::make_unique<MockTokenStore>(); + tokenStore->setToken(OAuthToken( + "Bearer", 3600, "2KSL5aEnLvIudMHIjc7LcBWBCfxOHZ", + "api:read data:read settings:read", + boost::make_optional<std::string>("eZEiz7LbgFrkL5ZHv7R4ck9gOzXexb"))); + return std::move(tokenStore); +} + +const static std::string DUMMY_URL = "https://not.a.real.url"; +const static std::string DUMMY_CLIENT_ID = + "0e527a36-297d-4cb4-8a35-84f6b11248d7"; +} // namespace + +//---------------------------------------------------------------------- +// Tests +//---------------------------------------------------------------------- + +class ONCatTest : public CxxTest::TestSuite { +public: + // CxxTest boilerplate. + static ONCatTest *createSuite() { return new ONCatTest(); } + static void destroySuite(ONCatTest *suite) { delete suite; } + + void test_login_with_invalid_credentials_throws() { + ONCat oncat(DUMMY_URL, make_mock_token_store(), + OAuthFlow::RESOURCE_OWNER_CREDENTIALS, DUMMY_CLIENT_ID); + + TS_ASSERT(!oncat.isUserLoggedIn()); + + auto mock_oncat_api = make_mock_oncat_api( + {{DUMMY_URL + "/oauth/token", + std::make_pair( + HTTPResponse::HTTP_UNAUTHORIZED, + "{\"error\": \"invalid_grant\", " + "\"error_description\": \"Invalid credentials given.\"}")}}); + + oncat.setInternetHelper(mock_oncat_api); + + TS_ASSERT_THROWS(oncat.login("user", "does_not_exist"), + InvalidCredentialsError); + TS_ASSERT(!oncat.isUserLoggedIn()); + + TS_ASSERT(mock_oncat_api->allResponsesCalledOnce()); + } + + void test_login_with_valid_credentials_is_successful() { + ONCat oncat(DUMMY_URL, make_mock_token_store(), + OAuthFlow::RESOURCE_OWNER_CREDENTIALS, DUMMY_CLIENT_ID); + + TS_ASSERT(!oncat.isUserLoggedIn()); + + auto mock_oncat_api = make_mock_oncat_api( + {{DUMMY_URL + "/oauth/token", + std::make_pair( + HTTPResponse::HTTP_OK, + "{\"token_type\": \"Bearer\", \"expires_in\": 3600, " + "\"access_token\": \"2KSL5aEnLvIudMHIjc7LcBWBCfxOHZ\", " + "\"scope\": \"api:read data:read settings:read\", " + "\"refresh_token\": \"eZEiz7LbgFrkL5ZHv7R4ck9gOzXexb\"}")}}); + + oncat.setInternetHelper(mock_oncat_api); + + oncat.login("user", "does_exist"); + + TS_ASSERT(oncat.isUserLoggedIn()); + + TS_ASSERT(mock_oncat_api->allResponsesCalledOnce()); + } + + void test_refreshing_token_when_needed() { + ONCat oncat(DUMMY_URL, make_mock_token_store(), + OAuthFlow::RESOURCE_OWNER_CREDENTIALS, DUMMY_CLIENT_ID); + + auto mock_oncat_api = make_mock_oncat_api( + {{DUMMY_URL + "/oauth/token", + std::make_pair( + HTTPResponse::HTTP_OK, + "{\"token_type\": \"Bearer\", \"expires_in\": 3600, " + "\"access_token\": \"2KSL5aEnLvIudMHIjc7LcBWBCfxOHZ\", " + "\"scope\": \"api:read data:read settings:read\", " + "\"refresh_token\": \"eZEiz7LbgFrkL5ZHv7R4ck9gOzXexb\"}")}}); + + oncat.setInternetHelper(mock_oncat_api); + + oncat.login("user", "does_exist"); + + TS_ASSERT(oncat.isUserLoggedIn()); + + oncat.refreshTokenIfNeeded(); + TS_ASSERT(oncat.isUserLoggedIn()); + + TS_ASSERT(mock_oncat_api->allResponsesCalledOnce()); + + mock_oncat_api = make_mock_oncat_api( + {{DUMMY_URL + "/oauth/token", + std::make_pair( + HTTPResponse::HTTP_OK, + "{\"token_type\": \"Bearer\", \"expires_in\": 3600, " + "\"access_token\": \"7dS7flfhsf7ShndHJSFknfskfeu789\", " + "\"scope\": \"api:read data:read settings:read\", " + "\"refresh_token\": \"sdagSDGF87dsgljerg6gdfgddfgfdg\"}")}}); + + oncat.setInternetHelper(mock_oncat_api); + + oncat.refreshTokenIfNeeded(DateAndTime::getCurrentTime() + 3601.0); + TS_ASSERT(oncat.isUserLoggedIn()); + + TS_ASSERT(mock_oncat_api->allResponsesCalledOnce()); + } + + void test_logged_out_when_refreshing_fails() { + ONCat oncat(DUMMY_URL, make_mock_token_store(), + OAuthFlow::RESOURCE_OWNER_CREDENTIALS, DUMMY_CLIENT_ID); + + auto mock_oncat_api = make_mock_oncat_api( + {{DUMMY_URL + "/oauth/token", + std::make_pair( + HTTPResponse::HTTP_OK, + "{\"token_type\": \"Bearer\", \"expires_in\": 3600, " + "\"access_token\": \"2KSL5aEnLvIudMHIjc7LcBWBCfxOHZ\", " + "\"scope\": \"api:read data:read settings:read\", " + "\"refresh_token\": \"eZEiz7LbgFrkL5ZHv7R4ck9gOzXexb\"}")}}); + + oncat.setInternetHelper(mock_oncat_api); + + oncat.login("user", "does_exist"); + + TS_ASSERT(oncat.isUserLoggedIn()); + + oncat.refreshTokenIfNeeded(); + TS_ASSERT(oncat.isUserLoggedIn()); + + TS_ASSERT(mock_oncat_api->allResponsesCalledOnce()); + + mock_oncat_api = make_mock_oncat_api( + {{DUMMY_URL + "/oauth/token", + std::make_pair( + HTTPResponse::HTTP_UNAUTHORIZED, + "{\"error\": \"invalid_grant\", " + "\"error_description\": \"Bearer token not found.\"}")}}); + + oncat.setInternetHelper(mock_oncat_api); + + TS_ASSERT_THROWS( + oncat.refreshTokenIfNeeded(DateAndTime::getCurrentTime() + 3601.0), + InvalidRefreshTokenError); + + TS_ASSERT(!oncat.isUserLoggedIn()); + + TS_ASSERT(mock_oncat_api->allResponsesCalledOnce()); + } + + void test_retrieve_entity_unauthenticated() { + ONCat oncat(DUMMY_URL, nullptr, OAuthFlow::NONE, boost::none, boost::none); + + TS_ASSERT(!oncat.isUserLoggedIn()); + + auto mock_oncat_api = make_mock_oncat_api( + {{DUMMY_URL + "/api/instruments/HB2C?facility=HFIR", + std::make_pair(HTTPResponse::HTTP_OK, "{\"facility\": \"HFIR\"," + "\"name\": \"HB2C\"," + "\"id\": \"HB2C\"," + "\"type\": \"instrument\"}")}}); + + oncat.setInternetHelper(mock_oncat_api); + + const auto entity = oncat.retrieve("api", "instruments", "HB2C", + {QueryParameter("facility", "HFIR")}); + + TS_ASSERT_EQUALS(entity.id(), std::string("HB2C")); + TS_ASSERT_EQUALS(entity.get<std::string>("name"), std::string("HB2C")); + + TS_ASSERT(mock_oncat_api->allResponsesCalledOnce()); + } + + void test_retrieve_entity() { + ONCat oncat(DUMMY_URL, make_mock_token_store_already_logged_in(), + OAuthFlow::RESOURCE_OWNER_CREDENTIALS, DUMMY_CLIENT_ID); + + TS_ASSERT(oncat.isUserLoggedIn()); + + auto mock_oncat_api = make_mock_oncat_api( + {{DUMMY_URL + "/api/instruments/HB2C?facility=HFIR", + std::make_pair(HTTPResponse::HTTP_OK, "{\"facility\": \"HFIR\"," + "\"name\": \"HB2C\"," + "\"id\": \"HB2C\"," + "\"type\": \"instrument\"}")}}); + + oncat.setInternetHelper(mock_oncat_api); + + const auto entity = oncat.retrieve("api", "instruments", "HB2C", + {QueryParameter("facility", "HFIR")}); + + TS_ASSERT_EQUALS(entity.id(), std::string("HB2C")); + TS_ASSERT_EQUALS(entity.get<std::string>("name"), std::string("HB2C")); + + TS_ASSERT(mock_oncat_api->allResponsesCalledOnce()); + } + + void test_list_entities() { + ONCat oncat(DUMMY_URL, make_mock_token_store_already_logged_in(), + OAuthFlow::RESOURCE_OWNER_CREDENTIALS, DUMMY_CLIENT_ID); + + TS_ASSERT(oncat.isUserLoggedIn()); + + auto mock_oncat_api = make_mock_oncat_api( + {{DUMMY_URL + "/api/instruments?facility=HFIR", + std::make_pair(HTTPResponse::HTTP_OK, "[" + " {" + " \"facility\": \"HFIR\"," + " \"name\": \"HB2C\"," + " \"id\": \"HB2C\"," + " \"type\": \"instrument\"" + " }," + " {" + " \"facility\": \"HFIR\"," + " \"name\": \"CG1D\"," + " \"id\": \"CG1D\"," + " \"type\": \"instrument\"" + " }" + "]")}}); + + oncat.setInternetHelper(mock_oncat_api); + + const auto entities = + oncat.list("api", "instruments", {QueryParameter("facility", "HFIR")}); + + TS_ASSERT_EQUALS(entities.size(), 2); + TS_ASSERT_EQUALS(entities[0].id(), std::string("HB2C")); + TS_ASSERT_EQUALS(entities[0].get<std::string>("name"), std::string("HB2C")); + TS_ASSERT_EQUALS(entities[1].id(), std::string("CG1D")); + TS_ASSERT_EQUALS(entities[1].get<std::string>("name"), std::string("CG1D")); + + TS_ASSERT(mock_oncat_api->allResponsesCalledOnce()); + } + + void test_send_api_request_logs_out_with_invalid_grant() { + ONCat oncat(DUMMY_URL, make_mock_token_store_already_logged_in(), + OAuthFlow::RESOURCE_OWNER_CREDENTIALS, DUMMY_CLIENT_ID); + + TS_ASSERT(oncat.isUserLoggedIn()); + + auto mock_oncat_api = make_mock_oncat_api( + {{DUMMY_URL + "/api/instruments?facility=HFIR", + std::make_pair(HTTPResponse::HTTP_UNAUTHORIZED, "{}")}}); + + oncat.setInternetHelper(mock_oncat_api); + + TS_ASSERT_THROWS( + oncat.list("api", "instruments", {QueryParameter("facility", "HFIR")}), + TokenRejectedError); + TS_ASSERT(!oncat.isUserLoggedIn()); + + TS_ASSERT(mock_oncat_api->allResponsesCalledOnce()); + } + + void test_client_credentials_flow_with_refresh() { + ONCat oncat(DUMMY_URL, make_mock_token_store(), + OAuthFlow::CLIENT_CREDENTIALS, DUMMY_CLIENT_ID, + boost::make_optional<std::string>( + "9a2ad07a-a139-438b-8116-08c5452f96ad")); + + auto mock_oncat_api = make_mock_oncat_api( + {{DUMMY_URL + "/oauth/token", + std::make_pair( + HTTPResponse::HTTP_OK, + "{\"token_type\": \"Bearer\", \"expires_in\": 3600, " + "\"access_token\": \"2KSL5aEnLvIudMHIjc7LcBWBCfxOHZ\", " + "\"scope\": \"api:read data:read settings:read\"}")}, + {DUMMY_URL + "/api/instruments/HB2C?facility=HFIR", + std::make_pair(HTTPResponse::HTTP_OK, "{\"facility\": \"HFIR\"," + "\"name\": \"HB2C\"," + "\"id\": \"HB2C\"," + "\"type\": \"instrument\"}")}}); + + oncat.setInternetHelper(mock_oncat_api); + + oncat.retrieve("api", "instruments", "HB2C", + {QueryParameter("facility", "HFIR")}); + + TS_ASSERT(mock_oncat_api->allResponsesCalledOnce()); + + mock_oncat_api = make_mock_oncat_api({{ + DUMMY_URL + "/oauth/token", + std::make_pair(HTTPResponse::HTTP_OK, + "{\"token_type\": \"Bearer\", \"expires_in\": 3600, " + "\"access_token\": \"987JHGFiusdvs72fAkjhsKJH32tkjk\", " + "\"scope\": \"api:read data:read settings:read\"}"), + }}); + + oncat.setInternetHelper(mock_oncat_api); + + oncat.refreshTokenIfNeeded(DateAndTime::getCurrentTime() + 3601.0); + + TS_ASSERT(mock_oncat_api->allResponsesCalledOnce()); + } + + void test_config_service_token_store_roundtrip() { + ConfigServiceTokenStore tokenStore; + + const auto testToken = boost::make_optional(OAuthToken( + "Bearer", 3600, "2KSL5aEnLvIudMHIjc7LcBWBCfxOHZ", + "api:read data:read settings:read", + boost::make_optional<std::string>("eZEiz7LbgFrkL5ZHv7R4ck9gOzXexb"))); + + tokenStore.setToken(testToken); + + const auto result = tokenStore.getToken(); + + TS_ASSERT_EQUALS(testToken->tokenType(), result->tokenType()); + TS_ASSERT_EQUALS(testToken->expiresIn(), result->expiresIn()); + TS_ASSERT_EQUALS(testToken->accessToken(), result->accessToken()); + TS_ASSERT_EQUALS(testToken->scope(), result->scope()); + TS_ASSERT_EQUALS(*testToken->refreshToken(), *result->refreshToken()); + } +}; + +#endif /* MANTID_CATALOG_ONCATTEST_H_ */ diff --git a/Framework/Crystal/src/SaveIsawPeaks.cpp b/Framework/Crystal/src/SaveIsawPeaks.cpp index 40125d4e21978ebb12e6940775dbc58fb6828aec..eeb794b81face329131c93ab6ae7d828a314ec5a 100644 --- a/Framework/Crystal/src/SaveIsawPeaks.cpp +++ b/Framework/Crystal/src/SaveIsawPeaks.cpp @@ -51,6 +51,10 @@ void SaveIsawPeaks::init() { make_unique<WorkspaceProperty<Workspace2D>>( "ProfileWorkspace", "", Direction::Input, PropertyMode::Optional), "An optional Workspace2D of profiles from integrating cylinder."); + + declareProperty("RenumberPeaks", false, + "If true, sequential peak numbers\n" + "If false, keep original numbering (default)."); } /** Execute the algorithm. @@ -142,13 +146,34 @@ void SaveIsawPeaks::exec() { std::ofstream out; bool append = getProperty("AppendFile"); + bool renumber = getProperty("RenumberPeaks"); // do not append if file does not exist if (!Poco::File(filename.c_str()).exists()) append = false; + int appendPeakNumb = 0; if (append) { + std::ifstream infile(filename.c_str()); + std::string line; + while (!infile.eof()) // To get you all the lines. + { + getline(infile, line); // Saves the line in STRING. + if (infile.eof()) + break; + std::stringstream ss(line); + double three; + ss >> three; + if (three == 3) { + int peakNumber; + ss >> peakNumber; + appendPeakNumb = std::max(peakNumber, appendPeakNumb); + } + } + + infile.close(); out.open(filename.c_str(), std::ios::app); + appendPeakNumb = appendPeakNumb + 1; } else { out.open(filename.c_str()); @@ -268,15 +293,10 @@ void SaveIsawPeaks::exec() { // ========================================= // Go in order of run numbers - int maxPeakNumb = 0; - int appendPeakNumb = 0; + int sequenceNumber = appendPeakNumb; runMap_t::iterator runMap_it; for (runMap_it = runMap.begin(); runMap_it != runMap.end(); ++runMap_it) { // Start of a new run - if (maxPeakNumb > 0) { - appendPeakNumb += maxPeakNumb + 1; - maxPeakNumb = 0; - } int run = runMap_it->first; bankMap_t &bankMap = runMap_it->second; @@ -321,8 +341,12 @@ void SaveIsawPeaks::exec() { Peak &p = peaks[wi]; // Sequence (run) number - maxPeakNumb = std::max(maxPeakNumb, p.getPeakNumber()); - out << "3" << std::setw(7) << p.getPeakNumber() + appendPeakNumb; + if (renumber) { + out << "3" << std::setw(7) << sequenceNumber; + sequenceNumber++; + } else { + out << "3" << std::setw(7) << p.getPeakNumber() + appendPeakNumb; + } // HKL's are flipped by -1 because of the internal Q convention // unless Crystallography convention diff --git a/Framework/Crystal/test/SaveIsawPeaksTest.h b/Framework/Crystal/test/SaveIsawPeaksTest.h index 8b9ceae2e48fa3b8c49f3eb37b3013187a8596ca..5f9d6a5f406b9c833f2426356843d0450af94986 100644 --- a/Framework/Crystal/test/SaveIsawPeaksTest.h +++ b/Framework/Crystal/test/SaveIsawPeaksTest.h @@ -17,6 +17,7 @@ #include "MantidTestHelpers/ComponentCreationHelper.h" #include <Poco/File.h> #include <cxxtest/TestSuite.h> +#include <fstream> using namespace Mantid; using namespace Mantid::Crystal; @@ -51,6 +52,9 @@ public: p.setIntensity(static_cast<double>(i) + 0.1); p.setSigmaIntensity(sqrt(static_cast<double>(i))); p.setBinCount(static_cast<double>(i)); + p.setPeakNumber((run - 1000) * + static_cast<int>(numBanks * numPeaksPerBank) + + static_cast<int>(b * numPeaksPerBank + i)); ws->addPeak(p); } @@ -63,9 +67,33 @@ public: TS_ASSERT_THROWS_NOTHING(alg.execute();); TS_ASSERT(alg.isExecuted()); + // Test appending same file to check peak numbers + SaveIsawPeaks alg2; + TS_ASSERT_THROWS_NOTHING(alg2.initialize()) + TS_ASSERT(alg2.isInitialized()) + TS_ASSERT_THROWS_NOTHING(alg2.setProperty("InputWorkspace", ws)); + TS_ASSERT_THROWS_NOTHING(alg2.setPropertyValue("Filename", outfile)); + TS_ASSERT_THROWS_NOTHING(alg2.setProperty("AppendFile", true)); + TS_ASSERT_THROWS_NOTHING(alg2.execute();); + // Get the file - outfile = alg.getPropertyValue("Filename"); - TS_ASSERT(Poco::File(outfile).exists()); + if (numPeaksPerBank > 0) { + outfile = alg2.getPropertyValue("Filename"); + TS_ASSERT(Poco::File(outfile).exists()); + std::ifstream in(outfile.c_str()); + std::string line, line0; + while (!in.eof()) // To get you all the lines. + { + getline(in, line0); // Saves the line in STRING. + if (in.eof()) + break; + line = line0; + } + TS_ASSERT_EQUALS(line, "3 71 -3 -3 -3 3.00 4.00 " + "27086 2061.553 0.24498 0.92730 3.500000 " + "14.3227 3 3.10 1.73 310"); + } + if (Poco::File(outfile).exists()) Poco::File(outfile).remove(); } diff --git a/Framework/CurveFitting/inc/MantidCurveFitting/Algorithms/QENSFitSequential.h b/Framework/CurveFitting/inc/MantidCurveFitting/Algorithms/QENSFitSequential.h index d78e6faecc3e68dec90cb375cdd6b7d91ca69fb6..6feb6c897f029b309402514f69af60ac5e2c36f7 100644 --- a/Framework/CurveFitting/inc/MantidCurveFitting/Algorithms/QENSFitSequential.h +++ b/Framework/CurveFitting/inc/MantidCurveFitting/Algorithms/QENSFitSequential.h @@ -63,10 +63,18 @@ private: const std::vector<API::MatrixWorkspace_sptr> &workspaces) const; void renameWorkspaces(API::WorkspaceGroup_sptr outputGroup, - const std::vector<std::string> &spectra); + std::vector<std::string> const &spectra, + std::string const &outputBaseName, + std::string const &endOfSuffix); void renameWorkspaces(API::WorkspaceGroup_sptr outputGroup, - const std::vector<std::string> &spectra, - const std::vector<API::MatrixWorkspace_sptr> &names); + std::vector<std::string> const &spectra, + std::string const &outputBaseName, + std::string const &endOfSuffix, + std::vector<API::MatrixWorkspace_sptr> const &names); + void renameGroupWorkspace(std::string const ¤tName, + std::vector<std::string> const &spectra, + std::string const &outputBaseName, + std::string const &endOfSuffix); void copyLogs(API::WorkspaceGroup_sptr resultWorkspace, const std::vector<API::MatrixWorkspace_sptr> &workspaces); void copyLogs(API::MatrixWorkspace_sptr resultWorkspace, diff --git a/Framework/CurveFitting/inc/MantidCurveFitting/FuncMinimizers/FABADAMinimizer.h b/Framework/CurveFitting/inc/MantidCurveFitting/FuncMinimizers/FABADAMinimizer.h index 70ea43a39b702e4e66fffbb0760e13ca2ba607ff..75227d03ebc454998266333824fed5b135ceb282 100644 --- a/Framework/CurveFitting/inc/MantidCurveFitting/FuncMinimizers/FABADAMinimizer.h +++ b/Framework/CurveFitting/inc/MantidCurveFitting/FuncMinimizers/FABADAMinimizer.h @@ -80,8 +80,22 @@ private: /// Output cost function void outputCostFunctionTable(size_t convLength, double mostProbableChi2); /// Output PDF - double outputPDF(size_t convLength, + double outputPDF(std::size_t const &convLength, std::vector<std::vector<double>> &reducedChain); + void outputPDF(std::vector<double> &xValues, std::vector<double> &yValues, + std::vector<std::vector<double>> &reducedChain, + std::size_t const &convLength, int const &pdfLength); + /// Finds the most probable Chi Squared value + double getMostProbableChiSquared( + std::size_t const &convLength, + std::vector<std::vector<double>> &reducedChain, int const &pdfLength, + std::vector<double> &xValues, std::vector<double> &yValues, + std::vector<double> &PDFYAxis, double const &start, double const &bin); + /// Computes the X and Y for the Parameter PDF's + void setParameterXAndYValuesForPDF( + std::vector<double> &xValues, std::vector<double> &yValues, + std::vector<std::vector<double>> &reducedChain, + std::size_t const &convLength, int const &pdfLength); /// Output parameter table void outputParameterTable(const std::vector<double> &bestParameters, const std::vector<double> &errorsLeft, diff --git a/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp b/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp index 2824a582ac5a2da6e83369e30c3a6299c61d473f..73bc15b98fe2be28bf1163bf05b07d5c0f5aa486 100644 --- a/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp +++ b/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp @@ -239,15 +239,14 @@ template <typename F> void renameWorkspacesInQENSFit(Algorithm *qensFit, IAlgorithm_sptr renameAlgorithm, WorkspaceGroup_sptr outputGroup, + std::string const &outputBaseName, + std::string const &groupSuffix, const F &getNameSuffix) { - const auto groupName = qensFit->getPropertyValue("OutputWorkspaceGroup"); - auto outputBase = groupName.substr(0, groupName.rfind("_Workspaces")); - Progress renamerProg(qensFit, 0.98, 1.0, outputGroup->size() + 1); renamerProg.report("Renaming group workspaces..."); auto getName = [&](std::size_t i) { - return outputBase + "_" + getNameSuffix(i); + return outputBaseName + "_" + getNameSuffix(i); }; auto renamer = [&](Workspace_sptr workspace, const std::string &name) { @@ -256,6 +255,7 @@ void renameWorkspacesInQENSFit(Algorithm *qensFit, }; renameWorkspacesWith(outputGroup, getName, renamer); + auto const groupName = outputBaseName + groupSuffix; if (outputGroup->getName() != groupName) renameWorkspace(renameAlgorithm, outputGroup, groupName); } @@ -518,15 +518,18 @@ void QENSFitSequential::exec() { getPropertyValue("OutputWorkspace"), resultWs); if (containsMultipleData(workspaces)) - renameWorkspaces(groupWs, spectra, inputWorkspaces); + renameWorkspaces(groupWs, spectra, outputBaseName, "_Workspace", + inputWorkspaces); else - renameWorkspaces(groupWs, spectra); + renameWorkspaces(groupWs, spectra, outputBaseName, "_Workspace"); copyLogs(resultWs, workspaces); const bool doExtractMembers = getProperty("ExtractMembers"); if (doExtractMembers) extractMembers(groupWs, workspaces, outputBaseName + "_Members"); + renameGroupWorkspace("__PDF_Workspace", spectra, outputBaseName, "_PDF"); + deleteTemporaryWorkspaces(outputBaseName); addAdditionalLogs(resultWs); @@ -658,20 +661,34 @@ QENSFitSequential::processParameterTable(ITableWorkspace_sptr parameterTable) { } void QENSFitSequential::renameWorkspaces( - WorkspaceGroup_sptr outputGroup, const std::vector<std::string> &spectra, - const std::vector<MatrixWorkspace_sptr> &inputWorkspaces) { + WorkspaceGroup_sptr outputGroup, std::vector<std::string> const &spectra, + std::string const &outputBaseName, std::string const &endOfSuffix, + std::vector<MatrixWorkspace_sptr> const &inputWorkspaces) { auto rename = createChildAlgorithm("RenameWorkspace", -1.0, -1.0, false); const auto getNameSuffix = [&](std::size_t i) { - return inputWorkspaces[i]->getName() + "_" + spectra[i] + "_Workspace"; + return inputWorkspaces[i]->getName() + "_" + spectra[i] + endOfSuffix; }; - return renameWorkspacesInQENSFit(this, rename, outputGroup, getNameSuffix); + return renameWorkspacesInQENSFit(this, rename, outputGroup, outputBaseName, + endOfSuffix + "s", getNameSuffix); } void QENSFitSequential::renameWorkspaces( - WorkspaceGroup_sptr outputGroup, const std::vector<std::string> &spectra) { + WorkspaceGroup_sptr outputGroup, std::vector<std::string> const &spectra, + std::string const &outputBaseName, std::string const &endOfSuffix) { auto rename = createChildAlgorithm("RenameWorkspace", -1.0, -1.0, false); - auto getNameSuffix = [&](std::size_t i) { return spectra[i] + "_Workspace"; }; - return renameWorkspacesInQENSFit(this, rename, outputGroup, getNameSuffix); + auto getNameSuffix = [&](std::size_t i) { return spectra[i] + endOfSuffix; }; + return renameWorkspacesInQENSFit(this, rename, outputGroup, outputBaseName, + endOfSuffix + "s", getNameSuffix); +} + +void QENSFitSequential::renameGroupWorkspace( + std::string const ¤tName, std::vector<std::string> const &spectra, + std::string const &outputBaseName, std::string const &endOfSuffix) { + if (AnalysisDataService::Instance().doesExist(currentName)) { + auto const pdfGroup = + AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(currentName); + renameWorkspaces(pdfGroup, spectra, outputBaseName, endOfSuffix); + } } ITableWorkspace_sptr QENSFitSequential::performFit(const std::string &input, diff --git a/Framework/CurveFitting/src/Constraints/BoundaryConstraint.cpp b/Framework/CurveFitting/src/Constraints/BoundaryConstraint.cpp index 0ed6f0bfa64edfbbef86829dd5b6b8e653bd1ea2..af34a1ea6a11388e8f6f54fc9f7a2de8f6e1d168 100644 --- a/Framework/CurveFitting/src/Constraints/BoundaryConstraint.cpp +++ b/Framework/CurveFitting/src/Constraints/BoundaryConstraint.cpp @@ -28,17 +28,17 @@ DECLARE_CONSTRAINT(BoundaryConstraint) // using namespace Kernel; using namespace API; - /// Default constructor BoundaryConstraint::BoundaryConstraint() - : API::IConstraint(), m_penaltyFactor(1000.0), m_hasLowerBound(false), - m_hasUpperBound(false), m_lowerBound(DBL_MAX), m_upperBound(-DBL_MAX) {} + : API::IConstraint(), m_penaltyFactor(getDefaultPenaltyFactor()), + m_hasLowerBound(false), m_hasUpperBound(false), m_lowerBound(DBL_MAX), + m_upperBound(-DBL_MAX) {} /// Constructor with no boundary arguments /// @param paramName :: The parameter name BoundaryConstraint::BoundaryConstraint(const std::string ¶mName) - : API::IConstraint(), m_penaltyFactor(1000.0), m_hasLowerBound(false), - m_hasUpperBound(false) { + : API::IConstraint(), m_penaltyFactor(getDefaultPenaltyFactor()), + m_hasLowerBound(false), m_hasUpperBound(false) { UNUSED_ARG(paramName); } @@ -55,16 +55,17 @@ BoundaryConstraint::BoundaryConstraint(API::IFunction *fun, const std::string paramName, const double lowerBound, const double upperBound, bool isDefault) - : m_penaltyFactor(1000.0), m_hasLowerBound(true), m_hasUpperBound(true), - m_lowerBound(lowerBound), m_upperBound(upperBound) { + : m_penaltyFactor(getDefaultPenaltyFactor()), m_hasLowerBound(true), + m_hasUpperBound(true), m_lowerBound(lowerBound), + m_upperBound(upperBound) { reset(fun, fun->parameterIndex(paramName), isDefault); } BoundaryConstraint::BoundaryConstraint(API::IFunction *fun, const std::string paramName, const double lowerBound, bool isDefault) - : m_penaltyFactor(1000.0), m_hasLowerBound(true), m_hasUpperBound(false), - m_lowerBound(lowerBound), m_upperBound(-DBL_MAX) { + : m_penaltyFactor(getDefaultPenaltyFactor()), m_hasLowerBound(true), + m_hasUpperBound(false), m_lowerBound(lowerBound), m_upperBound(-DBL_MAX) { reset(fun, fun->parameterIndex(paramName), isDefault); } @@ -261,6 +262,9 @@ std::string BoundaryConstraint::asString() const { if (m_hasUpperBound) { ostr << '<' << m_upperBound; } + if (m_penaltyFactor != getDefaultPenaltyFactor()) { + ostr << ",penalty=" << m_penaltyFactor; + } return ostr.str(); } diff --git a/Framework/CurveFitting/src/FuncMinimizers/FABADAMinimizer.cpp b/Framework/CurveFitting/src/FuncMinimizers/FABADAMinimizer.cpp index 8eaabff8193ccd09ad43c81b5d72ad6c89706377..aba2f22e27f43ddbe5074b02133fe479aada7a0a 100644 --- a/Framework/CurveFitting/src/FuncMinimizers/FABADAMinimizer.cpp +++ b/Framework/CurveFitting/src/FuncMinimizers/FABADAMinimizer.cpp @@ -4,6 +4,7 @@ // NScD Oak Ridge National Laboratory, European Spallation Source // & Institut Laue - Langevin // SPDX - License - Identifier: GPL - 3.0 + +#include "MantidAPI/AlgorithmManager.h" #include "MantidAPI/AnalysisDataService.h" #include "MantidAPI/CostFunctionFactory.h" #include "MantidAPI/FuncMinimizerFactory.h" @@ -13,6 +14,7 @@ #include "MantidAPI/ParameterTie.h" #include "MantidAPI/TableRow.h" #include "MantidAPI/WorkspaceFactory.h" +#include "MantidAPI/WorkspaceGroup.h" #include "MantidAPI/WorkspaceProperty.h" #include "MantidCurveFitting/Constraints/BoundaryConstraint.h" @@ -35,8 +37,12 @@ namespace Mantid { namespace CurveFitting { namespace FuncMinimisers { +using namespace Mantid::API; namespace { + +std::string const PDF_GROUP_NAME = "__PDF_Workspace"; + // static logger object Kernel::Logger g_log("FABADAMinimizer"); // number of iterations when convergence isn't expected @@ -47,6 +53,27 @@ const size_t JUMP_CHECKING_RATE = 200; const double LOW_JUMP_LIMIT = 1e-25; // random number generator std::mt19937 rng; + +API::MatrixWorkspace_sptr +createWorkspace(std::vector<double> const &xValues, + std::vector<double> const &yValues, int const numberOfSpectra, + std::vector<std::string> const &verticalAxisNames) { + auto createWorkspaceAlgorithm = + AlgorithmManager::Instance().createUnmanaged("CreateWorkspace"); + createWorkspaceAlgorithm->initialize(); + createWorkspaceAlgorithm->setChild(true); + createWorkspaceAlgorithm->setLogging(false); + createWorkspaceAlgorithm->setProperty("DataX", xValues); + createWorkspaceAlgorithm->setProperty("DataY", yValues); + createWorkspaceAlgorithm->setProperty("NSpec", numberOfSpectra); + createWorkspaceAlgorithm->setProperty("VerticalAxisUnit", "Text"); + createWorkspaceAlgorithm->setProperty("VerticalAxisValues", + verticalAxisNames); + createWorkspaceAlgorithm->setProperty("OutputWorkspace", "__PDF"); + createWorkspaceAlgorithm->execute(); + return createWorkspaceAlgorithm->getProperty("OutputWorkspace"); +} + } // namespace DECLARE_FUNCMINIMIZER(FABADAMinimizer, FABADA) @@ -88,10 +115,7 @@ FABADAMinimizer::FABADAMinimizer() " constant during the convergence period)." " Useful to find the exact minimum."); // Output Properties - declareProperty(Kernel::make_unique<API::WorkspaceProperty<>>( - "PDF", "PDF", Kernel::Direction::Output), - "The name to give the output workspace for the" - " Probability Density Functions"); + declareProperty("PDF", true, "If the PDF's should be calculated or not."); declareProperty("NumberBinsPDF", 20, "Number of bins used for the output PDFs"); declareProperty(Kernel::make_unique<API::WorkspaceProperty<>>( @@ -792,7 +816,7 @@ void FABADAMinimizer::outputCostFunctionTable(size_t convLength, * @return :: most probable chi square value */ double -FABADAMinimizer::outputPDF(size_t convLength, +FABADAMinimizer::outputPDF(std::size_t const &convLength, std::vector<std::vector<double>> &reducedChain) { // To store the most probable chi square value @@ -806,68 +830,112 @@ FABADAMinimizer::outputPDF(size_t convLength, " Default value (20 bins) taken\n"; pdfLength = 20; } - API::MatrixWorkspace_sptr ws = API::WorkspaceFactory::Instance().create( - "Workspace2D", m_nParams + 1, pdfLength + 1, pdfLength); // Calculate the cost function Probability Density Function if (convLength > 0) { std::sort(reducedChain[m_nParams].begin(), reducedChain[m_nParams].end()); + std::vector<double> xValues((m_nParams + 1) * (pdfLength + 1)); + std::vector<double> yValues((m_nParams + 1) * pdfLength); std::vector<double> PDFYAxis(pdfLength, 0); - double start = reducedChain[m_nParams][0]; - double bin = + double const start = reducedChain[m_nParams][0]; + double const bin = (reducedChain[m_nParams][convLength - 1] - start) / double(pdfLength); - size_t step = 0; - MantidVec &X = ws->dataX(m_nParams); - MantidVec &Y = ws->dataY(m_nParams); - X[0] = start; - for (size_t i = 1; i < static_cast<size_t>(pdfLength) + 1; i++) { - double binEnd = start + double(i) * bin; - X[i] = binEnd; - while (step < convLength && reducedChain[m_nParams][step] <= binEnd) { - PDFYAxis[i - 1] += 1; - ++step; - } - // Divided by convLength * bin to normalize - Y[i - 1] = PDFYAxis[i - 1] / (double(convLength) * bin); - } - auto indexMostProbableChi2 = - std::max_element(PDFYAxis.begin(), PDFYAxis.end()); + mostPchi2 = + getMostProbableChiSquared(convLength, reducedChain, pdfLength, xValues, + yValues, PDFYAxis, start, bin); - mostPchi2 = X[indexMostProbableChi2 - PDFYAxis.begin()] + (bin / 2.0); + if (getProperty("PDF")) + outputPDF(xValues, yValues, reducedChain, convLength, pdfLength); - // Do one iteration for each parameter. - for (size_t j = 0; j < m_nParams; ++j) { - // Calculate the Probability Density Function - std::vector<double> PDFYAxis(pdfLength, 0); - double start = reducedChain[j][0]; - double bin = - (reducedChain[j][convLength - 1] - start) / double(pdfLength); - size_t step = 0; - MantidVec &X = ws->dataX(j); - MantidVec &Y = ws->dataY(j); - X[0] = start; - for (size_t i = 1; i < static_cast<size_t>(pdfLength) + 1; i++) { - double binEnd = start + double(i) * bin; - X[i] = binEnd; - while (step < convLength && reducedChain[j][step] <= binEnd) { - PDFYAxis[i - 1] += 1; - ++step; - } - Y[i - 1] = PDFYAxis[i - 1] / (double(convLength) * bin); - } - } } // if convLength > 0 else { - g_log.warning() << "No points to create PDF. Empty Wokspace returned.\n"; + g_log.warning() << "No points to create PDF. Empty Workspace returned.\n"; mostPchi2 = -1; } - // Set and name the PDF workspace. - setProperty("PDF", ws); return mostPchi2; } +void FABADAMinimizer::outputPDF(std::vector<double> &xValues, + std::vector<double> &yValues, + std::vector<std::vector<double>> &reducedChain, + std::size_t const &convLength, + int const &pdfLength) { + setParameterXAndYValuesForPDF(xValues, yValues, reducedChain, convLength, + pdfLength); + auto parameterNames = m_fitFunction->getParameterNames(); + parameterNames.emplace_back("Chi Squared"); + auto const workspace = + createWorkspace(xValues, yValues, int(m_nParams) + 1, parameterNames); + + if (AnalysisDataService::Instance().doesExist(PDF_GROUP_NAME)) { + auto groupPDF = AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>( + PDF_GROUP_NAME); + groupPDF->addWorkspace(workspace); + AnalysisDataService::Instance().addOrReplace(PDF_GROUP_NAME, groupPDF); + } else { + auto groupPDF = boost::make_shared<WorkspaceGroup>(); + groupPDF->addWorkspace(workspace); + AnalysisDataService::Instance().addOrReplace(PDF_GROUP_NAME, groupPDF); + } +} + +double FABADAMinimizer::getMostProbableChiSquared( + std::size_t const &convLength, + std::vector<std::vector<double>> &reducedChain, int const &pdfLength, + std::vector<double> &xValues, std::vector<double> &yValues, + std::vector<double> &PDFYAxis, double const &start, double const &bin) { + std::size_t step = 0; + std::size_t const chiXStartPos = m_nParams * (pdfLength + 1); + std::size_t const chiYStartPos = m_nParams * pdfLength; + + xValues[chiXStartPos] = start; + for (std::size_t i = 1; i < static_cast<std::size_t>(pdfLength) + 1; i++) { + double binEnd = start + double(i) * bin; + xValues[chiXStartPos + i] = binEnd; + while (step < convLength && reducedChain[m_nParams][step] <= binEnd) { + PDFYAxis[i - 1] += 1; + ++step; + } + // Divided by convLength * bin to normalize + yValues[chiYStartPos + i - 1] = + PDFYAxis[i - 1] / (double(convLength) * bin); + } + + auto indexMostProbableChi2 = + std::max_element(PDFYAxis.begin(), PDFYAxis.end()); + + return xValues[indexMostProbableChi2 - PDFYAxis.begin()] + (bin / 2.0); +} + +void FABADAMinimizer::setParameterXAndYValuesForPDF( + std::vector<double> &xValues, std::vector<double> &yValues, + std::vector<std::vector<double>> &reducedChain, + std::size_t const &convLength, int const &pdfLength) { + for (std::size_t j = 0; j < m_nParams; ++j) { + + // Calculate the Probability Density Function + std::vector<double> PDFYAxis(pdfLength, 0); + double start = reducedChain[j][0]; + double bin = (reducedChain[j][convLength - 1] - start) / double(pdfLength); + std::size_t step = 0; + std::size_t const startXPos = j * (pdfLength + 1); + std::size_t const startYPos = j * pdfLength; + + xValues[startXPos] = start; + for (std::size_t i = 1; i < static_cast<std::size_t>(pdfLength) + 1; i++) { + double binEnd = start + double(i) * bin; + xValues[startXPos + i] = binEnd; + while (step < convLength && reducedChain[j][step] <= binEnd) { + PDFYAxis[i - 1] += 1; + ++step; + } + yValues[startYPos + i - 1] = PDFYAxis[i - 1] / (double(convLength) * bin); + } + } +} + /** Create the table workspace containing parameter values * * @param bestParameters :: vector containing best values for fitting parameters @@ -887,7 +955,7 @@ void FABADAMinimizer::outputParameterTable( wsPdfE->addColumn("str", "Name"); wsPdfE->addColumn("double", "Value"); wsPdfE->addColumn("double", "Left's error"); - wsPdfE->addColumn("double", "Rigth's error"); + wsPdfE->addColumn("double", "Right's error"); for (size_t j = 0; j < m_nParams; ++j) { API::TableRow row = wsPdfE->appendRow(); @@ -1108,6 +1176,7 @@ void FABADAMinimizer::initSimulatedAnnealing() { m_leftRefrPoints = 0; } } + } // namespace FuncMinimisers } // namespace CurveFitting } // namespace Mantid diff --git a/Framework/CurveFitting/test/FuncMinimizers/FABADAMinimizerTest.h b/Framework/CurveFitting/test/FuncMinimizers/FABADAMinimizerTest.h index 0d35c8deb7a986f7c016078a460cc823e86f67af..b58729baeb0a2db328bd7316f2a21abce61579e8 100644 --- a/Framework/CurveFitting/test/FuncMinimizers/FABADAMinimizerTest.h +++ b/Framework/CurveFitting/test/FuncMinimizers/FABADAMinimizerTest.h @@ -12,6 +12,8 @@ #include "MantidCurveFitting/FuncMinimizers/FABADAMinimizer.h" #include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/MatrixWorkspace.h" +#include "MantidAPI/WorkspaceGroup.h" #include "MantidCurveFitting/Algorithms/Fit.h" #include "MantidCurveFitting/Constraints/BoundaryConstraint.h" #include "MantidCurveFitting/CostFunctions/CostFuncLeastSquares.h" @@ -26,6 +28,8 @@ using namespace Mantid::CurveFitting::Functions; namespace { +std::string const PDF_GROUP_NAME = "__PDF_Workspace"; + MatrixWorkspace_sptr createTestWorkspace(size_t NVectors = 2, size_t XYLength = 20) { MatrixWorkspace_sptr ws2(new WorkspaceTester); @@ -76,10 +80,13 @@ void doTestExpDecay(MatrixWorkspace_sptr ws2) { size_t n = fun->nParams(); - TS_ASSERT(AnalysisDataService::Instance().doesExist("PDF")); - MatrixWorkspace_sptr wsPDF = boost::dynamic_pointer_cast<MatrixWorkspace>( - AnalysisDataService::Instance().retrieve("PDF")); - TS_ASSERT(wsPDF); + TS_ASSERT(AnalysisDataService::Instance().doesExist(PDF_GROUP_NAME)); + auto const pdfGroup = + AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>( + PDF_GROUP_NAME); + TS_ASSERT(pdfGroup); + auto const wsPDF = + boost::dynamic_pointer_cast<MatrixWorkspace>(pdfGroup->getItem(0)); TS_ASSERT_EQUALS(wsPDF->getNumberHistograms(), n + 1); const auto &X = wsPDF->mutableX(0); @@ -147,7 +154,7 @@ void doTestExpDecay(MatrixWorkspace_sptr ws2) { TS_ASSERT_EQUALS(Ptable->getColumn(2)->type(), "double"); TS_ASSERT_EQUALS(Ptable->getColumn(2)->name(), "Left's error"); TS_ASSERT_EQUALS(Ptable->getColumn(3)->type(), "double"); - TS_ASSERT_EQUALS(Ptable->getColumn(3)->name(), "Rigth's error"); + TS_ASSERT_EQUALS(Ptable->getColumn(3)->name(), "Right's error"); TS_ASSERT(Ptable->Double(0, 1) == fun->getParameter("Height")); TS_ASSERT(Ptable->Double(1, 1) == fun->getParameter("Lifetime")); } @@ -194,8 +201,12 @@ public: size_t nParams = fun->nParams(); // Test PDF workspace - MatrixWorkspace_sptr PDF = fit.getProperty("PDF"); - TS_ASSERT(PDF); + auto const PDFGroup = + AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>( + PDF_GROUP_NAME); + TS_ASSERT(PDFGroup); + auto const PDF = + boost::dynamic_pointer_cast<MatrixWorkspace>(PDFGroup->getItem(0)); TS_ASSERT_EQUALS(PDF->getNumberHistograms(), nParams + 1); TS_ASSERT_EQUALS(PDF->x(0).size(), 21); TS_ASSERT_EQUALS(PDF->y(0).size(), 20); @@ -253,7 +264,7 @@ public: TS_ASSERT_EQUALS(param->getColumn(2)->type(), "double"); TS_ASSERT_EQUALS(param->getColumn(2)->name(), "Left's error"); TS_ASSERT_EQUALS(param->getColumn(3)->type(), "double"); - TS_ASSERT_EQUALS(param->getColumn(3)->name(), "Rigth's error"); + TS_ASSERT_EQUALS(param->getColumn(3)->name(), "Right's error"); TS_ASSERT(param->Double(0, 1) == fun->getParameter("Height")); TS_ASSERT(param->Double(1, 1) == fun->getParameter("Lifetime")); } diff --git a/Framework/CurveFitting/test/FuncMinimizers/LevenbergMarquardtMDTest.h b/Framework/CurveFitting/test/FuncMinimizers/LevenbergMarquardtMDTest.h index 78ab3b8ac2192e3a678fbee99b8c471327b7c0ee..d11110224ddf8b47249f02d937ab50264e8310ee 100644 --- a/Framework/CurveFitting/test/FuncMinimizers/LevenbergMarquardtMDTest.h +++ b/Framework/CurveFitting/test/FuncMinimizers/LevenbergMarquardtMDTest.h @@ -256,6 +256,7 @@ public: // 0 fun->addConstraint( Kernel::make_unique<BoundaryConstraint>(fun.get(), "a", 0.001, 2.0)); + fun->setConstraintPenaltyFactor("a", 1.e20); boost::shared_ptr<CostFuncLeastSquares> costFun = boost::make_shared<CostFuncLeastSquares>(); diff --git a/Framework/CurveFitting/test/FunctionFactoryConstraintTest.h b/Framework/CurveFitting/test/FunctionFactoryConstraintTest.h index 45ffdd2e4e26bb9c41574fbfb2be5ab8639310c1..97632cec757163b219851d3408deb6b6762e945e 100644 --- a/Framework/CurveFitting/test/FunctionFactoryConstraintTest.h +++ b/Framework/CurveFitting/test/FunctionFactoryConstraintTest.h @@ -179,6 +179,14 @@ public: funa->setParameter("a0", -1); TS_ASSERT_EQUALS(c->check(), 1000); + + TS_ASSERT_EQUALS(funa->asString(), + "name=FunctionFactoryConstraintTest_FunctA,a0=-1," + "a1=1.1,constraints=(0<a0<0.2)"); + funa->setConstraintPenaltyFactor("a0", 10.2); + TS_ASSERT_EQUALS(funa->asString(), + "name=FunctionFactoryConstraintTest_FunctA,a0=-1," + "a1=1.1,constraints=(0<a0<0.2,penalty=10.2)"); } void testCreateWithConstraint2() { @@ -210,6 +218,79 @@ public: funa->setParameter("a1", 11); TS_ASSERT_EQUALS(c1->check(), 0); + + funa->setConstraintPenaltyFactor("a1", 18.4); + TS_ASSERT_EQUALS(funa->asString(), + "name=FunctionFactoryConstraintTest_FunctA,a0=-1," + "a1=11,constraints=(0<a0<0.2,10<a1,penalty=18.4)"); + } + + void testSetConstraintPenaltyFactor1() { + std::string fnString = + "name=FunctionFactoryConstraintTest_FunctA,a0=0.1,a1=15.1," + "constraints=(0<a0<0.2,a1>10,penalty=12.)"; + + IFunction_sptr funa = + FunctionFactory::Instance().createInitialized(fnString); + TS_ASSERT(funa); + + IConstraint *c0 = funa->getConstraint(0); + TS_ASSERT(c0); + TS_ASSERT_EQUALS(c0->check(), 0); + TS_ASSERT_EQUALS(c0->getPenaltyFactor(), c0->getDefaultPenaltyFactor()) + + IConstraint *c1 = funa->getConstraint(1); + TS_ASSERT(c1); + TS_ASSERT_EQUALS(c1->check(), 0); + TS_ASSERT_EQUALS(c1->getPenaltyFactor(), 12.); + + TS_ASSERT_EQUALS(funa->asString(), + "name=FunctionFactoryConstraintTest_FunctA,a0=0.1," + "a1=15.1,constraints=(0<a0<0.2,10<a1,penalty=12)"); + + c1->setPenaltyFactor(c1->getDefaultPenaltyFactor()); + TS_ASSERT_EQUALS(c1->getPenaltyFactor(), c1->getDefaultPenaltyFactor()); + TS_ASSERT_EQUALS(funa->asString(), + "name=FunctionFactoryConstraintTest_FunctA,a0=0.1," + "a1=15.1,constraints=(0<a0<0.2,10<a1)"); + + c0->setPenaltyFactor(0.5); + TS_ASSERT_EQUALS(c0->getPenaltyFactor(), 0.5); + TS_ASSERT_EQUALS(funa->asString(), + "name=FunctionFactoryConstraintTest_FunctA,a0=0.1," + "a1=15.1,constraints=(0<a0<0.2,penalty=0.5,10<a1)"); + + funa->setParameter("a0", 0.5); + TS_ASSERT_EQUALS(c0->check(), 0.045); + TS_ASSERT_EQUALS(c0->getPenaltyFactor(), 0.5); + TS_ASSERT_EQUALS(funa->asString(), + "name=FunctionFactoryConstraintTest_FunctA,a0=0.5," + "a1=15.1,constraints=(0<a0<0.2,penalty=0.5,10<a1)"); + } + + void testSetConstraintPenaltyFactor2() { + std::string fnString = + "name=FunctionFactoryConstraintTest_FunctA,a0=0.1,a1=15.1"; + IFunction_sptr funa = + FunctionFactory::Instance().createInitialized(fnString); + TS_ASSERT(funa); + + funa->addConstraints("0<a0<0.2,penalty=8,a1>10"); + IConstraint *c0 = funa->getConstraint(0); + TS_ASSERT(c0); + IConstraint *c1 = funa->getConstraint(1); + TS_ASSERT(c1); + TS_ASSERT_EQUALS(c0->getPenaltyFactor(), 8.); + TS_ASSERT_EQUALS(c1->getPenaltyFactor(), c1->getDefaultPenaltyFactor()); + + funa->clearConstraints(); + funa->addConstraints("0<a0<0.2,a1>10,penalty=0."); + IConstraint *c2 = funa->getConstraint(0); + IConstraint *c3 = funa->getConstraint(1); + TS_ASSERT(c2); + TS_ASSERT(c3); + TS_ASSERT_EQUALS(c2->asString(), "0<a0<0.2"); + TS_ASSERT_EQUALS(c3->asString(), "10<a1,penalty=0"); } void testCreateCompositeWithConstraints() { diff --git a/Framework/DataHandling/CMakeLists.txt b/Framework/DataHandling/CMakeLists.txt index 1fac8ec0ef5f83faf61af1274192eefd39e4de9f..2c388a5b6a0d33d8ac24cfdd7a54acb4e21c3c2b 100644 --- a/Framework/DataHandling/CMakeLists.txt +++ b/Framework/DataHandling/CMakeLists.txt @@ -37,6 +37,7 @@ src/LoadANSTOHelper.cpp src/LoadAscii.cpp src/LoadAscii2.cpp + src/LoadAsciiStl.cpp src/LoadBBY.cpp src/LoadBankFromDiskTask.cpp src/LoadBinaryStl.cpp @@ -49,6 +50,7 @@ src/LoadDiffCal.cpp src/LoadDspacemap.cpp src/LoadEmptyInstrument.cpp + src/LoadEMU.cpp src/LoadEventNexus.cpp src/LoadEventNexusIndexSetup.cpp src/LoadEventPreNexus2.cpp @@ -62,7 +64,7 @@ src/LoadILLIndirect2.cpp src/LoadILLPolarizationFactors.cpp src/LoadILLReflectometry.cpp - src/LoadILLSANS.cpp + src/LoadILLSANS.cpp src/LoadILLTOF2.cpp src/LoadISISNexus2.cpp src/LoadISISPolarizationEfficiencies.cpp @@ -116,6 +118,7 @@ src/LoadSpiceAscii.cpp src/LoadSpiceXML2DDet.cpp src/LoadSwans.cpp + src/LoadStl.cpp src/LoadTBL.cpp src/LoadTOFRawNexus.cpp src/LoadVulcanCalFile.cpp @@ -136,7 +139,7 @@ src/RotateInstrumentComponent.cpp src/RotateSource.cpp src/SNSDataArchive.cpp - src/SaveANSTOAscii.cpp + src/SaveANSTOAscii.cpp src/SaveAscii.cpp src/SaveAscii2.cpp src/SaveBankScatteringAngles.cpp @@ -158,7 +161,7 @@ src/SaveILLCosmosAscii.cpp src/SaveISISNexus.cpp src/SaveIsawDetCal.cpp - src/SaveMask.cpp + src/SaveMask.cpp src/SaveNISTDAT.cpp src/SaveNXSPE.cpp src/SaveNXTomo.cpp @@ -225,9 +228,11 @@ set ( INC_FILES inc/MantidDataHandling/ISISRunLogs.h inc/MantidDataHandling/JoinISISPolarizationEfficiencies.h inc/MantidDataHandling/Load.h + inc/MantidDataHandling/LoadANSTOEventFile.h inc/MantidDataHandling/LoadANSTOHelper.h inc/MantidDataHandling/LoadAscii.h inc/MantidDataHandling/LoadAscii2.h + inc/MantidDataHandling/LoadAsciiStl.h inc/MantidDataHandling/LoadBBY.h inc/MantidDataHandling/LoadBankFromDiskTask.h inc/MantidDataHandling/LoadBinaryStl.h @@ -240,6 +245,7 @@ set ( INC_FILES inc/MantidDataHandling/LoadDiffCal.h inc/MantidDataHandling/LoadDspacemap.h inc/MantidDataHandling/LoadEmptyInstrument.h + inc/MantidDataHandling/LoadEMU.h inc/MantidDataHandling/LoadEventNexus.h inc/MantidDataHandling/LoadEventNexusIndexSetup.h inc/MantidDataHandling/LoadEventPreNexus2.h @@ -301,6 +307,7 @@ set ( INC_FILES inc/MantidDataHandling/LoadSpice2D.h inc/MantidDataHandling/LoadSpiceAscii.h inc/MantidDataHandling/LoadSpiceXML2DDet.h + inc/MantidDataHandling/LoadStl.h inc/MantidDataHandling/LoadSwans.h inc/MantidDataHandling/LoadTBL.h inc/MantidDataHandling/LoadTOFRawNexus.h @@ -414,6 +421,7 @@ set ( TEST_FILES JoinISISPolarizationEfficienciesTest.h LoadAscii2Test.h LoadAsciiTest.h + LoadAsciiStlTest.h LoadBBYTest.h LoadBinaryStlTest.h LoadCalFileTest.h @@ -424,6 +432,7 @@ set ( TEST_FILES LoadDiffCalTest.h LoadDspacemapTest.h LoadEmptyInstrumentTest.h + LoadEMUauTest.h LoadEventNexusIndexSetupTest.h LoadEventNexusTest.h LoadEventPreNexus2Test.h diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadANSTOEventFile.h b/Framework/DataHandling/inc/MantidDataHandling/LoadANSTOEventFile.h new file mode 100644 index 0000000000000000000000000000000000000000..549a68d77d87e2501fed72631df30b1f1a8c82b6 --- /dev/null +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadANSTOEventFile.h @@ -0,0 +1,360 @@ +#ifndef DATAHANDING_ANSTOEVENTFILE_H_ +#define DATAHANDING_ANSTOEVENTFILE_H_ + +#include <cstdio> +#include <stdexcept> +#include <stdint.h> + +namespace Mantid { +namespace DataHandling { +namespace ANSTO { + +// The function reads a binary ANSTO event file, +// It opens the file and returns the data through the callbacks. + +constexpr int32_t EVENTFILEHEADER_BASE_MAGIC_NUMBER = 0x0DAE0DAE; +constexpr int32_t EVENTFILEHEADER_BASE_FORMAT_NUMBER = 0x00010002; + +// all events contain some or all of these fields +constexpr int32_t NVAL = 5; // x, y, v, w, wa + +#pragma pack(push, 1) // otherwise may get 8 byte aligned, no good for us + +struct EventFileHeader_Base { // total content should be 16*int (64 bytes) + int32_t + magic_number; // must equal EVENTFILEHEADER_BASE_MAGIC_NUMBER (DAE data) + int32_t format_number; // must equal EVENTFILEHEADER_BASE_FORMAT_NUMBER, + // identifies this header format + // cppcheck-suppress unusedStructMember + int32_t anstohm_version; // ANSTOHM_VERSION server/filler version number that + // generated the file + int32_t pack_format; // typically 0 if packed binary, 1 if unpacked binary. + int32_t + oob_enabled; // if set, OOB events can be present in the data, otherwise + // only neutron and t0 events are stored + int32_t clock_scale; // the CLOCK_SCALE setting, ns per timestamp unit + // cppcheck-suppress unusedStructMember + int32_t spares[16 - 6]; // spares (padding) +}; + +struct EventFileHeader_Packed { // total content should be 16*int (64 bytes) + int32_t evt_stg_nbits_x; // number of bits in x datum + int32_t evt_stg_nbits_y; // number of bits in y datum + int32_t evt_stg_nbits_v; // number of bits in v datum + int32_t evt_stg_nbits_w; // number of bits in w datum + int32_t evt_stg_nbits_wa; // number of bits in wa datum // MJL added 5/15 for + // format 0x00010002 + int32_t + evt_stg_xy_signed; // 0 if x and y are unsigned, 1 if x and y are signed + // ints + // cppcheck-suppress unusedStructMember + int32_t spares[16 - 6]; // spares (padding) +}; + +#pragma pack(pop) + +// event decoding state machine +enum event_decode_state { + // for all events + DECODE_START, // initial state - then DECODE_VAL_BITFIELDS (for neutron + // events) or DECODE_OOB_BYTE_1 (for OOB events) for OOB events + // only + DECODE_OOB_BYTE_1, + DECODE_OOB_BYTE_2, + // for all events + DECODE_VAL_BITFIELDS, + DECODE_DT // final state - then output data and return to DECODE_START +}; + +/* +Types of OOB events, and 'NEUTRON' event. Not all are used for all +instruments, or supported yet. + +NEUTRON = 0 = a neutron detected, FRAME_START = -2 = T0 pulse (e.g. from +chopper, or from Doppler on Emu). For most instruments, these are the only +types used. + +FRAME_AUX_START = -3 (e.g. from reflecting chopper on Emu), VETO = -6 (e.g. +veto signal from ancillary) + +BEAM_MONITOR = -7 (e.g. if beam monitors connected direct to Mesytec MCPD8 DAE) + +RAW = -8 = pass-through, non-decoded raw event directly from the DAE (e.g. +Mesytec MCPD8). Used to access special features of DAE. + +Other types are not used in general (DATASIZES = -1 TBD in future, FLUSH = -4 +deprecated, FRAME_DEASSERT = -5 only on Fastcomtec P7888 DAE). +*/ + +template <class IReader, class IEventHandler, class IProgress> +void ReadEventFile(IReader &loader, IEventHandler &handler, IProgress &progress, + int32_t def_clock_scale, bool use_tx_chopper) { + // read file headers (base header then packed-format header) + EventFileHeader_Base hdr_base; + if (!loader.read(reinterpret_cast<int8_t *>(&hdr_base), sizeof(hdr_base))) + throw std::runtime_error("unable to load EventFileHeader-Base"); + + EventFileHeader_Packed hdr_packed; + if (!loader.read(reinterpret_cast<int8_t *>(&hdr_packed), sizeof(hdr_packed))) + throw std::runtime_error("unable to load EventFileHeader-Packed"); + + if (hdr_base.magic_number != EVENTFILEHEADER_BASE_MAGIC_NUMBER) + throw std::runtime_error("bad magic number"); + + if (hdr_base.format_number > EVENTFILEHEADER_BASE_FORMAT_NUMBER) { + char txtBuffer[255] = {}; + snprintf(txtBuffer, sizeof(txtBuffer), + "invalid file (only format_number=%08Xh or lower)", + EVENTFILEHEADER_BASE_FORMAT_NUMBER); + throw std::runtime_error(txtBuffer); + } + + if (hdr_base.pack_format != 0) + throw std::runtime_error("only packed binary format is supported"); + + if (hdr_base.clock_scale == 0) + throw std::runtime_error("clock scale cannot be zero"); + + // note: in the old format 0x00010001, the evt_stg_nbits_wa did not exist and + // it contained evt_stg_xy_signed + if (hdr_base.format_number <= 0x00010001) { + hdr_packed.evt_stg_xy_signed = hdr_packed.evt_stg_nbits_wa; + hdr_packed.evt_stg_nbits_wa = 0; + } + + // Setup the clock_scale. In format 0x00010001 this was not part of the + // headers, hence a function argument is provided to allow it to be + // specified manually. In the current format 0x00010002, clock_scale is + // written to the header and need not be specified, unless some alternate + // scale is needed. + double scale_microsec = hdr_base.clock_scale / 1000.0; + if (!hdr_base.clock_scale) { + // old eventfile format did not have clock_scale... + scale_microsec = def_clock_scale / 1000.0; + } + + // the initial time is not set correctly so wait until primary and auxillary + // time have been reset before sending events + int64_t primary_time = 0; + int64_t auxillary_time = 0; + bool primary_ok = false; + bool auxillary_ok = false; + + // main loop + uint32_t x = 0, y = 0, v = 0, w = 0, + wa = 0; // storage for event data fields + uint32_t *ptr_val[NVAL] = {&x, &y, &v, &w, + &wa}; // used to store data into fields + + // All events are also timestamped. The differential timestamp dt stored in + // each event is summed to recover the event timestamp t. All timestamps are + // frame-relative, i.e. FRAME_START event represents T0 (e.g. from a chopper) + // and t is reset to 0. In OOB mode and for certain DAE types only (e.g. + // Mesytec MCPD8), the FRAME_START event is timestamped relative to the last + // FRAME_START. The timestamp t on the FRAME_START event is therefore the + // total frame duration, and this can be used to recover the absolute + // timestamp of all events in the DAQ, if desired (e.g. for accurate timing + // during long term kinematic experiments). + int32_t dt; // , t = 0 dt may be negative occasionally for some DAE types, + // therefore dt and t are signed ints. + + int32_t nbits_val_oob[NVAL] = {}; + + int32_t nbits_val_neutron[NVAL] = { + hdr_packed.evt_stg_nbits_x, hdr_packed.evt_stg_nbits_y, + hdr_packed.evt_stg_nbits_v, hdr_packed.evt_stg_nbits_w, + hdr_packed.evt_stg_nbits_wa}; + + int32_t ind_val = 0; + int32_t nbits_val = 0; + int32_t nbits_val_filled = 0; + int32_t nbits_dt_filled = 0; + + int32_t oob_en = + hdr_base.oob_enabled; // will be 1 if we are reading a new OOB + // event file (format 0x00010002 only). + int32_t oob_event = 0, + c = 0; // For neutron events, oob_event = 0, and for OOB + // events, oob_event = 1 and c indicates the OOB + // event type. c<0 for all OOB events currently. + + event_decode_state state = DECODE_START; // event decoding state machine + bool event_ended = false; + + while (true) { + + // read next byte + uint8_t ch; + if (!loader.read(reinterpret_cast<int8_t *>(&ch), 1)) + break; + + int32_t nbits_ch_used = 0; // no bits used initially, 8 to go + + // start of event processing + if (state == DECODE_START) { + + // if OOB event mode is enabled, the leading Bit 0 of the first byte + // indicates whether the event is a neutron event or an OOB event + if (!oob_en) + state = DECODE_VAL_BITFIELDS; + else { + oob_event = (ch & 1); + nbits_ch_used = 1; // leading bit used as OOB bit + + if (!oob_event) + state = DECODE_VAL_BITFIELDS; + else + state = DECODE_OOB_BYTE_1; + } + + // setup to decode new event bitfields (for both neutron and OOB events) + for (ind_val = 0; ind_val < NVAL; ind_val++) + *ptr_val[ind_val] = 0; + + ind_val = 0; + nbits_val_filled = 0; + + dt = 0; + nbits_dt_filled = 0; + } + + // state machine for event decoding + switch (state) { + case DECODE_START: // Should never get here + throw std::runtime_error("Failure in event decoding"); + case DECODE_OOB_BYTE_1: // first OOB header byte + // OOB event Byte 1: Bit 0 = 1 = OOB event, Bit 1 = + // mode (only mode=0 suported currently), Bits 2-5 = + // c (OOB event type), Bits 6-7 = bitfieldsize_x + // / 8. bitfieldsize_x and following 2-bit + // bitfieldsizes are the number of bytes used to + // store the OOB parameter. All of x,y,v,w,wa are + // short integers (16 bits maximum) and so + // bitfieldsizes = 0, 1 or 2 only. + c = (ch >> 2) & 0xF; // Bits 2-5 = c + + if (c & 0x8) + c |= 0xFFFFFFF0; // c is a signed parameter so sign extend - OOB events + // are negative values + nbits_val_oob[0] = (ch & 0xC0) >> 3; // Bits 6-7 * 8 = bitfieldsize_x + + state = DECODE_OOB_BYTE_2; // Proceed to process second OOB event header + // byte next time + break; + + case DECODE_OOB_BYTE_2: // second OOB header byte + // bitfieldsizes for y, v, w and wa, as for + // bitfieldsize_x above. + nbits_val_oob[1] = (ch & 0x03) << 3; // Bits 0-1 * 8 = bitfieldsize_y + nbits_val_oob[2] = (ch & 0x0C) << 1; // Bits 2-3 * 8 = bitfieldsize_v + nbits_val_oob[3] = (ch & 0x30) >> 1; // Bits 4-5 * 8 = bitfieldsize_w + nbits_val_oob[4] = (ch & 0xC0) >> 3; // Bits 6-7 * 8 = bitfieldsize_wa + + state = DECODE_VAL_BITFIELDS; // Proceed to read and store x,y,v,w,wa for + // the OOB event + break; + + case DECODE_VAL_BITFIELDS: + // fill bits of the incoming ch to the event's bitfields. + // stop when we've filled them all, or all bits of ch are used. + do { + nbits_val = + (oob_event ? nbits_val_oob[ind_val] : nbits_val_neutron[ind_val]); + if (!nbits_val) { + nbits_val_filled = 0; + ind_val++; + } else { + int32_t nbits_val_to_fill = (nbits_val - nbits_val_filled); + if ((8 - nbits_ch_used) >= nbits_val_to_fill) { + *ptr_val[ind_val] |= + ((ch >> nbits_ch_used) & ((1 << nbits_val_to_fill) - 1)) + << nbits_val_filled; + nbits_val_filled = 0; + nbits_ch_used += nbits_val_to_fill; + ind_val++; + } else { + *ptr_val[ind_val] |= (ch >> nbits_ch_used) << nbits_val_filled; + nbits_val_filled += (8 - nbits_ch_used); + nbits_ch_used = 8; + } + } + } while ((ind_val < NVAL) && (nbits_ch_used < 8)); + + // + if (ind_val == NVAL) + state = DECODE_DT; // and fall through for dt processing + + if (nbits_ch_used == 8) // read next byte + break; + + case DECODE_DT: + if ((8 - nbits_ch_used) <= 2) { + dt |= (ch >> nbits_ch_used) << nbits_dt_filled; + nbits_dt_filled += (8 - nbits_ch_used); + } else if ((ch & 0xC0) == 0xC0) { + dt |= ((ch & 0x3F) >> nbits_ch_used) << nbits_dt_filled; + nbits_dt_filled += (6 - nbits_ch_used); + } else { + dt |= (ch >> nbits_ch_used) << nbits_dt_filled; + nbits_dt_filled += (8 - nbits_ch_used); + event_ended = true; + } + + break; + } + + if (event_ended) { + state = DECODE_START; // start on new event next time + + // update times + primary_time += dt; + auxillary_time += dt; + + // is this event a frame_start? // FRAME_START is an OOB event when oob + // mode enabled + bool frame_start_event = + (oob_en ? (oob_event && c == -2) : (x == 0 && y == 0 && dt == -1)); + + if (oob_en || !frame_start_event) { + if (oob_event) { + if (c == -3) { // FRAME_AUX_START = -3 + // 0 is the reflecting chopper and 1 is the transmission chopper + if (!use_tx_chopper && x == 0) { + auxillary_time = 0; + auxillary_ok = true; + } + if (use_tx_chopper && x == 1) { + auxillary_time = 0; + auxillary_ok = true; + } + } + } else { + // if times are ok pass the event trhough the call back, time units in + // nsec + if (primary_ok && auxillary_ok) + handler.addEvent( + x, y, static_cast<double>(primary_time) * scale_microsec, + static_cast<double>(auxillary_time) * scale_microsec); + } + } + + if (frame_start_event) { + // reset timestamp at start of a new frame and pass the absolute time if + // it is available resolve the logic for the running time so that that + // the absolute time can be used + primary_time = 0; + primary_ok = true; + handler.newFrame(); + } + + progress.update(loader.selected_position()); + + event_ended = false; + } + } +} +} // namespace ANSTO +} // namespace DataHandling +} // namespace Mantid + +#endif // DATAHANDING_ANSTOEVENTFILE_H_ diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadAsciiStl.h b/Framework/DataHandling/inc/MantidDataHandling/LoadAsciiStl.h new file mode 100644 index 0000000000000000000000000000000000000000..9559fe2e6f7a74f7b71c77f9d2bc35b2869e1a3e --- /dev/null +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadAsciiStl.h @@ -0,0 +1,26 @@ +#ifndef MANTID_DATAHANDLING_LOADASCIISTL_H_ +#define MANTID_DATAHANDLING_LOADASCIISTL_H_ +#include "MantidDataHandling/LoadStl.h" +#include "MantidGeometry/Objects/MeshObject.h" +#include "MantidKernel/V3D.h" +#include <fstream> +namespace Mantid { +namespace DataHandling { + +class DLLExport LoadAsciiStl : LoadStl { +public: + LoadAsciiStl(std::string filename) : LoadStl(filename) {} + std::unique_ptr<Geometry::MeshObject> readStl() override; + bool isAsciiSTL(); + +private: + int m_lineNumber = 0; + bool readSTLTriangle(std::ifstream &file, Kernel::V3D &v1, Kernel::V3D &v2, + Kernel::V3D &v3); + bool readSTLVertex(std::ifstream &file, Kernel::V3D &vertex); + bool readSTLLine(std::ifstream &file, std::string const &type); +}; + +} // namespace DataHandling +} // namespace Mantid +#endif /* MANTID_DATAHANDLING_LOADASCIISTL_H_ */ \ No newline at end of file diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadBinaryStl.h b/Framework/DataHandling/inc/MantidDataHandling/LoadBinaryStl.h index 8024c363c7f8d9b4ad5e74bb5344b58d71116a94..e141f4c63d9c925b06c935dfc5aad15e633824a7 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/LoadBinaryStl.h +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadBinaryStl.h @@ -1,32 +1,26 @@ #ifndef MANTID_DATAHANDLING_LOADBINARYSTL_H_ #define MANTID_DATAHANDLING_LOADBINARYSTL_H_ +#include "MantidDataHandling/LoadStl.h" #include "MantidGeometry/Objects/MeshObject.h" #include "MantidKernel/BinaryStreamReader.h" -#include <MantidKernel/V3D.h> namespace Mantid { namespace DataHandling { -class DLLExport LoadBinaryStl { +class DLLExport LoadBinaryStl : LoadStl { public: static constexpr int HEADER_SIZE = 80; static constexpr uint32_t TRIANGLE_DATA_SIZE = 50; static constexpr uint32_t TRIANGLE_COUNT_DATA_SIZE = 4; static constexpr uint32_t VECTOR_DATA_SIZE = 12; - LoadBinaryStl(std::string filename) : m_filename(filename) {} - std::unique_ptr<Geometry::MeshObject> readStl(); + LoadBinaryStl(std::string filename) : LoadStl(filename) {} + std::unique_ptr<Geometry::MeshObject> readStl() override; bool isBinarySTL(); private: uint32_t getNumberTriangles(Kernel::BinaryStreamReader); void readTriangle(Kernel::BinaryStreamReader); - std::string m_filename; - - std::vector<uint16_t> m_triangle; - std::vector<Kernel::V3D> m_verticies; }; -uint16_t addSTLVertex(Kernel::V3D &vertex, std::vector<Kernel::V3D> &vertices); -bool areEqualVertices(Kernel::V3D const &v1, Kernel::V3D const &v2); } // namespace DataHandling } // namespace Mantid diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadCalFile.h b/Framework/DataHandling/inc/MantidDataHandling/LoadCalFile.h index 74e70d830483263596e0075664400db9ef75e696..6946b50cdc308398813774b1c2c5046813fcbc56 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/LoadCalFile.h +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadCalFile.h @@ -38,10 +38,10 @@ public: /// Algorithm's version for identification int version() const override { return 1; }; const std::vector<std::string> seeAlso() const override { - return {"ReadGroupsFromFile", "CreateDummyCalFile", - "CreateCalFileByNames", "AlignDetectors", - "DiffractionFocussing", "SaveCalFile", - "MergeCalFiles"}; + return {"LoadDiffCal", "ReadGroupsFromFile", + "CreateDummyCalFile", "CreateCalFileByNames", + "AlignDetectors", "DiffractionFocussing", + "SaveCalFile", "MergeCalFiles"}; } /// Algorithm's category for identification const std::string category() const override { diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadEMU.h b/Framework/DataHandling/inc/MantidDataHandling/LoadEMU.h new file mode 100644 index 0000000000000000000000000000000000000000..829ac8d3fd84ce7c27ee7d9bde58324982297b9f --- /dev/null +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadEMU.h @@ -0,0 +1,137 @@ +#ifndef DATAHANDING_LOADEMU_H_ +#define DATAHANDING_LOADEMU_H_ + +//--------------------------------------------------- +// Includes +//--------------------------------------------------- + +#include "LoadANSTOEventFile.h" +#include "LoadANSTOHelper.h" +#include "MantidAPI/IFileLoader.h" +#include "MantidAPI/LogManager.h" +#include "MantidDataObjects/EventWorkspace.h" +#include "MantidGeometry/Instrument.h" +#include "MantidNexus/NexusClasses.h" + +namespace Mantid { +namespace DataHandling { + +using ANSTO::EventVector_pt; + +/* +Loads an ANSTO EMU event file and stores it in an event workspace. +LoadEMU is an algorithm and as such inherits from the Algorithm class, +via DataHandlingCommand, and overrides the init() & exec() methods. + +Required Properties: +<UL> +<LI> Filename - Name of and path to the input event file</LI> +<LI> OutputWorkspace - Name of the workspace which stores the data</LI> +</UL> + +Optional Properties: +<UL> +<LI> Mask - The input filename of the mask data</LI> +<LI> SelectDetectorTubes - Range of detector tubes to be loaded</LI> +<LI> OverrideDopplerPhase - Override the Doppler phase (degrees)</LI> +<LI> FilterByTimeStart - Only include events after the start time</LI> +<LI> FilterByTimeStop - Only include events before the stop time</LI> +<LI> LoadAsRawDopplerTime - Save event time relative the Doppler</LI> +</UL> + +@author Geish Miladinovic (ANSTO) + +Copyright © 2010 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 DLLExport LoadEMU : public API::IFileLoader<Kernel::FileDescriptor> { + +public: + // description + int version() const override { return 1; } + const std::vector<std::string> seeAlso() const override { + return {"Load", "LoadQKK"}; + } + const std::string name() const override { return "LoadEMU"; } + const std::string category() const override { return "DataHandling\\ANSTO"; } + const std::string summary() const override { + return "Loads an EMU data file into a workspace."; + } + + // returns a confidence value that this algorithm can load a specified file + int confidence(Kernel::FileDescriptor &descriptor) const override; + +private: + // initialisation + void init() override; + + // execution + void exec() override; + + // region of intreset + static std::vector<bool> createRoiVector(const std::string &seltubes, + const std::string &maskfile); + + // load parameters from input file + void loadParameters(ANSTO::Tar::File &tarFile, API::LogManager &logm); + + // load the instrument definition and instrument parameters + void loadInstrument(); + + // create workspace + void createWorkspace(ANSTO::Tar::File &tarFile); + + // dynamically update the neutronic position + void loadDetectorL2Values(); + void updateNeutronicPostions(detid_t detID, double sampleAnalyser); + + // load and log the Doppler parameters + void loadDopplerParameters(API::LogManager &logm); + + // prepare event storage + void prepareEventStorage(ANSTO::ProgressTracker &prog, + std::vector<size_t> &eventCounts, + std::vector<EventVector_pt> &eventVectors); + + // set up the detector masks + void setupDetectorMasks(std::vector<bool> &roi); + + // binary file access + template <class EventProcessor> + static void loadEvents(API::Progress &prog, const char *progMsg, + ANSTO::Tar::File &tarFile, + EventProcessor &eventProcessor); + + // shared member variables + DataObjects::EventWorkspace_sptr m_localWorkspace; + std::vector<double> m_detectorL2; + + // Doppler characteristics + double m_dopplerAmpl; + double m_dopplerFreq; + double m_dopplerPhase; +}; + +} // namespace DataHandling +} // namespace Mantid + +#endif // DATAHANDING_LOADEMU_H_ diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadILLSANS.h b/Framework/DataHandling/inc/MantidDataHandling/LoadILLSANS.h index 02a8f37dba7e6fd59b9495e29a170585267f7b36..0074fdfde204f1f658ec9095160f554fc4338fc2 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/LoadILLSANS.h +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadILLSANS.h @@ -89,9 +89,8 @@ private: bool m_isTOF; ///< TOF or monochromatic flag double m_sourcePos; ///< Source Z (for D33 TOF) - double calculateQ(const double lambda, const double twoTheta) const; - std::pair<double, double> calculateQMaxQMin(); void setFinalProperties(const std::string &filename); + void setPixelSize(); }; } // namespace DataHandling diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadStl.h b/Framework/DataHandling/inc/MantidDataHandling/LoadStl.h new file mode 100644 index 0000000000000000000000000000000000000000..ee78eafd159ea53cce6432611fb80e1bd1ede4e1 --- /dev/null +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadStl.h @@ -0,0 +1,24 @@ +#ifndef MANTID_DATAHANDLING_LOADSTL_H_ +#define MANTID_DATAHANDLING_LOADSTL_H_ +#include "MantidGeometry/Objects/MeshObject.h" +#include <MantidKernel/V3D.h> + +namespace Mantid { +namespace DataHandling { + +class DLLExport LoadStl { +public: + LoadStl(std::string filename) : m_filename(filename) {} + virtual std::unique_ptr<Geometry::MeshObject> readStl() = 0; + +protected: + uint16_t addSTLVertex(Kernel::V3D &vertex); + bool areEqualVertices(Kernel::V3D const &v1, Kernel::V3D const &v2); + std::string m_filename; + std::vector<uint16_t> m_triangle; + std::vector<Kernel::V3D> m_verticies; +}; + +} // namespace DataHandling +} // namespace Mantid +#endif /* MANTID_DATAHANDLING_LOADSTL_H_ */ \ No newline at end of file diff --git a/Framework/DataHandling/inc/MantidDataHandling/SaveCalFile.h b/Framework/DataHandling/inc/MantidDataHandling/SaveCalFile.h index eb077968dd58a2597174a175415a1ca580b4ad0e..4484b51821c39e62269ad3b5d9c093f6278e2053 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/SaveCalFile.h +++ b/Framework/DataHandling/inc/MantidDataHandling/SaveCalFile.h @@ -36,10 +36,10 @@ public: /// Algorithm's version for identification int version() const override { return 1; }; const std::vector<std::string> seeAlso() const override { - return {"ReadGroupsFromFile", "CreateDummyCalFile", - "CreateCalFileByNames", "AlignDetectors", - "DiffractionFocussing", "LoadCalFile", - "MergeCalFiles"}; + return {"SaveDiffCal", "ReadGroupsFromFile", + "CreateDummyCalFile", "CreateCalFileByNames", + "AlignDetectors", "DiffractionFocussing", + "LoadCalFile", "MergeCalFiles"}; } /// Algorithm's category for identification const std::string category() const override { diff --git a/Framework/DataHandling/src/GroupDetectors2.cpp b/Framework/DataHandling/src/GroupDetectors2.cpp index c2d25d5c250501bd94d0d645efeb89ba12bf5174..d1ad725e416fe4074ce99053b0a5f4a6592d29c6 100644 --- a/Framework/DataHandling/src/GroupDetectors2.cpp +++ b/Framework/DataHandling/src/GroupDetectors2.cpp @@ -1256,7 +1256,7 @@ std::map<std::string, std::string> GroupDetectors2::validateInputs() { const std::string pattern = getPropertyValue("GroupingPattern"); - boost::regex re( + static const boost::regex re( R"(^\s*[0-9]+\s*$|^(\s*,*[0-9]+(\s*(,|:|\+|\-)\s*)*[0-9]*)*$)"); try { diff --git a/Framework/DataHandling/src/LoadAsciiStl.cpp b/Framework/DataHandling/src/LoadAsciiStl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9e3e22f3ccef2bbb5f4f9111813915750f94c078 --- /dev/null +++ b/Framework/DataHandling/src/LoadAsciiStl.cpp @@ -0,0 +1,101 @@ +#include "MantidDataHandling/LoadAsciiStl.h" +#include "MantidKernel/Exception.h" +#include "MantidKernel/make_unique.h" +#include <boost/algorithm/string.hpp> +#include <boost/lexical_cast.hpp> +#include <fstream> +#include <string> +namespace Mantid { +namespace DataHandling { + +bool LoadAsciiStl::isAsciiSTL() { + std::ifstream file(m_filename.c_str()); + std::string line; + getline(file, line); + boost::trim(line); + return (line.size() >= 5 && line.substr(0, 5) == "solid"); +} + +std::unique_ptr<Geometry::MeshObject> LoadAsciiStl::readStl() { + std::ifstream file(m_filename.c_str()); + std::string line; + getline(file, line); + m_lineNumber++; + Kernel::V3D t1, t2, t3; + while (readSTLTriangle(file, t1, t2, t3)) { + // Add triangle if all 3 vertices are distinct + if (!areEqualVertices(t1, t2) && !areEqualVertices(t1, t3) && + !areEqualVertices(t2, t3)) { + m_triangle.emplace_back(addSTLVertex(t1)); + m_triangle.emplace_back(addSTLVertex(t2)); + m_triangle.emplace_back(addSTLVertex(t3)); + } + } + // Use efficient constructor of MeshObject + std::unique_ptr<Geometry::MeshObject> retVal = + Kernel::make_unique<Geometry::MeshObject>(std::move(m_triangle), + std::move(m_verticies), + Mantid::Kernel::Material()); + return retVal; +} + +bool LoadAsciiStl::readSTLTriangle(std::ifstream &file, Kernel::V3D &v1, + Kernel::V3D &v2, Kernel::V3D &v3) { + if (readSTLLine(file, "facet") && readSTLLine(file, "outer loop")) { + const bool ok = (readSTLVertex(file, v1) && readSTLVertex(file, v2) && + readSTLVertex(file, v3)); + if (!ok) { + throw Kernel::Exception::ParseError("Error on reading STL triangle", + m_filename, m_lineNumber); + } + } else { + return false; // End of file + } + return readSTLLine(file, "endloop") && readSTLLine(file, "endfacet"); +} + +bool LoadAsciiStl::readSTLVertex(std::ifstream &file, Kernel::V3D &vertex) { + std::string line; + m_lineNumber++; + if (getline(file, line)) { + boost::trim(line); + std::vector<std::string> tokens; + boost::split(tokens, line, boost::is_any_of(" "), boost::token_compress_on); + if (tokens.size() == 4 && tokens[0] == "vertex") { + vertex.setX(std::stod(tokens[1])); + vertex.setY(std::stod(tokens[2])); + vertex.setZ(std::stod(tokens[3])); + return true; + } else { + throw Kernel::Exception::ParseError("Error on reading STL vertex", + m_filename, m_lineNumber); + } + } + return false; +} + +// Read, check and ignore line in STL file. Return true if line is read +bool LoadAsciiStl::readSTLLine(std::ifstream &file, std::string const &type) { + std::string line; + m_lineNumber++; + if (getline(file, line)) { + boost::trim(line); + if (line.size() < type.size() || line.substr(0, type.size()) != type) { + // Before throwing, check for endsolid statment + const std::string type2 = "endsolid"; + if (line.size() < type2.size() || line.substr(0, type2.size()) != type2) { + throw Kernel::Exception::ParseError("Expected STL line begining with " + + type + " or " + type2, + m_filename, m_lineNumber); + } else { + return false; // ends reading at endsolid + } + } + return true; // expected line read, then ignored + } else { + return false; // end of file + } +} + +} // namespace DataHandling +} // namespace Mantid \ No newline at end of file diff --git a/Framework/DataHandling/src/LoadBinaryStl.cpp b/Framework/DataHandling/src/LoadBinaryStl.cpp index 7ba14554bfceb0ca0af0fd8030726b560c0fbfce..36992f6f12b4764422d47b6a2a112510b385619c 100644 --- a/Framework/DataHandling/src/LoadBinaryStl.cpp +++ b/Framework/DataHandling/src/LoadBinaryStl.cpp @@ -1,10 +1,6 @@ #include "MantidDataHandling/LoadBinaryStl.h" -#include "MantidAPI/FileProperty.h" -#include "MantidGeometry/Objects/MeshObject.h" -#include "MantidKernel/BinaryStreamReader.h" #include <Poco/File.h> #include <fstream> -#include <iostream> namespace Mantid { namespace DataHandling { @@ -35,7 +31,6 @@ bool LoadBinaryStl::isBinarySTL() { uint32_t LoadBinaryStl::getNumberTriangles(Kernel::BinaryStreamReader streamReader) { uint32_t numberTrianglesLong; - // skip header streamReader.moveStreamToPosition(HEADER_SIZE); // Read the number of triangles @@ -50,8 +45,8 @@ std::unique_ptr<Geometry::MeshObject> LoadBinaryStl::readStl() { const auto numberTrianglesLong = getNumberTriangles(streamReader); uint32_t nextToRead = HEADER_SIZE + TRIANGLE_COUNT_DATA_SIZE + VECTOR_DATA_SIZE; - // now read in all the triangles + for (uint32_t i = 0; i < numberTrianglesLong; i++) { // find next triangle, skipping the normal and attribute streamReader.moveStreamToPosition(nextToRead); @@ -77,28 +72,8 @@ void LoadBinaryStl::readTriangle(Kernel::BinaryStreamReader streamReader) { streamReader >> zVal; Kernel::V3D vec = Kernel::V3D(double(xVal), double(yVal), double(zVal)); // add index of new vertex to triangle - m_triangle.push_back(addSTLVertex(vec, m_verticies)); - } -} - -bool areEqualVertices(Kernel::V3D const &v1, Kernel::V3D const &v2) { - Kernel::V3D diff = v1 - v2; - return diff.norm() < 1e-9; // This is 1 nanometre for a unit of a metre. -} - -// Adds vertex to list if distinct and returns index to vertex added or equal -uint16_t addSTLVertex(Kernel::V3D &vertex, std::vector<Kernel::V3D> &vertices) { - for (uint16_t i = 0; i < vertices.size(); ++i) { - if (areEqualVertices(vertex, vertices[i])) { - return i; - } - } - vertices.push_back(vertex); - uint16_t index = static_cast<uint16_t>(vertices.size() - 1); - if (index != vertices.size() - 1) { - throw std::runtime_error("Too many vertices in solid"); + m_triangle.push_back(addSTLVertex(vec)); } - return index; } } // namespace DataHandling diff --git a/Framework/DataHandling/src/LoadEMU.cpp b/Framework/DataHandling/src/LoadEMU.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3fe201297385a994454244fee3b9a6a3f9c3599f --- /dev/null +++ b/Framework/DataHandling/src/LoadEMU.cpp @@ -0,0 +1,922 @@ + +#include "MantidDataHandling/LoadEMU.h" +#include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/Axis.h" +#include "MantidAPI/FileProperty.h" +#include "MantidAPI/LogManager.h" +#include "MantidAPI/RegisterFileLoader.h" +#include "MantidAPI/Run.h" +#include "MantidDataHandling/LoadANSTOEventFile.h" +#include "MantidDataObjects/EventWorkspace.h" +#include "MantidGeometry/Instrument.h" +#include "MantidGeometry/Instrument/ComponentInfo.h" +#include "MantidGeometry/Instrument/DetectorInfo.h" +#include "MantidGeometry/Instrument/RectangularDetector.h" +#include "MantidGeometry/Objects/ShapeFactory.h" +#include "MantidKernel/OptionalBool.h" +#include "MantidKernel/PropertyWithValue.h" +#include "MantidKernel/UnitFactory.h" +#include "MantidNexus/NexusClasses.h" + +#include <boost/math/special_functions/round.hpp> + +#include <Poco/AutoPtr.h> +#include <Poco/TemporaryFile.h> +#include <Poco/Util/PropertyFileConfiguration.h> + +#include <math.h> + +namespace Mantid { +namespace DataHandling { + +using namespace Kernel; + +namespace { + +// number of physical detectors +constexpr size_t HORIZONTAL_TUBES = 16; +constexpr size_t VERTICAL_TUBES = 35; +constexpr size_t DETECTORS = HORIZONTAL_TUBES + VERTICAL_TUBES; +// analysed and direct detectors +constexpr size_t HISTO_BINS_X = DETECTORS * 2; +constexpr size_t HISTO_BINS_Y = 1024; +constexpr size_t HISTO_BINS_Y_DENUMERATOR = 16; +constexpr size_t PIXELS_PER_TUBE = HISTO_BINS_Y / HISTO_BINS_Y_DENUMERATOR; +constexpr size_t HISTOGRAMS = + HISTO_BINS_X * HISTO_BINS_Y / HISTO_BINS_Y_DENUMERATOR; + +// File loading progress boundaries +constexpr size_t Progress_LoadBinFile = 48; +constexpr size_t Progress_ReserveMemory = 4; +constexpr size_t Progress_Total = + 2 * Progress_LoadBinFile + Progress_ReserveMemory; + +// Algorithm parameter names +constexpr char FilenameStr[] = "Filename"; +constexpr char MaskStr[] = "Mask"; +constexpr char SelectDetectorTubesStr[] = "SelectDetectorTubes"; +constexpr char OverrideDopplerPhaseStr[] = "OverrideDopplerPhase"; +constexpr char FilterByTimeStartStr[] = "FilterByTimeStart"; +constexpr char FilterByTimeStopStr[] = "FilterByTimeStop"; +constexpr char RawDopplerTimeStr[] = "LoadAsRawDopplerTime"; + +// Common pairing of limits +using TimeLimits = std::pair<double, double>; + +// Utility functions for loading values with defaults +// Single value properties only support int, double, string and bool +template <typename Type> +Type GetNeXusValue(NeXus::NXEntry &entry, const std::string &path, + const Type &defval) { + try { + NeXus::NXDataSetTyped<Type> dataSet = entry.openNXDataSet<Type>(path); + dataSet.load(); + + return *dataSet(); + } catch (std::runtime_error &) { + return defval; + } +} +// string and double are special cases +template <> +double GetNeXusValue<double>(NeXus::NXEntry &entry, const std::string &path, + const double &defval) { + try { + NeXus::NXDataSetTyped<float> dataSet = entry.openNXDataSet<float>(path); + dataSet.load(); + + return *dataSet(); + } catch (std::runtime_error &) { + return defval; + } +} +template <> +std::string GetNeXusValue<std::string>(NeXus::NXEntry &entry, + const std::string &path, + const std::string &defval) { + + try { + NeXus::NXChar dataSet = entry.openNXChar(path); + dataSet.load(); + + return std::string(dataSet(), dataSet.dim0()); + } catch (std::runtime_error &) { + return defval; + } +} + +template <typename T> +void MapNeXusToProperty(NeXus::NXEntry &entry, const std::string &path, + const T &defval, API::LogManager &logManager, + const std::string &name, const T &factor) { + + T value = GetNeXusValue<T>(entry, path, defval); + logManager.addProperty<T>(name, value * factor); +} + +// sting is a special case +template <> +void MapNeXusToProperty<std::string>( + NeXus::NXEntry &entry, const std::string &path, const std::string &defval, + API::LogManager &logManager, const std::string &name, const std::string &) { + + std::string value = GetNeXusValue<std::string>(entry, path, defval); + logManager.addProperty<std::string>(name, value); +} + +// map the comma separated range of indexes to the vector via a lambda function +// throws an exception if it is outside the vector range +// +template <typename T, typename F> +void mapRangeToIndex(const std::string &line, std::vector<T> &result, + const F &fn) { + + std::stringstream ss(line); + std::string item; + size_t index = 0; + while (std::getline(ss, item, ',')) { + auto k = item.find('-'); + + size_t p0, p1; + if (k != std::string::npos) { + p0 = boost::lexical_cast<size_t>(item.substr(0, k)); + p1 = boost::lexical_cast<size_t>(item.substr(k + 1, item.size() - k - 1)); + } else { + p0 = boost::lexical_cast<size_t>(item); + p1 = p0; + } + + if (p1 < result.size() && p0 <= p1) { + while (p0 <= p1) { + result[p0++] = fn(index); + index++; + } + } else if (p0 < result.size() && p1 < p0) { + do { + result[p0] = fn(index); + index++; + } while (p1 < p0--); + } else + throw std::invalid_argument("invalid range specification"); + } +} + +// secant invert fucntion +// +template <typename F> +double invert(double y, const F &f, double x0 = 0.0, const double eps = 1e-16) { + // secant method + double e0 = f(x0) - y; + + double x1 = x0 + eps; + double e1 = f(x1) - y; + int loop = 16; + while (fabs(e0) > eps && loop-- > 0) { + double x = (x1 * e0 - x0 * e1) / (e0 - e1); + + x1 = x0; + e1 = e0; + + x0 = x; + e0 = f(x0) - y; + } + + return x0; +} + +// Need to convert two different methods for direct and analysed data +// provide two distinct methods that may be called but the distances between +// the detectors is not constant so it needs to store the distance for each +// Note that observation time and TOF are in usec +// +class ConvertTOF { + const double m_w; + const double m_phi; + const double m_L0; + const double m_v2; + const double m_A; + std::vector<double> m_L2; + + inline double L1(double t) const { return m_L0 + m_A * sin(m_w * t + m_phi); } + + inline double v1(double t) const { + return m_v2 - m_A * m_w * cos(m_w * t + m_phi); + } + +public: + ConvertTOF(double Amp, double freq, double phase, double L1, double v2, + std::vector<double> &L2) + : m_w(2 * M_PI * freq), m_phi(M_PI * phase / 180.0), m_L0(L1), m_v2(v2), + m_A(Amp), m_L2(L2) {} + + double directTOF(size_t detID, double tobs) const { + + // observation time and tof are in usec + auto tn = [=](double t) { return t + (L1(t) + m_L2[detID]) / v1(t); }; + + double tsec = tobs * 1.0e-6; + double t0 = tsec - (m_L0 + m_L2[detID]) / m_v2; + double tinv = invert(tsec, tn, t0); + double tof = m_L0 / v1(tinv) + m_L2[detID] / m_v2; + + return tof * 1.0e6; + } + + double analysedTOF(size_t detID, double tobs) const { + // observation time and tof are in usec + auto tn = [=](double t) { return t + L1(t) / v1(t) + m_L2[detID] / m_v2; }; + + double tsec = tobs * 1.0e-6; + double t0 = tsec - (m_L0 + m_L2[detID]) / m_v2; + double t = invert(tsec, tn, t0); + double tof = m_L0 / v1(t) + m_L2[detID] / m_v2; + + return tof * 1.0e6; + } +}; + +} // anonymous namespace + +namespace EMU { + +// Implement emu specific handlers for the more general EMU events. The main +// differences with the current impementation of handlers: +// - tof needs to be derived from the observed time because of the doppler drive +// - emu includes direct and indirect virtual detectors that account for +// alternate paths +// - the loader returns the doppler and auxillary time along with the +// absolute time rather than just the primary observed time that is +// equivalent to tof +// +// In the future the ANSTO helper and event file loader will be generalized to +// handle the instruments consistently. + +class EventProcessor { +protected: + // fields + const std::vector<bool> &m_roi; + const std::vector<size_t> &m_mapIndex; + const size_t m_stride; + const double m_framePeriod; + const double m_gatePeriod; + + // number of frames + size_t m_frames; + size_t m_framesValid; + + // time boundaries + const TimeLimits m_timeBoundary; // seconds + const TimeLimits m_directTaux; // microsec + const TimeLimits m_analysedTaux; // microsec + + virtual void addEventImpl(size_t id, size_t x, size_t y, double tof) = 0; + +public: + EventProcessor(const std::vector<bool> &roi, + const std::vector<size_t> &mapIndex, const size_t stride, + const double framePeriod, const double gatePeriod, + const TimeLimits &timeBoundary, const TimeLimits &directLimits, + const TimeLimits &analysedLimits) + : m_roi(roi), m_mapIndex(mapIndex), m_stride(stride), + m_framePeriod(framePeriod), m_gatePeriod(gatePeriod), m_frames(0), + m_framesValid(0), m_timeBoundary(timeBoundary), + m_directTaux(directLimits), m_analysedTaux(analysedLimits) {} + + void newFrame() { + m_frames++; + if (validFrame()) + m_framesValid++; + } + + inline bool validFrame() const { + double frameTime = m_framePeriod * static_cast<double>(m_frames) * 1.0e-6; + return (frameTime >= m_timeBoundary.first && + frameTime <= m_timeBoundary.second); + } + + double duration() const { + // length test in seconds + return m_framePeriod * static_cast<double>(m_frames) * 1.0e-6; + } + void addEvent(size_t x, size_t p, double tdop, double taux) { + + // check if in time boundaries + if (!validFrame()) + return; + + // group pixels + auto y = static_cast<size_t>(p / HISTO_BINS_Y_DENUMERATOR); + + // determine detector id and check limits + if (x >= DETECTORS || y >= m_stride) + return; + + // map the raw detector index to the physical model + size_t xid = m_mapIndex[x]; + + // take the modules of the taux time to account for the + // longer background chopper rate + double ptaux = fmod(taux, m_gatePeriod); + if (ptaux >= m_directTaux.first && ptaux <= m_directTaux.second) + xid = xid + DETECTORS; + else if (!(ptaux >= m_analysedTaux.first && ptaux <= m_analysedTaux.second)) + return; + + size_t id = m_stride * xid + y; + if (id >= m_roi.size()) + return; + + // check if neutron is in region of interest + if (!m_roi[id]) + return; + + // finally pass to specific handler + addEventImpl(id, xid, y, tdop); + } +}; + +class EventCounter : public EventProcessor { +protected: + // fields + std::vector<size_t> &m_eventCounts; + + void addEventImpl(size_t id, size_t, size_t, double) override { + m_eventCounts[id]++; + } + +public: + // construction + EventCounter(const std::vector<bool> &roi, + const std::vector<size_t> &mapIndex, const size_t stride, + const double framePeriod, const double gatePeriod, + const TimeLimits &timeBoundary, const TimeLimits &directLimits, + const TimeLimits &analysedLimits, + std::vector<size_t> &eventCounts) + : EventProcessor(roi, mapIndex, stride, framePeriod, gatePeriod, + timeBoundary, directLimits, analysedLimits), + m_eventCounts(eventCounts) {} + + size_t numFrames() const { return m_framesValid; } +}; + +class EventAssigner : public EventProcessor { +protected: + // fields + std::vector<EventVector_pt> &m_eventVectors; + const ConvertTOF &m_convertTOF; + double m_tofMin; + double m_tofMax; + bool m_saveAsTOF; + + void addEventImpl(size_t id, size_t x, size_t, double tobs) override { + + // convert observation time to tof + double tof = tobs; + if (m_saveAsTOF) + tof = x < DETECTORS ? m_convertTOF.analysedTOF(id, tobs) + : m_convertTOF.directTOF(id, tobs); + + if (m_tofMin > tof) + m_tofMin = tof; + if (m_tofMax < tof) + m_tofMax = tof; + + m_eventVectors[id]->push_back(tof); + } + +public: + EventAssigner(const std::vector<bool> &roi, + const std::vector<size_t> &mapIndex, const size_t stride, + const double framePeriod, const double gatePeriod, + const TimeLimits &timeBoundary, const TimeLimits &directLimits, + const TimeLimits &analysedLimits, ConvertTOF &convert, + std::vector<EventVector_pt> &eventVectors, bool saveAsTOF) + : EventProcessor(roi, mapIndex, stride, framePeriod, gatePeriod, + timeBoundary, directLimits, analysedLimits), + m_eventVectors(eventVectors), m_convertTOF(convert), + m_tofMin(std::numeric_limits<double>::max()), + m_tofMax(std::numeric_limits<double>::min()), m_saveAsTOF(saveAsTOF) {} + + double tofMin() const { return m_tofMin <= m_tofMax ? m_tofMin : 0.0; } + double tofMax() const { return m_tofMin <= m_tofMax ? m_tofMax : 0.0; } +}; +} // namespace EMU + +// register the algorithm into the AlgorithmFactory +DECLARE_FILELOADER_ALGORITHM(LoadEMU) + +/** + * Return the confidence value that this algorithm can load the file + * @param descriptor A descriptor for the file + * @returns An integer specifying the confidence level. 0 indicates it will not + * be used + */ +int LoadEMU::confidence(Kernel::FileDescriptor &descriptor) const { + if (descriptor.extension() != ".tar") + return 0; + + ANSTO::Tar::File file(descriptor.filename()); + if (!file.good()) + return 0; + + size_t hdfFiles = 0; + size_t binFiles = 0; + const std::vector<std::string> &subFiles = file.files(); + for (const auto &subFile : subFiles) { + auto len = subFile.length(); + if ((len > 4) && + (subFile.find_first_of("\\/", 0, 2) == std::string::npos)) { + if ((subFile.rfind(".hdf") == len - 4) && + (subFile.compare(0, 3, "EMU") == 0)) + hdfFiles++; + else if (subFile.rfind(".bin") == len - 4) + binFiles++; + } + } + + return (hdfFiles == 1) && (binFiles == 1) ? 50 : 0; +} + +/** + * Initialise the algorithm. Declare properties which can be set before + * execution (input) and + * read from after the execution (output). + */ +void LoadEMU::init() { + // Specify file extensions which can be associated with a specific file. + std::vector<std::string> exts; + + // Declare the Filename algorithm property. Mandatory. Sets the path to the + // file to load. + exts.clear(); + exts.emplace_back(".tar"); + declareProperty(Kernel::make_unique<API::FileProperty>( + FilenameStr, "", API::FileProperty::Load, exts), + "The input filename of the stored data"); + + // mask + exts.clear(); + exts.emplace_back(".xml"); + declareProperty(Kernel::make_unique<API::FileProperty>( + MaskStr, "", API::FileProperty::OptionalLoad, exts), + "The input filename of the mask data"); + + declareProperty(SelectDetectorTubesStr, "", + "Comma separated range of detectors tubes to be loaded,\n" + " e.g. 16,19-45,47"); + + declareProperty( + Kernel::make_unique<API::WorkspaceProperty<API::IEventWorkspace>>( + "OutputWorkspace", "", Kernel::Direction::Output)); + + declareProperty(OverrideDopplerPhaseStr, EMPTY_DBL(), + "Override the Doppler phase, in degrees."); + + declareProperty(RawDopplerTimeStr, false, + "Import file as observed time relative the Doppler " + "drive, in microsecs."); + + declareProperty(FilterByTimeStartStr, 0.0, + "Only include events after the provided start time, in " + "seconds (relative to the start of the run)."); + + declareProperty(FilterByTimeStopStr, EMPTY_DBL(), + "Only include events before the provided stop time, in " + "seconds (relative to the start of the run)."); + + std::string grpOptional = "Filters"; + setPropertyGroup(FilterByTimeStartStr, grpOptional); + setPropertyGroup(FilterByTimeStopStr, grpOptional); +} + +/** + * Creates an event workspace and sets the title. + */ +void LoadEMU::createWorkspace(ANSTO::Tar::File &tarFile) { + + // Create the workspace + m_localWorkspace = boost::make_shared<DataObjects::EventWorkspace>(); + m_localWorkspace->initialize(HISTOGRAMS, 2, 1); + + // set the units + m_localWorkspace->getAxis(0)->unit() = + Kernel::UnitFactory::Instance().create("TOF"); + m_localWorkspace->setYUnit("Counts"); + + // set title + const std::vector<std::string> &subFiles = tarFile.files(); + for (const auto &subFile : subFiles) + if (subFile.compare(0, 3, "EMU") == 0) { + std::string title = subFile; + + if (title.rfind(".hdf") == title.length() - 4) + title.resize(title.length() - 4); + + if (title.rfind(".nx") == title.length() - 3) + title.resize(title.length() - 3); + + m_localWorkspace->setTitle(title); + break; + } +} + +/** + * Execute the algorithm. The steps involved are: + * Get the instrument properties and load options + * Create the workspace + * Load the instrument from the IDF + * Reposition the relevant neutronic values for model based on the parameters + * Load the data values and convert to TOF + * Setting up the masks + */ +void LoadEMU::exec() { + + // Create workspace + // ---------------- + std::string filename = getPropertyValue(FilenameStr); + ANSTO::Tar::File tarFile(filename); + if (!tarFile.good()) + throw std::invalid_argument("invalid EMU file"); + createWorkspace(tarFile); + API::LogManager &logManager = m_localWorkspace->mutableRun(); + API::Progress prog(this, 0.0, 1.0, Progress_Total); + + // Load instrument and workspace properties + // ---------------------------------------- + loadParameters(tarFile, logManager); + prog.doReport("creating instrument"); + loadInstrument(); + + // Get the region of interest and filters + // + std::string maskfile = getPropertyValue(MaskStr); + std::string seltubes = getPropertyValue(SelectDetectorTubesStr); + std::vector<bool> roi = createRoiVector(seltubes, maskfile); + double timeMaxBoundary = getProperty(FilterByTimeStopStr); + if (isEmpty(timeMaxBoundary)) + timeMaxBoundary = std::numeric_limits<double>::infinity(); + TimeLimits timeBoundary(getProperty(FilterByTimeStartStr), timeMaxBoundary); + + // lambda to simplify loading instrument parameters + auto instr = m_localWorkspace->getInstrument(); + auto iparam = [&instr](std::string tag) { + return instr->getNumberParameter(tag)[0]; + }; + + // Update the neutronic positions for the indirect detectors + // noting that the vertical and horizontal are a continuous + // sequence starting from 0 + // + double sampleAnalyser = iparam("SampleAnalyser"); + auto endID = static_cast<detid_t>(DETECTORS * PIXELS_PER_TUBE); + for (detid_t detID = 0; detID < endID; detID++) + updateNeutronicPostions(detID, sampleAnalyser); + + // get the detector map from raw input to a physical detector + // + std::string dmapStr = instr->getParameterAsString("DetectorMap"); + std::vector<size_t> detMapIndex = std::vector<size_t>(DETECTORS, 0); + mapRangeToIndex(dmapStr, detMapIndex, [](size_t n) { return n; }); + + // Collect the L2 distances, Doppler characteristics and + // initiate the TOF converter + // + loadDetectorL2Values(); + loadDopplerParameters(logManager); + double v2 = iparam("AnalysedV2"); // analysed velocity in metres per sec + double framePeriod = + 1.0e6 / m_dopplerFreq; // period and max direct as microsec + double sourceSample = iparam("SourceSample"); + ConvertTOF convertTOF(m_dopplerAmpl, m_dopplerFreq, m_dopplerPhase, + sourceSample, v2, m_detectorL2); + + Types::Core::DateAndTime start_time( + logManager.getPropertyValueAsType<std::string>("StartTime")); + std::string time_str = start_time.toISO8601String(); + + // Load the events file + // -------------------- + // First count the number of events to reserve memory and then assign the + // events to the detectors + + // load events + size_t numberHistograms = m_localWorkspace->getNumberHistograms(); + std::vector<EventVector_pt> eventVectors(numberHistograms, nullptr); + std::vector<size_t> eventCounts(numberHistograms, 0); + + // Discriminating between direct and analysed is based on the auxillary time + // and is determined by the graphite chopper frequency and v2 which are stable + // so these limits are kept in the instrument parameter file. Convert from + // milsec to microsec. + TimeLimits directLimits(1000.0 * iparam("DirectTauxMin"), + 1000.0 * iparam("DirectTauxMax")); + TimeLimits analysedLimits(1000.0 * iparam("AnalysedTauxMin"), + 1000.0 * iparam("AnalysedTauxMax")); + + // fabs because the value can be negative + double gatePeriod = 1.0e6 / fabs(logManager.getPropertyValueAsType<double>( + "GraphiteChopperFrequency")); + + // count total events per pixel and reserve necessary memory + EMU::EventCounter eventCounter(roi, detMapIndex, PIXELS_PER_TUBE, framePeriod, + gatePeriod, timeBoundary, directLimits, + analysedLimits, eventCounts); + loadEvents(prog, "loading neutron counts", tarFile, eventCounter); + ANSTO::ProgressTracker progTracker(prog, "creating neutron event lists", + numberHistograms, Progress_ReserveMemory); + prepareEventStorage(progTracker, eventCounts, eventVectors); + + // now perfrom the actual event collection and TOF convert if necessary + bool saveAsTOF = !getProperty(RawDopplerTimeStr); + EMU::EventAssigner eventAssigner( + roi, detMapIndex, PIXELS_PER_TUBE, framePeriod, gatePeriod, timeBoundary, + directLimits, analysedLimits, convertTOF, eventVectors, saveAsTOF); + loadEvents(prog, "loading neutron events (TOF)", tarFile, eventAssigner); + + // just to make sure the bins hold it all and setup the detector masks + m_localWorkspace->setAllX( + HistogramData::BinEdges{std::max(0.0, floor(eventAssigner.tofMin())), + eventAssigner.tofMax() + 1}); + setupDetectorMasks(roi); + + // set log values + auto frame_count = static_cast<int>(eventCounter.numFrames()); + + logManager.addProperty("filename", filename); + logManager.addProperty("frame_count", frame_count); + + Types::Core::time_duration duration = boost::posix_time::microseconds( + static_cast<boost::int64_t>(eventCounter.duration() * 1.0e6)); + Types::Core::DateAndTime end_time(start_time + duration); + logManager.addProperty("start_time", start_time.toISO8601String()); + logManager.addProperty("end_time", end_time.toISO8601String()); + + setProperty("OutputWorkspace", m_localWorkspace); +} + +// set up the detector masks +void LoadEMU::setupDetectorMasks(std::vector<bool> &roi) { + + // count total number of masked bins + size_t maskedBins = 0; + for (size_t i = 0; i != roi.size(); i++) + if (!roi[i]) + maskedBins++; + + if (maskedBins > 0) { + // create list of masked bins + std::vector<size_t> maskIndexList(maskedBins); + size_t maskIndex = 0; + + for (size_t i = 0; i != roi.size(); i++) + if (!roi[i]) + maskIndexList[maskIndex++] = i; + + API::IAlgorithm_sptr maskingAlg = createChildAlgorithm("MaskDetectors"); + maskingAlg->setProperty("Workspace", m_localWorkspace); + maskingAlg->setProperty("WorkspaceIndexList", maskIndexList); + maskingAlg->executeAsChildAlg(); + } +} + +// prepare the event storage +void LoadEMU::prepareEventStorage(ANSTO::ProgressTracker &progTracker, + std::vector<size_t> &eventCounts, + std::vector<EventVector_pt> &eventVectors) { + + size_t numberHistograms = eventCounts.size(); + for (size_t i = 0; i != numberHistograms; ++i) { + DataObjects::EventList &eventList = m_localWorkspace->getSpectrum(i); + + eventList.setSortOrder(DataObjects::PULSETIME_SORT); + eventList.reserve(eventCounts[i]); + + eventList.setDetectorID(static_cast<detid_t>(i)); + eventList.setSpectrumNo(static_cast<detid_t>(i)); + + DataObjects::getEventsFrom(eventList, eventVectors[i]); + + progTracker.update(i); + } + progTracker.complete(); +} + +// get and log the Doppler parameters +void LoadEMU::loadDopplerParameters(API::LogManager &logm) { + + auto instr = m_localWorkspace->getInstrument(); + m_dopplerFreq = logm.getPropertyValueAsType<double>("DopplerFrequency"); + m_dopplerAmpl = logm.getPropertyValueAsType<double>("DopplerAmplitude"); + m_dopplerPhase = getProperty(OverrideDopplerPhaseStr); + if (isEmpty(m_dopplerPhase)) { + // sinusoidal motion crossing a threshold with a delay + double doppThreshold = + instr->getNumberParameter("DopplerReferenceThreshold")[0]; + double doppDelay = instr->getNumberParameter("DopplerReferenceDelay")[0]; + m_dopplerPhase = + 180.0 - asin(0.001 * doppThreshold / m_dopplerAmpl) * 180.0 / M_PI + + doppDelay * m_dopplerFreq; + } + logm.addProperty<double>("DopplerPhase", m_dopplerPhase); +} + +// Recovers the L2 neutronic distance for each detector. +void LoadEMU::loadDetectorL2Values() { + + m_detectorL2 = std::vector<double>(HISTOGRAMS); + const auto &detectorInfo = m_localWorkspace->detectorInfo(); + auto detIDs = detectorInfo.detectorIDs(); + for (const auto detID : detIDs) { + auto ix = detectorInfo.indexOf(detID); + double l2 = detectorInfo.l2(ix); + m_detectorL2[detID] = l2; + } +} + +// update the neutronic positins +void LoadEMU::updateNeutronicPostions(detid_t detID, double sampleAnalyser) { + + // get the instrument + + // get the list of indirect horizontal detectors + + // for each detector get the current position and scale + // the detectors + + Geometry::Instrument_const_sptr instrument = + m_localWorkspace->getInstrument(); + auto &compInfo = m_localWorkspace->mutableComponentInfo(); + + try { + auto component = instrument->getDetector(detID); + double rho, theta, phi; + V3D position = component->getPos(); + position.getSpherical(rho, theta, phi); + + double scale = -(2 * sampleAnalyser + rho) / rho; + position *= scale; + + const auto componentIndex = compInfo.indexOf(component->getComponentID()); + compInfo.setPosition(componentIndex, position); + } catch (const std::runtime_error &) { + // just continue with the remainder + } +} + +// region of interest is defined by the selected detectors and the mask +// +std::vector<bool> LoadEMU::createRoiVector(const std::string &selected, + const std::string &maskfile) { + + std::vector<bool> result(HISTOGRAMS, true); + + // turn off pixels linked to missing tubes + if (!selected.empty()) { + std::vector<bool> tubes(HISTO_BINS_X, false); + mapRangeToIndex(selected, tubes, [](size_t) { return true; }); + for (size_t i = 0; i < HISTO_BINS_X; i++) { + if (tubes[i] == false) { + for (size_t j = 0; j < PIXELS_PER_TUBE; j++) { + result[i * PIXELS_PER_TUBE + j] = false; + } + } + } + } + + if (maskfile.length() == 0) + return result; + + std::ifstream input(maskfile.c_str()); + if (!input.good()) + throw std::invalid_argument("invalid mask file"); + + std::string line; + while (std::getline(input, line)) { + auto i0 = line.find("<detids>"); + auto iN = line.find("</detids>"); + + if ((i0 != std::string::npos) && (iN != std::string::npos) && (i0 < iN)) { + line = line.substr(i0 + 8, iN - i0 - 8); // 8 = len("<detids>") + mapRangeToIndex(line, result, [](size_t) { return false; }); + } + } + + return result; +} + +// load parameters from input file +void LoadEMU::loadParameters(ANSTO::Tar::File &tarFile, API::LogManager &logm) { + + // extract log and hdf file + const std::vector<std::string> &files = tarFile.files(); + auto file_it = + std::find_if(files.cbegin(), files.cend(), [](const std::string &file) { + return file.rfind(".hdf") == file.length() - 4; + }); + if (file_it != files.end()) { + tarFile.select(file_it->c_str()); + // extract hdf file into tmp file + Poco::TemporaryFile hdfFile; + boost::shared_ptr<FILE> handle(fopen(hdfFile.path().c_str(), "wb"), fclose); + if (handle) { + // copy content + char buffer[4096]; + size_t bytesRead; + while (0 != (bytesRead = tarFile.read(buffer, sizeof(buffer)))) + fwrite(buffer, bytesRead, 1, handle.get()); + handle.reset(); + + NeXus::NXRoot root(hdfFile.path()); + NeXus::NXEntry entry = root.openFirstEntry(); + + MapNeXusToProperty<std::string>(entry, "sample/name", "unknown", logm, + "SampleName", ""); + MapNeXusToProperty<std::string>(entry, "sample/description", "unknown", + logm, "SampleDescription", ""); + MapNeXusToProperty<std::string>( + entry, "start_time", "2000-01-01T00:00:00", logm, "StartTime", ""); + + MapNeXusToProperty<double>(entry, "instrument/doppler/ctrl/amplitude", + 75.0, logm, "DopplerAmplitude", 0.001); + double speedToFreq = + 0.5 / (M_PI * m_localWorkspace->run().getPropertyValueAsType<double>( + "DopplerAmplitude")); + MapNeXusToProperty<double>(entry, "instrument/doppler/ctrl/velocity", 4.7, + logm, "DopplerFrequency", speedToFreq); + + MapNeXusToProperty<double>(entry, "instrument/chpr/background/actspeed", + 1272.8, logm, "BackgroundChopperFrequency", + 1.0 / 60); + MapNeXusToProperty<double>(entry, "instrument/chpr/graphite/actspeed", + 2545.6, logm, "GraphiteChopperFrequency", + 1.0 / 60); + MapNeXusToProperty<double>(entry, "instrument/hztubegap", 0.02, logm, + "HorizontalTubesGap", 1.0); + MapNeXusToProperty<int32_t>(entry, "monitor/bm1_counts", 0, logm, + "MonitorCounts", 1); + + // temp fix for source position when loading IDF + MapNeXusToProperty<double>(entry, "instrument/doppler/tosource", 2.035, + logm, "SourceSample", 1.0); + } + } + + // patching + file_it = std::find(files.cbegin(), files.cend(), "History.log"); + if (file_it != files.cend()) { + tarFile.select(file_it->c_str()); + std::string logContent; + logContent.resize(tarFile.selected_size()); + tarFile.read(&logContent[0], logContent.size()); + std::istringstream data(logContent); + Poco::AutoPtr<Poco::Util::PropertyFileConfiguration> conf( + new Poco::Util::PropertyFileConfiguration(data)); + + if (conf->hasProperty("SampleName")) { + std::string name = conf->getString("SampleName"); + logm.addProperty("SampleName", name); + } + } +} + +// load the instrument definition and instrument parameters +void LoadEMU::loadInstrument() { + + // loads the IDF and parameter file + API::IAlgorithm_sptr loadInstrumentAlg = + createChildAlgorithm("LoadInstrument"); + loadInstrumentAlg->setProperty("Workspace", m_localWorkspace); + loadInstrumentAlg->setPropertyValue("InstrumentName", "EMUau"); + loadInstrumentAlg->setProperty("RewriteSpectraMap", + Mantid::Kernel::OptionalBool(false)); + loadInstrumentAlg->executeAsChildAlg(); +} + +// read counts/events from binary file +template <class EventProcessor> +void LoadEMU::loadEvents(API::Progress &prog, const char *progMsg, + ANSTO::Tar::File &tarFile, + EventProcessor &eventProcessor) { + + using namespace ANSTO; + + prog.doReport(progMsg); + + // select bin file + int64_t fileSize = 0; + const std::vector<std::string> &files = tarFile.files(); + for (const auto &file : files) + if (file.rfind(".bin") == file.length() - 4) { + tarFile.select(file.c_str()); + fileSize = tarFile.selected_size(); + break; + } + + // for progress notifications + ANSTO::ProgressTracker progTracker(prog, progMsg, fileSize, + Progress_LoadBinFile); + + ReadEventFile(tarFile, eventProcessor, progTracker, 100, false); +} +} // namespace DataHandling +} // namespace Mantid \ No newline at end of file diff --git a/Framework/DataHandling/src/LoadEventNexus.cpp b/Framework/DataHandling/src/LoadEventNexus.cpp index 82617b23e93ac03809bd31514cb964389bea075d..26d19710944eb5f3bd1ed8fae5467963ea67ef77 100644 --- a/Framework/DataHandling/src/LoadEventNexus.cpp +++ b/Framework/DataHandling/src/LoadEventNexus.cpp @@ -82,7 +82,7 @@ int LoadEventNexus::confidence(Kernel::NexusDescriptor &descriptor) const { /** Initialisation method. */ void LoadEventNexus::init() { - const std::vector<std::string> exts{"_event.nxs", ".nxs.h5", ".nxs"}; + const std::vector<std::string> exts{".nxs.h5", ".nxs", "_event.nxs"}; this->declareProperty( Kernel::make_unique<FileProperty>("Filename", "", FileProperty::Load, exts), diff --git a/Framework/DataHandling/src/LoadILLSANS.cpp b/Framework/DataHandling/src/LoadILLSANS.cpp index e9ab2371b616104a94af92ef51c93c1cf043374e..bd71a01be4d69e1f04587dee3d4b242261f960e3 100644 --- a/Framework/DataHandling/src/LoadILLSANS.cpp +++ b/Framework/DataHandling/src/LoadILLSANS.cpp @@ -14,6 +14,7 @@ #include "MantidAPI/WorkspaceFactory.h" #include "MantidGeometry/IDetector.h" #include "MantidGeometry/Instrument.h" +#include "MantidGeometry/Instrument/RectangularDetector.h" #include "MantidHistogramData/LinearGenerator.h" #include "MantidKernel/ConfigService.h" #include "MantidKernel/OptionalBool.h" @@ -58,7 +59,7 @@ const std::string LoadILLSANS::category() const { /// Algorithm's summary. @see Algorithm::summery const std::string LoadILLSANS::summary() const { - return "Loads a ILL nexus files for SANS instruments D11, D22, D33."; + return "Loads ILL nexus files for SANS instruments D11, D22, D33."; } //---------------------------------------------------------------------------------------------- @@ -139,6 +140,7 @@ void LoadILLSANS::exec() { progress.report("Setting sample logs"); setFinalProperties(filename); + setPixelSize(); setProperty("OutputWorkspace", m_localWorkspace); } @@ -460,6 +462,9 @@ void LoadILLSANS::moveDetectorsD33(const DetectorPosition &detPos) { // Move in Y moveDetectorVertical(detPos.shiftUp, "front_detector_top"); moveDetectorVertical(-detPos.shiftDown, "front_detector_bottom"); + // Set the sample log + API::Run &runDetails = m_localWorkspace->mutableRun(); + runDetails.addProperty<double>("L2", detPos.distanceSampleRear, true); } /** @@ -481,6 +486,8 @@ void LoadILLSANS::moveDetectorDistance(double distance, mover->executeAsChildAlg(); g_log.debug() << "Moving component '" << componentName << "' to Z = " << distance << '\n'; + API::Run &runDetails = m_localWorkspace->mutableRun(); + runDetails.addProperty<double>("L2", distance, true); } /** @@ -586,8 +593,17 @@ void LoadILLSANS::loadMetaData(const NeXus::NXEntry &entry, "scientist!"); } } else { - double wavelengthRes = - entry.getFloat(instrumentNamePath + "/selector/wavelength_res"); + double wavelengthRes = 10.; + const std::string entryResolution = instrumentNamePath + "/selector/"; + try { + wavelengthRes = entry.getFloat(entryResolution + "wavelength_res"); + } catch (std::runtime_error) { + try { + wavelengthRes = entry.getFloat(entryResolution + "wave_length_res"); + } catch (std::runtime_error) { + g_log.warning("Could not find wavelength resolution, assuming 10%"); + } + } runDetails.addProperty<double>("wavelength", wavelength); double ei = m_loader.calculateEnergy(wavelength); runDetails.addProperty<double>("Ei", ei, true); @@ -595,55 +611,9 @@ void LoadILLSANS::loadMetaData(const NeXus::NXEntry &entry, m_defaultBinning[0] = wavelength - wavelengthRes * wavelength * 0.01 / 2; m_defaultBinning[1] = wavelength + wavelengthRes * wavelength * 0.01 / 2; } -} - -/** - * @param lambda : wavelength in Angstroms - * @param twoTheta : twoTheta in degreess - * @return Q : momentum transfer [Aˆ-1] - */ -double LoadILLSANS::calculateQ(const double lambda, - const double twoTheta) const { - return (4 * M_PI * std::sin(twoTheta * (M_PI / 180) / 2)) / (lambda); -} - -/** - * Calculates the max and min Q - * @return pair<min, max> - */ -std::pair<double, double> LoadILLSANS::calculateQMaxQMin() { - double min = std::numeric_limits<double>::max(), - max = std::numeric_limits<double>::min(); - g_log.debug("Calculating Qmin Qmax..."); - std::size_t nHist = m_localWorkspace->getNumberHistograms(); - const auto &spectrumInfo = m_localWorkspace->spectrumInfo(); - for (std::size_t i = 0; i < nHist; ++i) { - if (!spectrumInfo.isMonitor(i)) { - const auto &lambdaBinning = m_localWorkspace->x(i); - Kernel::V3D detPos = spectrumInfo.position(i); - double r, theta, phi; - detPos.getSpherical(r, theta, phi); - double v1 = calculateQ(*(lambdaBinning.begin()), theta); - double v2 = calculateQ(*(lambdaBinning.end() - 1), theta); - if (i == 0) { - min = v1; - max = v1; - } - if (v1 < min) { - min = v1; - } - if (v2 < min) { - min = v2; - } - if (v1 > max) { - max = v1; - } - if (v2 > max) { - max = v2; - } - } - } - return std::pair<double, double>(min, max); + // Add a log called timer with the value of duration + const double duration = entry.getFloat("duration"); + runDetails.addProperty<double>("timer", duration); } /** @@ -653,9 +623,6 @@ std::pair<double, double> LoadILLSANS::calculateQMaxQMin() { void LoadILLSANS::setFinalProperties(const std::string &filename) { API::Run &runDetails = m_localWorkspace->mutableRun(); runDetails.addProperty("is_frame_skipping", 0); - std::pair<double, double> minmax = LoadILLSANS::calculateQMaxQMin(); - runDetails.addProperty("qmin", minmax.first); - runDetails.addProperty("qmax", minmax.second); NXhandle nxHandle; NXstatus nxStat = NXopen(filename.c_str(), NXACC_READ, &nxHandle); if (nxStat != NX_ERROR) { @@ -717,5 +684,27 @@ void LoadILLSANS::moveSource() { mover->executeAsChildAlg(); } +/** + * Sets the width (x) and height (y) of the pixel + */ +void LoadILLSANS::setPixelSize() { + const auto instrument = m_localWorkspace->getInstrument(); + const std::string component = + (m_instrumentName == "D33") ? "back_detector" : "detector"; + auto detector = instrument->getComponentByName(component); + auto rectangle = + boost::dynamic_pointer_cast<const Geometry::RectangularDetector>( + detector); + if (rectangle) { + const double dx = rectangle->xstep(); + const double dy = rectangle->ystep(); + API::Run &runDetails = m_localWorkspace->mutableRun(); + runDetails.addProperty<double>("pixel_width", dx); + runDetails.addProperty<double>("pixel_height", dy); + } else { + g_log.debug("No pixel size available"); + } +} + } // namespace DataHandling } // namespace Mantid diff --git a/Framework/DataHandling/src/LoadInstrument.cpp b/Framework/DataHandling/src/LoadInstrument.cpp index 15116e5b340f42f89fd247f17abbfbf371a64041..dd956e8c9b381016765f4bb7f792a8cc71ba3de0 100644 --- a/Framework/DataHandling/src/LoadInstrument.cpp +++ b/Framework/DataHandling/src/LoadInstrument.cpp @@ -285,9 +285,8 @@ std::string LoadInstrument::getFullPathParamIDF(std::string directoryName) { const std::string::size_type prefix_end(instrumentFile.find(definitionPart)); const std::string::size_type suffix_start = prefix_end + definitionPart.length(); - // Make prefix and force it to be upper case + // Get prefix and leave case sensitive std::string prefix = instrumentFile.substr(0, prefix_end); - std::transform(prefix.begin(), prefix.end(), prefix.begin(), toupper); // Make suffix ensuring it has positive length std::string suffix = ".xml"; if (suffix_start < instrumentFile.length()) { diff --git a/Framework/DataHandling/src/LoadLog.cpp b/Framework/DataHandling/src/LoadLog.cpp index ae74cac2367ed57cb9879646a40cd32f73ea7fd7..53aee9d82be52c2abd5a4d4b6a5eb63bc86dbced 100644 --- a/Framework/DataHandling/src/LoadLog.cpp +++ b/Framework/DataHandling/src/LoadLog.cpp @@ -26,7 +26,6 @@ #include <Poco/File.h> #include <Poco/Path.h> #include <boost/algorithm/string.hpp> -#include <boost/regex.hpp> #include <fstream> // used to get ifstream #include <sstream> diff --git a/Framework/DataHandling/src/LoadSampleShape.cpp b/Framework/DataHandling/src/LoadSampleShape.cpp index 218ae7efc2ddfc4d40fab40eaba5d8bf9af46957..22e65d1143578a80915523edd6b7995e951fc400 100644 --- a/Framework/DataHandling/src/LoadSampleShape.cpp +++ b/Framework/DataHandling/src/LoadSampleShape.cpp @@ -5,6 +5,7 @@ // & Institut Laue - Langevin // SPDX - License - Identifier: GPL - 3.0 + #include "MantidDataHandling/LoadSampleShape.h" +#include "MantidDataHandling/LoadAsciiStl.h" #include "MantidDataHandling/LoadBinaryStl.h" #include "MantidGeometry/Objects/MeshObject.h" @@ -33,128 +34,6 @@ using namespace Geometry; namespace { -bool areEqualVertices(Kernel::V3D const &v1, Kernel::V3D const &v2) { - Kernel::V3D diff = v1 - v2; - return diff.norm() < 1e-9; // This is 1 nanometre for a unit of a metre. -} - -// Read, check and ignore line in STL file. Return true if line is read -bool readSTLLine(std::ifstream &file, std::string const &type) { - std::string line; - if (getline(file, line)) { - boost::trim(line); - if (line.size() < type.size() || line.substr(0, type.size()) != type) { - // Before throwing, check for endsolid statment - std::string type2 = "endsolid"; - if (line.size() < type2.size() || line.substr(0, type2.size()) != type2) { - throw std::runtime_error("Expected STL line begining with " + type + - " or " + type2); - } else { - return false; // ends reading at endsolid - } - } - return true; // expected line read, then ignored - } else { - return false; // end of file - } -} - -/* Reads vertex from STL file and returns true if vertex is found */ -bool readSTLVertex(std::ifstream &file, V3D &vertex) { - std::string line; - if (getline(file, line)) { - boost::trim(line); - std::vector<std::string> tokens; - boost::split(tokens, line, boost::is_any_of(" "), boost::token_compress_on); - if (tokens.size() == 4 && tokens[0] == "vertex") { - vertex.setX(boost::lexical_cast<double>(tokens[1])); - vertex.setY(boost::lexical_cast<double>(tokens[2])); - vertex.setZ(boost::lexical_cast<double>(tokens[3])); - return true; - } else { - throw std::runtime_error("Error on reading STL vertex"); - } - } - return false; -} - -/* Reads triangle for STL file and returns true if triangle is found */ -bool readSTLTriangle(std::ifstream &file, V3D &v1, V3D &v2, V3D &v3) { - - if (readSTLLine(file, "facet") && readSTLLine(file, "outer loop")) { - bool ok = (readSTLVertex(file, v1) && readSTLVertex(file, v2) && - readSTLVertex(file, v3)); - if (!ok) { - throw std::runtime_error("Error on reading STL triangle"); - } - } else { - return false; // End of file - } - return readSTLLine(file, "endloop") && readSTLLine(file, "endfacet"); -} - -// Adds vertex to list if distinct and returns index to vertex added or equal -uint16_t addSTLVertex(V3D &vertex, std::vector<V3D> &vertices) { - for (uint16_t i = 0; i < vertices.size(); ++i) { - if (areEqualVertices(vertex, vertices[i])) { - return i; - } - } - vertices.push_back(vertex); - uint16_t index = static_cast<uint16_t>(vertices.size() - 1); - if (index != vertices.size() - 1) { - throw std::runtime_error("Too many vertices in solid"); - } - return index; -} - -std::unique_ptr<MeshObject> readSTLMeshObject(std::ifstream &file) { - std::vector<uint16_t> triangleIndices; - std::vector<V3D> vertices; - V3D t1, t2, t3; - - while (readSTLTriangle(file, t1, t2, t3)) { - // Add triangle if all 3 vertices are distinct - if (!areEqualVertices(t1, t2) && !areEqualVertices(t1, t3) && - !areEqualVertices(t2, t3)) { - triangleIndices.push_back(addSTLVertex(t1, vertices)); - triangleIndices.push_back(addSTLVertex(t2, vertices)); - triangleIndices.push_back(addSTLVertex(t3, vertices)); - } - } - // Use efficient constructor of MeshObject - std::unique_ptr<MeshObject> retVal = std::unique_ptr<MeshObject>( - new MeshObject(std::move(triangleIndices), std::move(vertices), - Mantid::Kernel::Material())); - return retVal; -} - -std::unique_ptr<Geometry::MeshObject> -readSTLSolid(std::ifstream &file, std::string &name, std::string filename) { - // Read Solid name - // We expect line after trimming to be "solid "+name. - std::string line; - if (getline(file, line)) { - boost::trim(line); - if (line.size() < 5 || line.substr(0, 5) != "solid") { - // attempt to load stl binary instead - std::unique_ptr<LoadBinaryStl> binaryStlReader = - Kernel::make_unique<LoadBinaryStl>(filename); - if (binaryStlReader->isBinarySTL()) { - return binaryStlReader->LoadBinaryStl::readStl(); - } else { - throw std::runtime_error("Expected start of solid"); - } - - } else { - name = line.substr(6, std::string::npos); - } - // Read Solid shape - return readSTLMeshObject(file); - } - return nullptr; -} - bool getOFFline(std::ifstream &file, std::string &line) { // Get line from OFF file ignoring blank lines and comments // The line output is ready trimmed @@ -322,8 +201,16 @@ void LoadSampleShape::exec() { if (filetype == "off") { shape = readOFFshape(file); } else /* stl */ { - std::string solidName = ""; - shape = readSTLSolid(file, solidName, filename); + auto asciiStlReader = LoadAsciiStl(filename); + auto binaryStlReader = LoadBinaryStl(filename); + if (asciiStlReader.isAsciiSTL()) { + shape = asciiStlReader.readStl(); + } else if (binaryStlReader.isBinarySTL()) { + shape = binaryStlReader.readStl(); + } else { + throw Kernel::Exception::ParseError( + "Could not read file, did not match either STL Format", filename, 0); + } } // Put shape into sample. diff --git a/Framework/DataHandling/src/LoadStl.cpp b/Framework/DataHandling/src/LoadStl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f075134197b34dd2ddb9bf35d9c8efa0b6406271 --- /dev/null +++ b/Framework/DataHandling/src/LoadStl.cpp @@ -0,0 +1,27 @@ +#include "MantidDataHandling/LoadStl.h" +#include <Poco/File.h> + +namespace Mantid { +namespace DataHandling { + +// Adds vertex to list if distinct and returns index to vertex added or equal +uint16_t LoadStl::addSTLVertex(Kernel::V3D &vertex) { + for (uint16_t i = 0; i < m_verticies.size(); ++i) { + if (areEqualVertices(vertex, m_verticies[i])) { + return i; + } + } + m_verticies.emplace_back(vertex); + uint16_t index = static_cast<uint16_t>(m_verticies.size() - 1); + if (index != m_verticies.size() - 1) { + throw std::runtime_error("Too many vertices in solid"); + } + return index; +} + +bool LoadStl::areEqualVertices(Kernel::V3D const &v1, Kernel::V3D const &v2) { + Kernel::V3D diff = v1 - v2; + return diff.norm() < 1e-9; // This is 1 nanometre for a unit of a metre. +} +} // namespace DataHandling +} // namespace Mantid \ No newline at end of file diff --git a/Framework/DataHandling/src/MoveInstrumentComponent.cpp b/Framework/DataHandling/src/MoveInstrumentComponent.cpp index 7ef36b5102b9775c04b7cd70a7c7fc82f577b5f6..66403a9c97eab4b856f5dca7fad0ecac105fd76c 100644 --- a/Framework/DataHandling/src/MoveInstrumentComponent.cpp +++ b/Framework/DataHandling/src/MoveInstrumentComponent.cpp @@ -8,6 +8,7 @@ #include "MantidDataObjects/PeaksWorkspace.h" #include "MantidDataObjects/Workspace2D.h" #include "MantidGeometry/Instrument/ComponentInfo.h" +#include "MantidGeometry/Instrument/ComponentInfoBankHelpers.h" #include "MantidGeometry/Instrument/GridDetectorPixel.h" #include "MantidKernel/Exception.h" @@ -115,15 +116,10 @@ void MoveInstrumentComponent::exec() { const auto &componentInfo = inputW ? inputW->componentInfo() : inputP->componentInfo(); auto compIndex = componentInfo.indexOf(comp->getComponentID()); - auto parent = componentInfo.parent(compIndex); - auto grandParent = componentInfo.parent(parent); - auto grandParentType = componentInfo.componentType(grandParent); - if (componentInfo.isDetector(compIndex) && - (grandParentType == Mantid::Beamline::ComponentType::Grid || - grandParentType == Mantid::Beamline::ComponentType::Rectangular || - grandParentType == Mantid::Beamline::ComponentType::Structured)) { + if (ComponentInfoBankHelpers::isDetectorFixedInBank(componentInfo, + compIndex)) { // DetectorInfo makes changing positions possible but we keep the old - // behavior of ignoring position changes for GridDetectorPixel. + // behavior of ignoring position changes for Structured banks. g_log.warning("Component is fixed within a structured bank, moving is not " "possible, doing nothing."); return; diff --git a/Framework/DataHandling/src/SaveNexusProcessed.cpp b/Framework/DataHandling/src/SaveNexusProcessed.cpp index 2426c849d222c5f81368014f4dfe13ab5d1e8158..3de99640cfd8bd399effd09add1ded83ee241295 100644 --- a/Framework/DataHandling/src/SaveNexusProcessed.cpp +++ b/Framework/DataHandling/src/SaveNexusProcessed.cpp @@ -21,7 +21,6 @@ #include "MantidKernel/BoundedValidator.h" #include "MantidNexus/NexusFileIO.h" #include <Poco/File.h> -#include <boost/regex.hpp> #include <boost/shared_ptr.hpp> using namespace Mantid::API; diff --git a/Framework/DataHandling/test/LoadAsciiStlTest.h b/Framework/DataHandling/test/LoadAsciiStlTest.h new file mode 100644 index 0000000000000000000000000000000000000000..19d67dd4ce207a4de0432575937eb05fe23fb80c --- /dev/null +++ b/Framework/DataHandling/test/LoadAsciiStlTest.h @@ -0,0 +1,80 @@ +#ifndef LOAD_ASCIISTL_TEST_H_ +#define LOAD_ASCIISTL_TEST_H_ + +#include "MantidAPI/FileFinder.h" +#include "MantidDataHandling/LoadAsciiStl.h" +#include "MantidGeometry/Objects/MeshObject.h" +#include <cxxtest/TestSuite.h> + +using namespace Mantid; +using namespace Mantid::API; +using namespace Mantid::DataHandling; + +using namespace Mantid::Geometry; + +class LoadAsciiStlTest : public CxxTest::TestSuite { +public: + static LoadAsciiStlTest *createSuite() { return new LoadAsciiStlTest(); } + static void destroySuite(LoadAsciiStlTest *suite) { delete suite; } + + void testInit() {} + void test_cube() { + std::string path = FileFinder::Instance().getFullPath("cube.stl"); + auto Loader = LoadAsciiStl(path); + auto cube = Loader.readStl(); + TS_ASSERT(cube->hasValidShape()); + TS_ASSERT_EQUALS(cube->numberOfVertices(), 8); + TS_ASSERT_EQUALS(cube->numberOfTriangles(), 12); + TS_ASSERT_DELTA(cube->volume(), 3000, 0.001); + } + + void test_cylinder() { + std::string path = FileFinder::Instance().getFullPath("cylinder.stl"); + auto Loader = LoadAsciiStl(path); + auto cylinder = Loader.readStl(); + TS_ASSERT(cylinder->hasValidShape()); + TS_ASSERT_EQUALS(cylinder->numberOfVertices(), 722); + TS_ASSERT_EQUALS(cylinder->numberOfTriangles(), 1440); + TS_ASSERT_DELTA(cylinder->volume(), 589, 1); + } + + void test_tube() { + std::string path = FileFinder::Instance().getFullPath("tube.stl"); + auto Loader = LoadAsciiStl(path); + auto tube = Loader.readStl(); + TS_ASSERT(tube->hasValidShape()); + TS_ASSERT_EQUALS(tube->numberOfVertices(), 1080); + TS_ASSERT_EQUALS(tube->numberOfTriangles(), 2160); + TS_ASSERT_DELTA(tube->volume(), 7068, 1); + } + + void test_fail_invalid_stl_keyword() { + loadFailureTest("invalid_keyword.stl"); + } + + void test_fail_invalid_stl_vertex() { loadFailureTest("invalid_vertex.stl"); } + + void test_fail_invalid_stl_triangle() { + loadFailureTest("invalid_triangle.stl"); + } + + void loadFailureTest(const std::string filename) { + std::string path = FileFinder::Instance().getFullPath(filename); + auto Loader = LoadAsciiStl(path); + TS_ASSERT_THROWS_ANYTHING(Loader.readStl()); + } + + void test_return_false_on_binary_stl() { + std::string path = FileFinder::Instance().getFullPath("cubeBin.stl"); + auto Loader = LoadAsciiStl(path); + TS_ASSERT(!(Loader.isAsciiSTL())); + } + + void test_return_false_on_invalid_solid() { + std::string path = FileFinder::Instance().getFullPath("invalid_solid.stl"); + auto Loader = LoadAsciiStl(path); + TS_ASSERT(!(Loader.isAsciiSTL())); + } +}; + +#endif /* LOAD_ASCIISTL_TEST_H_ */ \ No newline at end of file diff --git a/Framework/DataHandling/test/LoadBinaryStlTest.h b/Framework/DataHandling/test/LoadBinaryStlTest.h index 4e5149b22b684d0388b69c0a28c6fd0b4078c780..fd5c3e33cf86ec75a9eb7928a7791002b889eb01 100644 --- a/Framework/DataHandling/test/LoadBinaryStlTest.h +++ b/Framework/DataHandling/test/LoadBinaryStlTest.h @@ -1,22 +1,12 @@ #ifndef LOAD_BINARYSTL_TEST_H_ #define LOAD_BINARYSTL_TEST_H_ - -#include "MantidAPI/AnalysisDataService.h" #include "MantidAPI/FileFinder.h" -#include "MantidAPI/FrameworkManager.h" -#include "MantidAPI/Sample.h" #include "MantidDataHandling/LoadBinaryStl.h" -#include "MantidDataHandling/LoadInstrument.h" #include "MantidGeometry/Objects/MeshObject.h" -#include "MantidKernel/OptionalBool.h" -#include "MantidTestHelpers/ComponentCreationHelper.h" -#include "MantidTestHelpers/WorkspaceCreationHelper.h" #include <cxxtest/TestSuite.h> - using namespace Mantid; using namespace Mantid::API; using namespace Mantid::DataHandling; -using namespace Mantid::DataObjects; using namespace Mantid::Geometry; class LoadBinaryStlTest : public CxxTest::TestSuite { @@ -32,34 +22,24 @@ public: TS_ASSERT_EQUALS(shape->numberOfTriangles(), triangles); TS_ASSERT_DELTA(shape->volume(), volume, delta); } - void test_loading_cube_stl() { std::string path = FileFinder::Instance().getFullPath("cubeBin.stl"); - - std::unique_ptr<LoadBinaryStl> loader = - std::make_unique<LoadBinaryStl>(path); - auto cube = loader->readStl(); - + auto loader = LoadBinaryStl(path); + auto cube = loader.readStl(); assert_shape_matches(cube, 8, 12, 3000, 0.001); } void test_loading_cylinder_stl() { std::string path = FileFinder::Instance().getFullPath("cylinderBin.stl"); - - std::unique_ptr<LoadBinaryStl> loader = - std::make_unique<LoadBinaryStl>(path); - auto cylinder = loader->readStl(); - + auto loader = LoadBinaryStl(path); + auto cylinder = loader.readStl(); assert_shape_matches(cylinder, 722, 1440, 589, 1); } void test_loading_tube_stl() { std::string path = FileFinder::Instance().getFullPath("tubeBin.stl"); - - std::unique_ptr<LoadBinaryStl> loader = - std::make_unique<LoadBinaryStl>(path); - auto tube = loader->readStl(); - + auto loader = LoadBinaryStl(path); + auto tube = loader.readStl(); assert_shape_matches(tube, 1080, 2160, 7068, 1); } // check that isBinaryStl returns false if the file contains an incomplete @@ -67,31 +47,22 @@ public: void test_fail_invalid_vertex() { std::string path = FileFinder::Instance().getFullPath("invalid_vertexBin.stl"); - - std::unique_ptr<LoadBinaryStl> loader = - std::make_unique<LoadBinaryStl>(path); - - TS_ASSERT(!(loader->isBinarySTL())); + auto loader = LoadBinaryStl(path); + TS_ASSERT(!(loader.isBinarySTL())); } // check that isBinaryStl returns false if the file contains an incomplete // triangle void test_fail_invalid_triangle() { std::string path = FileFinder::Instance().getFullPath("invalid_triangleBin.stl"); - - std::unique_ptr<LoadBinaryStl> loader = - std::make_unique<LoadBinaryStl>(path); - - TS_ASSERT(!(loader->isBinarySTL())); + auto loader = LoadBinaryStl(path); + TS_ASSERT(!(loader.isBinarySTL())); } void test_fail_ascii_stl() { std::string path = FileFinder::Instance().getFullPath("cube.stl"); - - std::unique_ptr<LoadBinaryStl> loader = - std::make_unique<LoadBinaryStl>(path); - - TS_ASSERT(!(loader->isBinarySTL())); + auto loader = LoadBinaryStl(path); + TS_ASSERT(!(loader.isBinarySTL())); } }; diff --git a/Framework/DataHandling/test/LoadEMUauTest.h b/Framework/DataHandling/test/LoadEMUauTest.h new file mode 100644 index 0000000000000000000000000000000000000000..4c9dffc97264f2a5f941523bbdfe05a4565c8d2b --- /dev/null +++ b/Framework/DataHandling/test/LoadEMUauTest.h @@ -0,0 +1,90 @@ + +#ifndef LOADEMUAUTEST_H_ +#define LOADEMUAUTEST_H_ + +#include <cxxtest/TestSuite.h> +#include <fstream> + +#include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/FrameworkManager.h" +#include "MantidAPI/Run.h" +#include "MantidAPI/WorkspaceFactory.h" +#include "MantidDataHandling/LoadEMU.h" +#include "MantidDataHandling/LoadInstrument.h" +#include "MantidDataObjects/WorkspaceSingleValue.h" +#include <Poco/Path.h> +#include <Poco/TemporaryFile.h> + +using namespace Mantid::API; +using namespace Mantid::Kernel; +using namespace Mantid::DataHandling; +using namespace Mantid::DataObjects; + +class LoadEMUauTest : public CxxTest::TestSuite { +public: + void test_load_emu_algorithm_init() { + LoadEMU algToBeTested; + + TS_ASSERT_THROWS_NOTHING(algToBeTested.initialize()); + TS_ASSERT(algToBeTested.isInitialized()); + } + + void test_load_emu_algorithm() { + LoadEMU algToBeTested; + + if (!algToBeTested.isInitialized()) + algToBeTested.initialize(); + + std::string outputSpace = "LoadEMUauTest"; + algToBeTested.setPropertyValue("OutputWorkspace", outputSpace); + + // should fail because mandatory parameter has not been set + TS_ASSERT_THROWS(algToBeTested.execute(), std::runtime_error); + + // should succeed now + std::string inputFile = "EMU0006330.tar"; + algToBeTested.setPropertyValue("Filename", inputFile); + algToBeTested.setPropertyValue("SelectDetectorTubes", "16-50"); + TS_ASSERT_THROWS_NOTHING(algToBeTested.execute()); + TS_ASSERT(algToBeTested.isExecuted()); + + // get workspace generated + MatrixWorkspace_sptr output = + AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>( + outputSpace); + + // check number of histograms + TS_ASSERT_EQUALS(output->getNumberHistograms(), 6528); + double sum = 0.0; + for (size_t i = 0; i < output->getNumberHistograms(); i++) + sum += output->readY(i)[0]; + TS_ASSERT_EQUALS(sum, 55126); + + // check that all required log values are there + auto run = output->run(); + + // test start and end time + TS_ASSERT( + run.getProperty("start_time")->value().compare("2018-07-26T10:13:12") == + 0) + TS_ASSERT( + run.getProperty("end_time")->value().find("2018-07-26T10:17:12.6") == 0) + + // test some data properties + auto logpm = [&run](std::string tag) { + return run.getPropertyValueAsType<double>(tag); + }; + TS_ASSERT_DELTA(logpm("DopplerFrequency"), 9.974, 1.0e-3); + TS_ASSERT_DELTA(logpm("DopplerAmplitude"), 0.075, 1.0e-4); + + // test some instrument parameters + auto instr = output->getInstrument(); + auto iparam = [&instr](std::string tag) { + return instr->getNumberParameter(tag)[0]; + }; + TS_ASSERT_DELTA(iparam("AnalysedV2"), 630.866, 1.0e-3); + TS_ASSERT_DELTA(iparam("SampleAnalyser"), 1.8, 1.0e-3); + } +}; + +#endif /*LoadEMUTEST_H_*/ \ No newline at end of file diff --git a/Framework/DataHandling/test/LoadILLDiffractionTest.h b/Framework/DataHandling/test/LoadILLDiffractionTest.h index 014f878632912bc35e3b274e4067bb76aaad2d4c..3e076c3edc90a8fb50822252b185a9b676f64f3d 100644 --- a/Framework/DataHandling/test/LoadILLDiffractionTest.h +++ b/Framework/DataHandling/test/LoadILLDiffractionTest.h @@ -318,30 +318,24 @@ public: "2015-04-16T16:40:34.289000000"; const std::string EXPECTED_END_TIME = "2015-04-16T16:41:11.956000000"; - for (size_t i = 0; i < detInfo.size(); ++i) { - TS_ASSERT_EQUALS(detInfo.scanCount(i), SCAN_COUNT) - - const auto &startRange = detInfo.scanInterval({i, 0}); - const auto &secondRange = detInfo.scanInterval({i, 1}); - const auto &secondFromEndRange = - detInfo.scanInterval({i, detInfo.scanCount(i) - 2}); - const auto &endRange = - detInfo.scanInterval({i, detInfo.scanCount(i) - 1}); - - TS_ASSERT_EQUALS(startRange.first.toISO8601String(), EXPECTED_START_TIME) - TS_ASSERT_EQUALS(startRange.second.toISO8601String(), - EXPECTED_SECOND_TIME) - TS_ASSERT_EQUALS(secondRange.first.toISO8601String(), - EXPECTED_SECOND_TIME) - TS_ASSERT_EQUALS(secondFromEndRange.second.toISO8601String(), - EXPECTED_SECOND_FROM_END_TIME) - TS_ASSERT_EQUALS(endRange.first.toISO8601String(), - EXPECTED_SECOND_FROM_END_TIME) - TS_ASSERT_EQUALS(endRange.second.toISO8601String(), EXPECTED_END_TIME) - } + TS_ASSERT_EQUALS(detInfo.scanCount(), SCAN_COUNT) + + const auto startRange = detInfo.scanIntervals()[0]; + const auto secondRange = detInfo.scanIntervals()[1]; + const auto secondFromEndRange = + detInfo.scanIntervals()[detInfo.scanCount() - 2]; + const auto endRange = detInfo.scanIntervals()[detInfo.scanCount() - 1]; + TS_ASSERT_EQUALS(startRange.first.toISO8601String(), EXPECTED_START_TIME) + TS_ASSERT_EQUALS(startRange.second.toISO8601String(), EXPECTED_SECOND_TIME) + TS_ASSERT_EQUALS(secondRange.first.toISO8601String(), EXPECTED_SECOND_TIME) + TS_ASSERT_EQUALS(secondFromEndRange.second.toISO8601String(), + EXPECTED_SECOND_FROM_END_TIME) + TS_ASSERT_EQUALS(endRange.first.toISO8601String(), + EXPECTED_SECOND_FROM_END_TIME) + TS_ASSERT_EQUALS(endRange.second.toISO8601String(), EXPECTED_END_TIME) // Check monitor does not move - for (size_t j = 0; j < detInfo.scanCount(0); ++j) { + for (size_t j = 0; j < detInfo.scanCount(); ++j) { TS_ASSERT(detInfo.isMonitor({0, j})) TS_ASSERT_EQUALS(detInfo.position({0, j}), detInfo.position({0, 0})) } @@ -352,7 +346,7 @@ public: const double TUBE_128_FIRST_ANGLE = 147.496; for (size_t i = 0; i < NUMBER_OF_TUBES; ++i) { - for (size_t j = 0; j < detInfo.scanCount(i); ++j) { + for (size_t j = 0; j < detInfo.scanCount(); ++j) { // Find two pixels just above and just below the centre, and take their // average position as the tube centre auto belowCentrePixel = i * NUMBER_OF_PIXELS + NUMBER_OF_PIXELS / 2; diff --git a/Framework/DataHandling/test/LoadSampleShapeTest.h b/Framework/DataHandling/test/LoadSampleShapeTest.h index 1d0d18b7d35c574ab9422063717106f9e97b16ad..fea1ee3b7d2f55c541b8aa64845eb61482194041 100644 --- a/Framework/DataHandling/test/LoadSampleShapeTest.h +++ b/Framework/DataHandling/test/LoadSampleShapeTest.h @@ -52,53 +52,11 @@ public: loadMeshObject(alg, true, "cube.stl"); } - void test_stl_cube() { - LoadSampleShape alg; - auto cube = loadMeshObject(alg, true, "cube.stl"); - TS_ASSERT(cube->hasValidShape()); - TS_ASSERT_EQUALS(cube->numberOfVertices(), 8); - TS_ASSERT_EQUALS(cube->numberOfTriangles(), 12); - TS_ASSERT_DELTA(cube->volume(), 3000, 0.001); - } - - void test_stl_cylinder() { - LoadSampleShape alg; - auto cylinder = loadMeshObject(alg, true, "cylinder.stl"); - TS_ASSERT(cylinder->hasValidShape()); - TS_ASSERT_EQUALS(cylinder->numberOfVertices(), 722); - TS_ASSERT_EQUALS(cylinder->numberOfTriangles(), 1440); - TS_ASSERT_DELTA(cylinder->volume(), 589, 1); - } - - void test_stl_tube() { - LoadSampleShape alg; - auto tube = loadMeshObject(alg, true, "tube.stl"); - TS_ASSERT(tube->hasValidShape()); - TS_ASSERT_EQUALS(tube->numberOfVertices(), 1080); - TS_ASSERT_EQUALS(tube->numberOfTriangles(), 2160); - TS_ASSERT_DELTA(tube->volume(), 7068, 1); - } - void test_fail_invalid_stl_solid() { LoadSampleShape alg; loadFailureTest(alg, "invalid_solid.stl"); } - void test_fail_invalid_stl_keyword() { - LoadSampleShape alg; - loadFailureTest(alg, "invalid_keyword.stl"); - } - - void test_fail_invalid_stl_vertex() { - LoadSampleShape alg; - loadFailureTest(alg, "invalid_vertex.stl"); - } - - void test_fail_invalid_stl_triangle() { - LoadSampleShape alg; - loadFailureTest(alg, "invalid_triangle.stl"); - } - void test_off_cube() { LoadSampleShape alg; auto cube = loadMeshObject(alg, true, "cube.off"); diff --git a/Framework/DataObjects/inc/MantidDataObjects/CoordTransformDistance.h b/Framework/DataObjects/inc/MantidDataObjects/CoordTransformDistance.h index 2f962b6791a92c0ca54dc546648db4f4fd62c54a..1449321ddcd2a64093bbc9661118cff74c7a427b 100644 --- a/Framework/DataObjects/inc/MantidDataObjects/CoordTransformDistance.h +++ b/Framework/DataObjects/inc/MantidDataObjects/CoordTransformDistance.h @@ -12,7 +12,6 @@ #include "MantidGeometry/MDGeometry/IMDDimension.h" #include "MantidKernel/Matrix.h" #include "MantidKernel/System.h" -#include <boost/scoped_ptr.hpp> namespace Mantid { namespace DataObjects { diff --git a/Framework/DataObjects/inc/MantidDataObjects/ScanningWorkspaceBuilder.h b/Framework/DataObjects/inc/MantidDataObjects/ScanningWorkspaceBuilder.h index d4c0c5e34319c26117e9b49ffdb602b4ced5fbf8..e4089a240615337059c382975ed91c2d3c040d90 100644 --- a/Framework/DataObjects/inc/MantidDataObjects/ScanningWorkspaceBuilder.h +++ b/Framework/DataObjects/inc/MantidDataObjects/ScanningWorkspaceBuilder.h @@ -84,7 +84,7 @@ private: IndexingType m_indexingType; void - buildOutputDetectorInfo(Geometry::DetectorInfo &outputDetectorInfo) const; + buildOutputComponentInfo(Geometry::ComponentInfo &outputComponentInfo) const; void buildPositions(Geometry::DetectorInfo &outputDetectorInfo) const; void buildRotations(Geometry::DetectorInfo &outputDetectorInfo) const; diff --git a/Framework/DataObjects/src/ScanningWorkspaceBuilder.cpp b/Framework/DataObjects/src/ScanningWorkspaceBuilder.cpp index 2cb74df91d991945b885747225bc434bb644eab9..84fb1bab9447371ab53e4d4ffb71da195bc89fb9 100644 --- a/Framework/DataObjects/src/ScanningWorkspaceBuilder.cpp +++ b/Framework/DataObjects/src/ScanningWorkspaceBuilder.cpp @@ -9,6 +9,7 @@ #include "MantidDataObjects/Workspace2D.h" #include "MantidDataObjects/WorkspaceCreation.h" #include "MantidGeometry/Instrument.h" +#include "MantidGeometry/Instrument/ComponentInfo.h" #include "MantidGeometry/Instrument/DetectorInfo.h" #include "MantidHistogramData/BinEdges.h" #include "MantidHistogramData/Histogram.h" @@ -199,10 +200,12 @@ MatrixWorkspace_sptr ScanningWorkspaceBuilder::buildWorkspace() const { auto outputWorkspace = create<Workspace2D>( m_instrument, m_nDetectors * m_nTimeIndexes, m_histogram); - auto &outputDetectorInfo = outputWorkspace->mutableDetectorInfo(); - outputDetectorInfo.setScanInterval(m_timeRanges[0]); + auto &outputComponentInfo = outputWorkspace->mutableComponentInfo(); + outputComponentInfo.setScanInterval(m_timeRanges[0]); + + buildOutputComponentInfo(outputComponentInfo); - buildOutputDetectorInfo(outputDetectorInfo); + auto &outputDetectorInfo = outputWorkspace->mutableDetectorInfo(); if (!m_positions.empty()) buildPositions(outputDetectorInfo); @@ -229,14 +232,14 @@ MatrixWorkspace_sptr ScanningWorkspaceBuilder::buildWorkspace() const { return boost::shared_ptr<MatrixWorkspace>(std::move(outputWorkspace)); } -void ScanningWorkspaceBuilder::buildOutputDetectorInfo( - Geometry::DetectorInfo &outputDetectorInfo) const { +void ScanningWorkspaceBuilder::buildOutputComponentInfo( + Geometry::ComponentInfo &outputComponentInfo) const { auto mergeWorkspace = create<Workspace2D>(m_instrument, m_nDetectors, m_histogram.binEdges()); for (size_t i = 1; i < m_nTimeIndexes; ++i) { - auto &mergeDetectorInfo = mergeWorkspace->mutableDetectorInfo(); - mergeDetectorInfo.setScanInterval(m_timeRanges[i]); - outputDetectorInfo.merge(mergeDetectorInfo); + auto &mergeComponentInfo = mergeWorkspace->mutableComponentInfo(); + mergeComponentInfo.setScanInterval(m_timeRanges[i]); + outputComponentInfo.merge(mergeComponentInfo); } } @@ -261,7 +264,7 @@ void ScanningWorkspaceBuilder::buildPositions( void ScanningWorkspaceBuilder::buildRelativeRotationsForScans( Geometry::DetectorInfo &outputDetectorInfo) const { for (size_t i = 0; i < outputDetectorInfo.size(); ++i) { - for (size_t j = 0; j < outputDetectorInfo.scanCount(i); ++j) { + for (size_t j = 0; j < outputDetectorInfo.scanCount(); ++j) { if (outputDetectorInfo.isMonitor({i, j})) continue; auto position = outputDetectorInfo.position({i, j}); diff --git a/Framework/DataObjects/test/MDBoxSaveableTest.h b/Framework/DataObjects/test/MDBoxSaveableTest.h index 8faeb56abc05213c419807c68dfcfcd8e2d44e00..4b54bdd84bee4f5717e2bb55cc6f99afd6d06017 100644 --- a/Framework/DataObjects/test/MDBoxSaveableTest.h +++ b/Framework/DataObjects/test/MDBoxSaveableTest.h @@ -21,7 +21,6 @@ #include "MantidTestHelpers/BoxControllerDummyIO.h" #include "MantidTestHelpers/MDEventsTestHelper.h" #include <Poco/File.h> -#include <boost/scoped_ptr.hpp> #include <cxxtest/TestSuite.h> #include <map> #include <memory> diff --git a/Framework/DataObjects/test/ScanningWorkspaceBuilderTest.h b/Framework/DataObjects/test/ScanningWorkspaceBuilderTest.h index e7ceb5dd536fc6cfcda3bd60a48531e3fac459b1..8c71477a9da1b6ce1f8927338abd272c1de35c8c 100644 --- a/Framework/DataObjects/test/ScanningWorkspaceBuilderTest.h +++ b/Framework/DataObjects/test/ScanningWorkspaceBuilderTest.h @@ -597,10 +597,8 @@ private: } void checkTimeRanges(const DetectorInfo &detectorInfo) { - for (size_t i = 0; i < nDetectors; ++i) { - for (size_t j = 0; j < nTimeIndexes; ++j) { - TS_ASSERT_EQUALS(detectorInfo.scanInterval({i, j}), timeRanges[j]); - } + for (size_t i = 0; i < nTimeIndexes; ++i) { + TS_ASSERT_EQUALS(detectorInfo.scanIntervals()[i], timeRanges[i]); } } }; diff --git a/Framework/Geometry/CMakeLists.txt b/Framework/Geometry/CMakeLists.txt index bb790d8264050377c082ac300fe88bbd1cbfefc6..df23d6fff779ad4bb640631186735bae866f2ee3 100644 --- a/Framework/Geometry/CMakeLists.txt +++ b/Framework/Geometry/CMakeLists.txt @@ -48,6 +48,7 @@ set ( SRC_FILES src/Instrument/Component.cpp src/Instrument/ComponentHelper.cpp src/Instrument/ComponentInfo.cpp + src/Instrument/ComponentInfoBankHelpers.cpp src/Instrument/Container.cpp src/Instrument/Detector.cpp src/Instrument/DetectorGroup.cpp @@ -105,8 +106,9 @@ set ( SRC_FILES src/Objects/BoundingBox.cpp src/Objects/CSGObject.cpp src/Objects/InstrumentRayTracer.cpp - src/Objects/MeshObject2D.cpp + src/Objects/MeshObject2D.cpp src/Objects/MeshObject.cpp + src/Objects/MeshObjectCommon.cpp src/Objects/RuleItems.cpp src/Objects/Rules.cpp src/Objects/ShapeFactory.cpp @@ -195,11 +197,14 @@ set ( INC_FILES inc/MantidGeometry/Instrument/Component.h inc/MantidGeometry/Instrument/ComponentHelper.h inc/MantidGeometry/Instrument/ComponentInfo.h + inc/MantidGeometry/Instrument/ComponentInfoBankHelpers.h inc/MantidGeometry/Instrument/ComponentVisitor.h inc/MantidGeometry/Instrument/Container.h inc/MantidGeometry/Instrument/Detector.h inc/MantidGeometry/Instrument/DetectorGroup.h inc/MantidGeometry/Instrument/DetectorInfo.h + inc/MantidGeometry/Instrument/DetectorInfoItem.h + inc/MantidGeometry/Instrument/DetectorInfoIterator.h inc/MantidGeometry/Instrument/FitParameter.h inc/MantidGeometry/Instrument/Goniometer.h inc/MantidGeometry/Instrument/GridDetector.h @@ -222,8 +227,6 @@ set ( INC_FILES inc/MantidGeometry/Instrument/StructuredDetector.h inc/MantidGeometry/Instrument/XMLInstrumentParameter.h inc/MantidGeometry/Instrument_fwd.h - inc/MantidGeometry/Instrument/DetectorInfoIterator.h - inc/MantidGeometry/Instrument/DetectorInfoItem.h inc/MantidGeometry/MDGeometry/CompositeImplicitFunction.h inc/MantidGeometry/MDGeometry/GeneralFrame.h inc/MantidGeometry/MDGeometry/HKL.h @@ -265,6 +268,7 @@ set ( INC_FILES inc/MantidGeometry/Objects/InstrumentRayTracer.h inc/MantidGeometry/Objects/MeshObject.h inc/MantidGeometry/Objects/MeshObject2D.h + inc/MantidGeometry/Objects/MeshObjectCommon.h inc/MantidGeometry/Objects/Rules.h inc/MantidGeometry/Objects/ShapeFactory.h inc/MantidGeometry/Objects/Track.h @@ -302,6 +306,7 @@ set ( TEST_FILES CSGObjectTest.h CenteringGroupTest.h CompAssemblyTest.h + ComponentInfoBankHelpersTest.h ComponentInfoTest.h ComponentParserTest.h ComponentTest.h @@ -316,6 +321,7 @@ set ( TEST_FILES CyclicGroupTest.h CylinderTest.h DetectorGroupTest.h + DetectorInfoIteratorTest.h DetectorTest.h FitParameterTest.h GeneralFrameTest.h @@ -352,7 +358,8 @@ set ( TEST_FILES MathSupportTest.h MatrixVectorPairParserTest.h MatrixVectorPairTest.h - MeshObject2DTest.h + MeshObject2DTest.h + MeshObjectCommonTest.h MeshObjectTest.h NiggliCellTest.h NullImplicitFunctionTest.h @@ -420,7 +427,6 @@ set ( TEST_FILES UnitCellTest.h V3RTest.h XMLInstrumentParameterTest.h - DetectorInfoIteratorTest.h ) set ( GMOCK_TEST_FILES diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfo.h b/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfo.h index 9e75154bcecb668cd3f6d3e0fe8c090e91cffadf..fcfc5e8c1e21f76be3dc73c7260a286cfe4ca6c4 100644 --- a/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfo.h +++ b/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfo.h @@ -10,6 +10,7 @@ #include "MantidBeamline/ComponentType.h" #include "MantidGeometry/DllConfig.h" #include "MantidGeometry/Objects/BoundingBox.h" +#include "MantidKernel/DateAndTime.h" #include <boost/shared_ptr.hpp> #include <unordered_map> #include <vector> @@ -135,9 +136,10 @@ public: BoundingBox boundingBox(const size_t componentIndex, const BoundingBox *reference = nullptr) const; Beamline::ComponentType componentType(const size_t componentIndex) const; - void setScanInterval(const std::pair<int64_t, int64_t> &interval); + void setScanInterval(const std::pair<Types::Core::DateAndTime, + Types::Core::DateAndTime> &interval); + size_t scanCount() const; void merge(const ComponentInfo &other); - size_t scanSize() const; friend class Instrument; }; diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfoBankHelpers.h b/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfoBankHelpers.h new file mode 100644 index 0000000000000000000000000000000000000000..49f6698bae8d0a227e4003539565c649cd847f89 --- /dev/null +++ b/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfoBankHelpers.h @@ -0,0 +1,19 @@ +#ifndef MANTID_GEOMETRY_COMPONENTINFOBANKHELPERS_H_ +#define MANTID_GEOMETRY_COMPONENTINFOBANKHELPERS_H_ + +#include "MantidGeometry/DllConfig.h" + +namespace Mantid { +namespace Geometry { + +class ComponentInfo; +namespace ComponentInfoBankHelpers { + +MANTID_GEOMETRY_DLL bool isDetectorFixedInBank(const ComponentInfo &compInfo, + const size_t detIndex); +} // namespace ComponentInfoBankHelpers + +} // namespace Geometry +} // namespace Mantid + +#endif /* MANTID_GEOMETRY_COMPONENTINFOBANKHELPERS_H_ */ \ No newline at end of file diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfo.h b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfo.h index 2d85c6fe60d7e5c4012231c6929bef038bdbbdbf..eefcf9fc90b5ee8a3896380a42201a3f1b6f3ae7 100644 --- a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfo.h +++ b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfo.h @@ -13,6 +13,7 @@ #include <vector> #include "MantidGeometry/DllConfig.h" +#include "MantidGeometry/Instrument/DetectorInfoIterator.h" #include "MantidKernel/DateAndTime.h" #include "MantidKernel/Quat.h" #include "MantidKernel/V3D.h" @@ -28,7 +29,6 @@ class SpectrumInfo; namespace Geometry { class IDetector; class Instrument; -class DetectorInfoIterator; /** Geometry::DetectorInfo is an intermediate step towards a DetectorInfo that is part of Instrument-2.0. The aim is to provide a nearly identical interface @@ -63,7 +63,6 @@ public: size_t size() const; size_t scanSize() const; bool isScanning() const; - bool isSyncScan() const; bool isMonitor(const size_t index) const; bool isMonitor(const std::pair<size_t, size_t> &index) const; @@ -104,22 +103,18 @@ public: /// This will throw an out of range exception if the detector does not exist. size_t indexOf(const detid_t id) const { return m_detIDToIndex->at(id); } - size_t scanCount(const size_t index) const; - std::pair<Types::Core::DateAndTime, Types::Core::DateAndTime> - scanInterval(const std::pair<size_t, size_t> &index) const; - void setScanInterval(const size_t index, - const std::pair<Types::Core::DateAndTime, - Types::Core::DateAndTime> &interval); - void setScanInterval(const std::pair<Types::Core::DateAndTime, - Types::Core::DateAndTime> &interval); - - void merge(const DetectorInfo &other); + size_t scanCount() const; + const std::vector< + std::pair<Types::Core::DateAndTime, Types::Core::DateAndTime>> + scanIntervals() const; friend class API::SpectrumInfo; friend class Instrument; - DetectorInfoIterator begin() const; - DetectorInfoIterator end() const; + DetectorInfoIterator<DetectorInfo> begin(); + DetectorInfoIterator<DetectorInfo> end(); + const DetectorInfoIterator<const DetectorInfo> cbegin() const; + const DetectorInfoIterator<const DetectorInfo> cend() const; private: const Geometry::IDetector &getDetector(const size_t index) const; @@ -138,6 +133,9 @@ private: mutable std::vector<size_t> m_lastIndex; }; +using DetectorInfoIt = DetectorInfoIterator<DetectorInfo>; +using DetectorInfoConstIt = DetectorInfoIterator<const DetectorInfo>; + } // namespace Geometry } // namespace Mantid diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoItem.h b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoItem.h index 4b9b2a5212c755c907d0c3025cc833f193b64126..3699f43492230bf8f19cd3c5a948a755f55ce83e 100644 --- a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoItem.h +++ b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoItem.h @@ -7,11 +7,9 @@ #ifndef MANTID_GEOMETRY_DETECTORINFOITEM_H_ #define MANTID_GEOMETRY_DETECTORINFOITEM_H_ -#include "MantidGeometry/Instrument/DetectorInfo.h" #include "MantidKernel/Quat.h" #include "MantidKernel/V3D.h" -using Mantid::Geometry::DetectorInfo; using Mantid::Kernel::Quat; using Mantid::Kernel::V3D; @@ -32,8 +30,7 @@ methods include: @author Bhuvan Bezawada, STFC @date 2018 */ - -class MANTID_GEOMETRY_DLL DetectorInfoItem { +template <typename T> class DetectorInfoItem { public: // Methods that can be accessed via the iterator @@ -41,6 +38,11 @@ public: bool isMasked() const { return m_detectorInfo->isMasked(m_index); } + void setMasked(bool masked) { + static_assert(!std::is_const<T>::value, "Operation disabled on const T"); + return m_detectorInfo->setMasked(m_index, masked); + } + double twoTheta() const { return m_detectorInfo->twoTheta(m_index); } Mantid::Kernel::V3D position() const { @@ -51,17 +53,12 @@ public: return m_detectorInfo->rotation(m_index); } -private: - // Allow DetectorInfoIterator access - friend class DetectorInfoIterator; - - // Private constructor, can only be created by DetectorInfoIterator - DetectorInfoItem(const DetectorInfo &detectorInfo, const size_t index) + DetectorInfoItem(T &detectorInfo, const size_t index) : m_detectorInfo(&detectorInfo), m_index(index) {} // Non-owning pointer. A reference makes the class unable to define an // assignment operator that we need. - const DetectorInfo *m_detectorInfo; + T *m_detectorInfo; size_t m_index; }; diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoIterator.h b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoIterator.h index 938aa15579871bbca1c5c94bda877f24678d8c21..3b40de2d809b94523fa02e271e7fc506ab2ff450 100644 --- a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoIterator.h +++ b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfoIterator.h @@ -8,7 +8,6 @@ #define MANTID_GEOMETRY_DETECTORINFOITERATOR_H_ #include "MantidGeometry/Instrument/DetectorInfoItem.h" - #include <boost/iterator/iterator_facade.hpp> using Mantid::Geometry::DetectorInfoItem; @@ -26,14 +25,14 @@ iterator. @author Bhuvan Bezawada, STFC @date 2018 */ - -class MANTID_GEOMETRY_DLL DetectorInfoIterator - : public boost::iterator_facade<DetectorInfoIterator, - const DetectorInfoItem &, +template <typename T> +class DetectorInfoIterator + : public boost::iterator_facade<DetectorInfoIterator<T>, + DetectorInfoItem<T> &, boost::random_access_traversal_tag> { public: - DetectorInfoIterator(const DetectorInfo &detectorInfo, const size_t index) + DetectorInfoIterator(T &detectorInfo, const size_t index) : m_item(detectorInfo, index) {} private: @@ -69,18 +68,18 @@ private: void setIndex(const size_t index) { m_item.m_index = index; } - bool equal(const DetectorInfoIterator &other) const { + bool equal(const DetectorInfoIterator<T> &other) const { return getIndex() == other.getIndex(); } - const DetectorInfoItem &dereference() const { return m_item; } + DetectorInfoItem<T> &dereference() const { return m_item; } - uint64_t distance_to(const DetectorInfoIterator &other) const { + uint64_t distance_to(const DetectorInfoIterator<T> &other) const { return static_cast<uint64_t>(other.getIndex()) - static_cast<uint64_t>(getIndex()); } - DetectorInfoItem m_item; + mutable DetectorInfoItem<T> m_item; }; } // namespace Geometry diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/ReferenceFrame.h b/Framework/Geometry/inc/MantidGeometry/Instrument/ReferenceFrame.h index 542887a18ef622f974dfaae96218b0295e323503..bedf6f495d0a7c7223a1617462ff686044ed90af 100644 --- a/Framework/Geometry/inc/MantidGeometry/Instrument/ReferenceFrame.h +++ b/Framework/Geometry/inc/MantidGeometry/Instrument/ReferenceFrame.h @@ -49,11 +49,13 @@ public: /// Destructor virtual ~ReferenceFrame() = default; /// Convert up axis into a 3D direction - const Mantid::Kernel::V3D vecPointingUp() const; + Mantid::Kernel::V3D vecPointingUp() const; /// Convert along beam axis into a 3D direction - const Mantid::Kernel::V3D vecPointingAlongBeam() const; + Mantid::Kernel::V3D vecPointingAlongBeam() const; + /// Convert along horizontal axis into a 3D direction + Mantid::Kernel::V3D vecPointingHorizontal() const; /// Convert along the axis defining the 2theta sign - const Mantid::Kernel::V3D vecThetaSign() const; + Mantid::Kernel::V3D vecThetaSign() const; /// Pointing up axis as a string std::string pointingUpAxis() const; /// Pointing along beam axis as a string diff --git a/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject.h b/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject.h index c3d6155311019a7736e6a53cb0417e51810cf4b1..e57513c99a6b416cb0b54d95c7f3918de92c122e 100644 --- a/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject.h +++ b/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject.h @@ -50,11 +50,10 @@ class MANTID_GEOMETRY_DLL MeshObject : public IObject { public: /// Constructor MeshObject(const std::vector<uint16_t> &faces, - const std::vector<Mantid::Kernel::V3D> &vertices, + const std::vector<Kernel::V3D> &vertices, const Kernel::Material &material); /// Constructor - MeshObject(std::vector<uint16_t> &&faces, - std::vector<Mantid::Kernel::V3D> &&vertices, + MeshObject(std::vector<uint16_t> &&faces, std::vector<Kernel::V3D> &&vertices, const Kernel::Material &&material); /// Copy constructor @@ -142,12 +141,7 @@ private: void getIntersections(const Kernel::V3D &start, const Kernel::V3D &direction, std::vector<Kernel::V3D> &intersectionPoints, std::vector<int> &entryExitFlags) const; - /// Determine intersection between ray and an one triangle - bool rayIntersectsTriangle(const Kernel::V3D &start, - const Kernel::V3D &direction, - const Kernel::V3D &v1, const Kernel::V3D &v2, - const Kernel::V3D &v3, Kernel::V3D &intersection, - int &entryExit) const; + /// Get triangle bool getTriangle(const size_t index, Kernel::V3D &v1, Kernel::V3D &v2, Kernel::V3D &v3) const; diff --git a/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject2D.h b/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject2D.h index eb65399c85c561e50aa7cb8bc059519f254bb744..e1374a0994ef078cf2cd89ffd6b57955104d92fc 100644 --- a/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject2D.h +++ b/Framework/Geometry/inc/MantidGeometry/Objects/MeshObject2D.h @@ -32,18 +32,16 @@ class MANTID_GEOMETRY_DLL MeshObject2D : public IObject { public: /// Constructor MeshObject2D(const std::vector<uint16_t> &faces, - const std::vector<Mantid::Kernel::V3D> &vertices, + const std::vector<Kernel::V3D> &vertices, const Kernel::Material &material); /// Constructor MeshObject2D(std::vector<uint16_t> &&faces, - std::vector<Mantid::Kernel::V3D> &&vertices, + std::vector<Kernel::V3D> &&vertices, const Kernel::Material &&material); double volume() const override; - static bool isOnTriangle(const Kernel::V3D &point, const Kernel::V3D &a, - const Kernel::V3D &b, const Kernel::V3D &c); - static bool pointsCoplanar(const std::vector<Mantid::Kernel::V3D> &vertices); + static bool pointsCoplanar(const std::vector<Kernel::V3D> &vertices); bool hasValidShape() const override; double distanceToPlane(const Kernel::V3D &point) const; diff --git a/Framework/Geometry/inc/MantidGeometry/Objects/MeshObjectCommon.h b/Framework/Geometry/inc/MantidGeometry/Objects/MeshObjectCommon.h new file mode 100644 index 0000000000000000000000000000000000000000..f0d61427f5005cd2238f1f61de82f1d933c40544 --- /dev/null +++ b/Framework/Geometry/inc/MantidGeometry/Objects/MeshObjectCommon.h @@ -0,0 +1,67 @@ +#ifndef MANTID_GEOMETRY_MESHOBJECTCOMMON_H_ +#define MANTID_GEOMETRY_MESHOBJECTCOMMON_H_ + +#include "MantidGeometry/DllConfig.h" +#include "MantidKernel/V3D.h" +#include <vector> + +namespace Mantid { +namespace Geometry { +class BoundingBox; +/** MeshObjectCommon : Performs functions common to 3D and 2D closed meshes + + Copyright © 2018 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> +*/ +namespace MeshObjectCommon { + +MANTID_GEOMETRY_DLL std::vector<double> +getVertices(const std::vector<Kernel::V3D> &vertices); + +MANTID_GEOMETRY_DLL bool isOnTriangle(const Kernel::V3D &point, + const Kernel::V3D &v1, + const Kernel::V3D &v2, + const Kernel::V3D &v3); +MANTID_GEOMETRY_DLL bool +rayIntersectsTriangle(const Kernel::V3D &start, const Kernel::V3D &direction, + const Kernel::V3D &v1, const Kernel::V3D &v2, + const Kernel::V3D &v3, Kernel::V3D &intersection, + int &entryExit); + +MANTID_GEOMETRY_DLL void checkVertexLimit(size_t nVertices); +MANTID_GEOMETRY_DLL std::vector<uint32_t> +getTriangles_uint32(const std::vector<uint16_t> &input); +MANTID_GEOMETRY_DLL const BoundingBox & +getBoundingBox(const std::vector<Kernel::V3D> &vertices, BoundingBox &cacheBB); +MANTID_GEOMETRY_DLL void +getBoundingBox(const std::vector<Kernel::V3D> &vertices, BoundingBox &cacheBB, + double &xmax, double &ymax, double &zmax, double &xmin, + double &ymin, double &zmin); +MANTID_GEOMETRY_DLL double getTriangleSolidAngle(const Kernel::V3D &a, + const Kernel::V3D &b, + const Kernel::V3D &c, + const Kernel::V3D &observer); +} // namespace MeshObjectCommon + +} // namespace Geometry +} // namespace Mantid + +#endif /* MANTID_GEOMETRY_MESHOBJECTCOMMON_H_ */ diff --git a/Framework/Geometry/src/Instrument.cpp b/Framework/Geometry/src/Instrument.cpp index 1faa3f771a3e64257657a0bc56bcb9a059a922a8..f180211d3b4b43e945c213a645485a03222371f6 100644 --- a/Framework/Geometry/src/Instrument.cpp +++ b/Framework/Geometry/src/Instrument.cpp @@ -8,6 +8,7 @@ #include "MantidBeamline/ComponentInfo.h" #include "MantidBeamline/DetectorInfo.h" #include "MantidGeometry/Instrument/ComponentInfo.h" +#include "MantidGeometry/Instrument/ComponentInfoBankHelpers.h" #include "MantidGeometry/Instrument/DetectorGroup.h" #include "MantidGeometry/Instrument/DetectorInfo.h" #include "MantidGeometry/Instrument/GridDetectorPixel.h" @@ -1295,7 +1296,7 @@ boost::shared_ptr<ParameterMap> Instrument::makeLegacyParameterMap() const { const int64_t parentIndex = componentInfo.parent(i); const bool makeTransform = parentIndex != oldParentIndex; - bool isGridDetectorPixel = false; + bool isDetFixedInBank = false; if (makeTransform) { oldParentIndex = parentIndex; @@ -1312,15 +1313,15 @@ boost::shared_ptr<ParameterMap> Instrument::makeLegacyParameterMap() const { const boost::shared_ptr<const IDetector> &baseDet = std::get<1>(baseInstr.m_detectorCache[i]); - isGridDetectorPixel = - bool(boost::dynamic_pointer_cast<const GridDetectorPixel>(baseDet)); + isDetFixedInBank = + ComponentInfoBankHelpers::isDetectorFixedInBank(componentInfo, i); if (detectorInfo.isMasked(i)) { pmap->forceUnsafeSetMasked(baseDet.get(), true); } if (makeTransform) { // Special case: scaling for GridDetectorPixel. - if (isGridDetectorPixel) { + if (isDetFixedInBank) { size_t panelIndex = componentInfo.parent(parentIndex); const auto panelID = componentInfo.componentID(panelIndex); @@ -1354,7 +1355,7 @@ boost::shared_ptr<ParameterMap> Instrument::makeLegacyParameterMap() const { // Tolerance 1e-9 m as in Beamline::DetectorInfo::isEquivalent. if ((relPos - toVector3d(baseComponent->getRelativePos())).norm() >= 1e-9) { - if (isGridDetectorPixel) { + if (isDetFixedInBank) { throw std::runtime_error("Cannot create legacy ParameterMap: Position " "parameters for GridDetectorPixel are " "not supported"); @@ -1410,7 +1411,6 @@ Instrument::makeWrappers(ParameterMap &pmap, const ComponentInfo &componentInfo, auto compInfo = componentInfo.cloneWithoutDetectorInfo(); auto detInfo = Kernel::make_unique<DetectorInfo>(detectorInfo); compInfo->m_componentInfo->setDetectorInfo(detInfo->m_detectorInfo.get()); - detInfo->m_detectorInfo->setComponentInfo(compInfo->m_componentInfo.get()); const auto parInstrument = ParComponentFactory::createInstrument( boost::shared_ptr<const Instrument>(this, NoDeleting()), boost::shared_ptr<ParameterMap>(&pmap, NoDeleting())); diff --git a/Framework/Geometry/src/Instrument/ComponentInfo.cpp b/Framework/Geometry/src/Instrument/ComponentInfo.cpp index 71304849ae05a416025aaa34c80b8e3ae6f49e67..d15a38008cd0c609daeadae94a40d2276683bb66 100644 --- a/Framework/Geometry/src/Instrument/ComponentInfo.cpp +++ b/Framework/Geometry/src/Instrument/ComponentInfo.cpp @@ -428,15 +428,17 @@ ComponentInfo::componentType(const size_t componentIndex) const { } void ComponentInfo::setScanInterval( - const std::pair<int64_t, int64_t> &interval) { - m_componentInfo->setScanInterval(interval); + const std::pair<Types::Core::DateAndTime, Types::Core::DateAndTime> + &interval) { + m_componentInfo->setScanInterval( + {interval.first.totalNanoseconds(), interval.second.totalNanoseconds()}); } +size_t ComponentInfo::scanCount() const { return m_componentInfo->scanCount(); } + void ComponentInfo::merge(const ComponentInfo &other) { m_componentInfo->merge(*other.m_componentInfo); } -size_t ComponentInfo::scanSize() const { return m_componentInfo->scanSize(); } - } // namespace Geometry } // namespace Mantid diff --git a/Framework/Geometry/src/Instrument/ComponentInfoBankHelpers.cpp b/Framework/Geometry/src/Instrument/ComponentInfoBankHelpers.cpp new file mode 100644 index 0000000000000000000000000000000000000000..188fb2f06008b533f6f749f551cd28a1c37c2675 --- /dev/null +++ b/Framework/Geometry/src/Instrument/ComponentInfoBankHelpers.cpp @@ -0,0 +1,37 @@ +#include "MantidGeometry/Instrument/ComponentInfoBankHelpers.h" +#include "MantidBeamline/ComponentType.h" +#include "MantidGeometry/Instrument/ComponentInfo.h" + +using Mantid::Beamline::ComponentType; + +namespace Mantid { +namespace Geometry { +namespace ComponentInfoBankHelpers { +/** Tests whether or not the detector is within a fixed bank. If detIndex does +not point to a detector, this will return false. This method only returns true +if the bank which houses the detector is rectangular, a grid or structured. +@param compInfo ComponentInfo which defines instrument tree for testing +@param detIndex Index of the detector to be tested +@returns True if the detector is fixed in a bank, False otherwise. +*/ +bool isDetectorFixedInBank(const ComponentInfo &compInfo, + const size_t detIndex) { + auto parent = compInfo.parent(detIndex); + auto grandParent = compInfo.parent(parent); + auto grandParentType = compInfo.componentType(grandParent); + auto greatGrandParent = compInfo.parent(parent); // bank + auto greatGrandParentType = compInfo.componentType(greatGrandParent); + + if (compInfo.isDetector(detIndex) && + (grandParentType == ComponentType::Rectangular || + grandParentType == ComponentType::Structured || + greatGrandParentType == ComponentType::Grid)) { + return true; + } + + return false; +} + +} // namespace ComponentInfoBankHelpers +} // namespace Geometry +} // namespace Mantid diff --git a/Framework/Geometry/src/Instrument/DetectorInfo.cpp b/Framework/Geometry/src/Instrument/DetectorInfo.cpp index cdcec8686ff8fe7fce7c1da723dcdcf4c097971c..5f8d6656a1816f61b8f526c02ff6807c87e8a67a 100644 --- a/Framework/Geometry/src/Instrument/DetectorInfo.cpp +++ b/Framework/Geometry/src/Instrument/DetectorInfo.cpp @@ -89,17 +89,9 @@ bool DetectorInfo::isEquivalent(const DetectorInfo &other) const { /// instrument. size_t DetectorInfo::size() const { return m_detectorIDs->size(); } -/// Returns the size of DetectorInfo taking into account scanning, i.e., the sum -/// of the number of scan points for every detector in the instrument. -size_t DetectorInfo::scanSize() const { return m_detectorInfo->scanSize(); } - /// Returns true if the beamline has scanning detectors. bool DetectorInfo::isScanning() const { return m_detectorInfo->isScanning(); } -/// Returns true if the beamline has scanning detectors and they have all the -/// same scan intervals. -bool DetectorInfo::isSyncScan() const { return m_detectorInfo->isSyncScan(); } - /// Returns true if the detector is a monitor. bool DetectorInfo::isMonitor(const size_t index) const { return m_detectorInfo->isMonitor(index); @@ -327,54 +319,24 @@ const std::vector<detid_t> &DetectorInfo::detectorIDs() const { } /// Returns the scan count of the detector with given detector index. -size_t DetectorInfo::scanCount(const size_t index) const { - return m_detectorInfo->scanCount(index); -} +size_t DetectorInfo::scanCount() const { return m_detectorInfo->scanCount(); } /** Returns the scan interval of the detector with given index. * * The interval start and end values would typically correspond to nanoseconds * since 1990, as in Types::Core::DateAndTime. */ -std::pair<Types::Core::DateAndTime, Types::Core::DateAndTime> -DetectorInfo::scanInterval(const std::pair<size_t, size_t> &index) const { - const auto &interval = m_detectorInfo->scanInterval(index); - return {interval.first, interval.second}; +const std::vector<std::pair<Types::Core::DateAndTime, Types::Core::DateAndTime>> +DetectorInfo::scanIntervals() const { + const auto &intervals = m_detectorInfo->scanIntervals(); + return {intervals.begin(), intervals.end()}; } -/** Set the scan interval of the detector with given detector index. - * - * The interval start and end values would typically correspond to nanoseconds - * since 1990, as in Types::Core::DateAndTime. Note that it is currently not - *possible - * to modify scan intervals for a DetectorInfo with time-dependent detectors, - * i.e., time intervals must be set with this method before merging individual - * scans. */ -void DetectorInfo::setScanInterval( - const size_t index, - const std::pair<Types::Core::DateAndTime, Types::Core::DateAndTime> - &interval) { - m_detectorInfo->setScanInterval(index, {interval.first.totalNanoseconds(), - interval.second.totalNanoseconds()}); -} - -/** Set the scan interval for all detectors. - * - * Prefer this over setting intervals for individual detectors since it enables - * internal performance optimization. See also overload for other details. */ -void DetectorInfo::setScanInterval( - const std::pair<Types::Core::DateAndTime, Types::Core::DateAndTime> - &interval) { - m_detectorInfo->setScanInterval( - {interval.first.totalNanoseconds(), interval.second.totalNanoseconds()}); +const DetectorInfoConstIt DetectorInfo::cbegin() const { + return DetectorInfoConstIt(*this, 0); } -/** Merges the contents of other into this. - * - * Scan intervals in both other and this must be set. Intervals must be - * identical or non-overlapping. If they are identical all other parameters (for - * that index) must match. */ -void DetectorInfo::merge(const DetectorInfo &other) { - m_detectorInfo->merge(*other.m_detectorInfo); +const DetectorInfoConstIt DetectorInfo::cend() const { + return DetectorInfoConstIt(*this, size()); } const Geometry::IDetector &DetectorInfo::getDetector(const size_t index) const { @@ -396,14 +358,10 @@ DetectorInfo::getDetectorPtr(const size_t index) const { } // Begin method for iterator -DetectorInfoIterator DetectorInfo::begin() const { - return DetectorInfoIterator(*this, 0); -} +DetectorInfoIt DetectorInfo::begin() { return DetectorInfoIt(*this, 0); } // End method for iterator -DetectorInfoIterator DetectorInfo::end() const { - return DetectorInfoIterator(*this, size()); -} +DetectorInfoIt DetectorInfo::end() { return DetectorInfoIt(*this, size()); } } // namespace Geometry } // namespace Mantid diff --git a/Framework/Geometry/src/Instrument/InstrumentVisitor.cpp b/Framework/Geometry/src/Instrument/InstrumentVisitor.cpp index 09726eb4c2a9d2c107172e8a4a5492c5e34e2ff4..48e671277707a06d4f3eac28b4a96693bfbb6345 100644 --- a/Framework/Geometry/src/Instrument/InstrumentVisitor.cpp +++ b/Framework/Geometry/src/Instrument/InstrumentVisitor.cpp @@ -433,7 +433,6 @@ InstrumentVisitor::makeWrappers() const { auto detInfo = detectorInfo(); // Cross link Component and Detector info objects compInfo->setDetectorInfo(detInfo.get()); - detInfo->setComponentInfo(compInfo.get()); auto compInfoWrapper = Kernel::make_unique<ComponentInfo>( std::move(compInfo), componentIds(), componentIdToIndexMap(), m_shapes); diff --git a/Framework/Geometry/src/Instrument/ReferenceFrame.cpp b/Framework/Geometry/src/Instrument/ReferenceFrame.cpp index 2be53f87bb73b595d9f9d4cf90f2298337f51677..ba3355c71b1f58345cb2433dd1e8ba841de97d9f 100644 --- a/Framework/Geometry/src/Instrument/ReferenceFrame.cpp +++ b/Framework/Geometry/src/Instrument/ReferenceFrame.cpp @@ -163,22 +163,30 @@ std::string ReferenceFrame::origin() const { return m_origin; } Getter for the up instrument direction @return up direction. */ -const V3D ReferenceFrame::vecPointingUp() const { return m_vecPointingUp; } +V3D ReferenceFrame::vecPointingUp() const { return m_vecPointingUp; } /** Getter for the direction defining the theta sign @return theta sign direction. */ -const V3D ReferenceFrame::vecThetaSign() const { return m_vecThetaSign; } +V3D ReferenceFrame::vecThetaSign() const { return m_vecThetaSign; } /** Getter for the along beam vector. @return along beam direction. */ -const V3D ReferenceFrame::vecPointingAlongBeam() const { +V3D ReferenceFrame::vecPointingAlongBeam() const { return m_vecPointingAlongBeam; } +/** +Calculate the horizontal vector. +@return horizontal direction. +*/ +V3D ReferenceFrame::vecPointingHorizontal() const { + return directionToVector(pointingHorizontal()); +} + /** Convenience method for checking whether or not a vector is aligned with the along beam vector. diff --git a/Framework/Geometry/src/Instrument/XMLInstrumentParameter.cpp b/Framework/Geometry/src/Instrument/XMLInstrumentParameter.cpp index 0d1ae96f3f109e10510887ab75ce21b23dca6603..eede81254bde8c70189274b5cd573c684c135a4f 100644 --- a/Framework/Geometry/src/Instrument/XMLInstrumentParameter.cpp +++ b/Framework/Geometry/src/Instrument/XMLInstrumentParameter.cpp @@ -68,7 +68,7 @@ XMLInstrumentParameter::XMLInstrumentParameter( m_extractSingleValueAs(extractSingleValueAs), m_eq(eq), m_component(comp), m_angleConvertConst(angleConvertConst), m_description("") { if (!description.empty()) { // remove multiple spaces - boost::regex re("\\s+"); + static const boost::regex re("\\s+"); std::string desc = boost::regex_replace(description, re, " "); (const_cast<std::string *>(&m_description))->assign(desc); } diff --git a/Framework/Geometry/src/Objects/CSGObject.cpp b/Framework/Geometry/src/Objects/CSGObject.cpp index 825e09618cec589dcdfe4f1827650a1d5c906789..105260c456d0c47980338f32d09e8d4953f0b93f 100644 --- a/Framework/Geometry/src/Objects/CSGObject.cpp +++ b/Framework/Geometry/src/Objects/CSGObject.cpp @@ -140,7 +140,8 @@ bool CSGObject::hasValidShape() const { */ int CSGObject::setObject(const int ON, const std::string &Ln) { // Split line - const boost::regex letters("[a-zA-Z]"); // Does the string now contain junk... + // Does the string now contain junk... + static const boost::regex letters("[a-zA-Z]"); if (Mantid::Kernel::Strings::StrLook(Ln, letters)) return 0; @@ -1057,6 +1058,18 @@ double CSGObject::triangleSolidAngle(const V3D &observer) const { sneg += sa; } } + /* We assume that objects are opaque to neutrons and that objects define + * closed surfaces which are convex. For such objects negative solid angle + * equals positive solid angle. This is true providing that the winding + * order is defined properly such that the contribution from each triangle + * w.r.t the observer gets counted to either the negative or positive + * contribution correctly. If that is done correctly then it would only be + * necessary to consider the positive contribution to the solid angle. + * + * The following provides a fix to situations where the winding order is + * incorrectly defined. It does not matter if the contribution is positive + * or negative since we take the average. + */ return 0.5 * (sangle - sneg); } } diff --git a/Framework/Geometry/src/Objects/MeshObject.cpp b/Framework/Geometry/src/Objects/MeshObject.cpp index 75ca88855f25c71396339079bd5166452c7d6912..138d52abb8586dce88200038361f645e188ed342 100644 --- a/Framework/Geometry/src/Objects/MeshObject.cpp +++ b/Framework/Geometry/src/Objects/MeshObject.cpp @@ -5,6 +5,7 @@ // & Institut Laue - Langevin // SPDX - License - Identifier: GPL - 3.0 + #include "MantidGeometry/Objects/MeshObject.h" +#include "MantidGeometry/Objects/MeshObjectCommon.h" #include "MantidGeometry/Objects/Track.h" #include "MantidGeometry/Rendering/GeometryHandler.h" #include "MantidGeometry/Rendering/vtkGeometryCacheReader.h" @@ -19,11 +20,8 @@ namespace Mantid { namespace Geometry { -using Kernel::Material; -using Kernel::V3D; - MeshObject::MeshObject(const std::vector<uint16_t> &faces, - const std::vector<V3D> &vertices, + const std::vector<Kernel::V3D> &vertices, const Kernel::Material &material) : m_boundingBox(), m_id("MeshObject"), m_triangles(faces), m_vertices(vertices), m_material(material) { @@ -32,7 +30,7 @@ MeshObject::MeshObject(const std::vector<uint16_t> &faces, } MeshObject::MeshObject(std::vector<uint16_t> &&faces, - std::vector<V3D> &&vertices, + std::vector<Kernel::V3D> &&vertices, const Kernel::Material &&material) : m_boundingBox(), m_id("MeshObject"), m_triangles(std::move(faces)), m_vertices(std::move(vertices)), m_material(material) { @@ -43,11 +41,7 @@ MeshObject::MeshObject(std::vector<uint16_t> &&faces, // Do things that need to be done in constructor void MeshObject::initialize() { - if (m_vertices.size() > std::numeric_limits<uint16_t>::max()) { - throw std::invalid_argument( - "Too many vertices (" + std::to_string(m_vertices.size()) + - "). MeshObject cannot have more than 65535 vertices."); - } + MeshObjectCommon::checkVertexLimit(m_vertices.size()); m_handler = boost::make_shared<GeometryHandler>(*this); } @@ -79,8 +73,8 @@ bool MeshObject::isValid(const Kernel::V3D &point) const { return false; } - V3D direction(0.0, 0.0, 1.0); // direction to look for intersections - std::vector<V3D> intersectionPoints; + Kernel::V3D direction(0.0, 0.0, 1.0); // direction to look for intersections + std::vector<Kernel::V3D> intersectionPoints; std::vector<int> entryExitFlags; getIntersections(point, direction, intersectionPoints, entryExitFlags); @@ -120,13 +114,13 @@ bool MeshObject::isOnSide(const Kernel::V3D &point) const { return false; } - const std::vector<V3D> directions = { - V3D{0, 0, 1}, V3D{0, 1, 0}, - V3D{1, 0, 0}}; // directions to look for intersections + const std::vector<Kernel::V3D> directions = { + Kernel::V3D{0, 0, 1}, Kernel::V3D{0, 1, 0}, + Kernel::V3D{1, 0, 0}}; // directions to look for intersections // We have to look in several directions in case a point is on a face // or edge parallel to the first direction or also the second direction. for (const auto &direction : directions) { - std::vector<V3D> intersectionPoints; + std::vector<Kernel::V3D> intersectionPoints; std::vector<int> entryExitFlags; getIntersections(point, direction, intersectionPoints, entryExitFlags); @@ -157,7 +151,7 @@ int MeshObject::interceptSurface(Geometry::Track &UT) const { return 0; } - std::vector<V3D> intersectionPoints; + std::vector<Kernel::V3D> intersectionPoints; std::vector<int> entryExitFlags; getIntersections(UT.startPoint(), UT.direction(), intersectionPoints, @@ -165,6 +159,7 @@ int MeshObject::interceptSurface(Geometry::Track &UT) const { if (intersectionPoints.empty()) return 0; // Quit if no intersections found + // For a 3D mesh, a ray may intersect several segments for (size_t i = 0; i < intersectionPoints.size(); ++i) { UT.addPoint(entryExitFlags[i], intersectionPoints[i], *this); } @@ -185,11 +180,12 @@ void MeshObject::getIntersections(const Kernel::V3D &start, std::vector<Kernel::V3D> &intersectionPoints, std::vector<int> &entryExitFlags) const { - V3D vertex1, vertex2, vertex3, intersection; + Kernel::V3D vertex1, vertex2, vertex3, intersection; int entryExit; for (size_t i = 0; getTriangle(i, vertex1, vertex2, vertex3); ++i) { - if (rayIntersectsTriangle(start, direction, vertex1, vertex2, vertex3, - intersection, entryExit)) { + if (MeshObjectCommon::rayIntersectsTriangle(start, direction, vertex1, + vertex2, vertex3, intersection, + entryExit)) { intersectionPoints.push_back(intersection); entryExitFlags.push_back(entryExit); } @@ -197,63 +193,6 @@ void MeshObject::getIntersections(const Kernel::V3D &start, // still need to deal with edge cases } -/** - * Get intersection points and their in out directions on the given ray - * @param start :: Start point of ray - * @param direction :: Direction of ray - * @param v1 :: First vertex of triangle - * @param v2 :: Second vertex of triangle - * @param v3 :: Third vertex of triangle - * @param intersection :: Intersection point - * @param entryExit :: 1 if intersection is entry, -1 if exit - * @returns true if there is an intersection - */ -bool MeshObject::rayIntersectsTriangle(const Kernel::V3D &start, - const Kernel::V3D &direction, - const V3D &v1, const V3D &v2, - const V3D &v3, V3D &intersection, - int &entryExit) const { - // Implements Möller–Trumbore intersection algorithm - V3D edge1, edge2, h, s, q; - double a, f, u, v; - edge1 = v2 - v1; - edge2 = v3 - v1; - h = direction.cross_prod(edge2); - a = edge1.scalar_prod(h); - - const double EPSILON = 0.0000001 * edge1.norm(); - if (a > -EPSILON && a < EPSILON) - return false; // Ray in or parallel to plane of triangle - f = 1 / a; - s = start - v1; - u = f * (s.scalar_prod(h)); - if (u < 0.0 || u > 1.0) - return false; // Intersection with plane outside triangle - q = s.cross_prod(edge1); - v = f * direction.scalar_prod(q); - if (v < 0.0 || u + v > 1.0) - return false; // Intersection with plane outside triangle - - // At this stage we can compute t to find out where the intersection point is - // on the line. - double t = f * edge2.scalar_prod(q); - if (t >= -EPSILON) // ray intersection - { - intersection = start + direction * t; - - // determine entry exit assuming anticlockwise triangle view from outside - V3D normalDirection = edge1.cross_prod(edge2); - if (normalDirection.scalar_prod(direction) > 0.0) { - entryExit = -1; // exit - } else { - entryExit = 1; // entry - } - return true; - } - // Here the intersection occurs on the line of the ray behind the ray. - return false; -} - /* * Get a triangle - useful for iterating over triangles * @param index :: Index of triangle in MeshObject @@ -262,8 +201,8 @@ bool MeshObject::rayIntersectsTriangle(const Kernel::V3D &start, * @param v3 :: Third vertex of triangle * @returns true if the specified triangle exists */ -bool MeshObject::getTriangle(const size_t index, V3D &vertex1, V3D &vertex2, - V3D &vertex3) const { +bool MeshObject::getTriangle(const size_t index, Kernel::V3D &vertex1, + Kernel::V3D &vertex2, Kernel::V3D &vertex3) const { bool triangleExists = index < m_triangles.size() / 3; if (triangleExists) { vertex1 = m_vertices[m_triangles[3 * index]]; @@ -273,41 +212,6 @@ bool MeshObject::getTriangle(const size_t index, V3D &vertex1, V3D &vertex2, return triangleExists; } -/** - * Find the solid angle of a triangle defined by vectors a,b,c from point - *"observer" - * - * formula (Oosterom) O=2atan([a,b,c]/(abc+(a.b)c+(a.c)b+(b.c)a)) - * - * @param a :: first point of triangle - * @param b :: second point of triangle - * @param c :: third point of triangle - * @param observer :: point from which solid angle is required - * @return :: solid angle of triangle in Steradians. - * - * This duplicates code in CSGOjbect both need a place to be merged. - * To aid this, this function has been defined as a non-member. - */ -double getTriangleSolidAngle(const V3D &a, const V3D &b, const V3D &c, - const V3D &observer) { - const V3D ao = a - observer; - const V3D bo = b - observer; - const V3D co = c - observer; - const double modao = ao.norm(); - const double modbo = bo.norm(); - const double modco = co.norm(); - const double aobo = ao.scalar_prod(bo); - const double aoco = ao.scalar_prod(co); - const double boco = bo.scalar_prod(co); - const double scalTripProd = ao.scalar_prod(bo.cross_prod(co)); - const double denom = - modao * modbo * modco + modco * aobo + modbo * aoco + modao * boco; - if (denom != 0.0) - return 2.0 * atan2(scalTripProd, denom); - else - return 0.0; // not certain this is correct -} - /** * Calculate if a point PT is a valid point on the track * @param point :: Point to calculate from. @@ -343,13 +247,8 @@ int MeshObject::calcValidType(const Kernel::V3D &point, void MeshObject::getBoundingBox(double &xmax, double &ymax, double &zmax, double &xmin, double &ymin, double &zmin) const { - BoundingBox bb = getBoundingBox(); - xmin = bb.xMin(); - xmax = bb.xMax(); - ymin = bb.yMin(); - ymax = bb.yMax(); - zmin = bb.zMin(); - zmax = bb.zMax(); + return MeshObjectCommon::getBoundingBox(m_vertices, m_boundingBox, xmax, ymax, + zmax, xmin, ymin, zmin); } /** @@ -358,17 +257,24 @@ void MeshObject::getBoundingBox(double &xmax, double &ymax, double &zmax, * @return :: estimate of solid angle of object. */ double MeshObject::solidAngle(const Kernel::V3D &observer) const { - double solidAngleSum(0), solidAngleNegativeSum(0); - V3D vertex1, vertex2, vertex3; - for (size_t i = 0; getTriangle(i, vertex1, vertex2, vertex3); ++i) { - double sa = getTriangleSolidAngle(vertex1, vertex2, vertex3, observer); + Kernel::V3D vertex1, vertex2, vertex3; + for (size_t i = 0; this->getTriangle(i, vertex1, vertex2, vertex3); ++i) { + double sa = MeshObjectCommon::getTriangleSolidAngle(vertex1, vertex2, + vertex3, observer); if (sa > 0.0) { solidAngleSum += sa; } else { solidAngleNegativeSum += sa; } } + /* + Same implementation as CSGObject. Assumes a convex closed mesh with + solidAngleSum == -solidAngleNegativeSum + + Average is used to bypass issues with winding order. Surface normal + affects magnitude of solid angle. See CSGObject. + */ return 0.5 * (solidAngleSum - solidAngleNegativeSum); } @@ -376,22 +282,20 @@ double MeshObject::solidAngle(const Kernel::V3D &observer) const { * Find solid angle of object wrt the observer with a scaleFactor for the * object. * @param observer :: point to measure solid angle from - * @param scaleFactor :: V3D giving scaling of the object + * @param scaleFactor :: Kernel::V3D giving scaling of the object * @return :: estimate of solid angle of object. */ double MeshObject::solidAngle(const Kernel::V3D &observer, const Kernel::V3D &scaleFactor) const { - std::vector<V3D> scaledVertices; + std::vector<Kernel::V3D> scaledVertices; scaledVertices.reserve(m_vertices.size()); for (const auto &vertex : m_vertices) { - scaledVertices.emplace_back(scaleFactor.X() * vertex.X(), - scaleFactor.Y() * vertex.Y(), - scaleFactor.Z() * vertex.Z()); + scaledVertices.emplace_back(scaleFactor * vertex); } - MeshObject scaledObject(m_triangles, scaledVertices, m_material); - return scaledObject.solidAngle(observer); + MeshObject meshScaled(m_triangles, scaledVertices, m_material); + return meshScaled.solidAngle(observer); } /** @@ -408,15 +312,15 @@ double MeshObject::volume() const { double cX = 0.5 * (bb.xMax() + bb.xMin()); double cY = 0.5 * (bb.yMax() + bb.yMin()); double cZ = 0.5 * (bb.zMax() + bb.zMin()); - V3D centre(cX, cY, cZ); + Kernel::V3D centre(cX, cY, cZ); double volumeTimesSix(0.0); - V3D vertex1, vertex2, vertex3; + Kernel::V3D vertex1, vertex2, vertex3; for (size_t i = 0; getTriangle(i, vertex1, vertex2, vertex3); ++i) { - V3D a = vertex1 - centre; - V3D b = vertex2 - centre; - V3D c = vertex3 - centre; + Kernel::V3D a = vertex1 - centre; + Kernel::V3D b = vertex2 - centre; + Kernel::V3D c = vertex3 - centre; volumeTimesSix += a.scalar_prod(b.cross_prod(c)); } @@ -428,35 +332,7 @@ double MeshObject::volume() const { * @returns A reference to a bounding box for this shape. */ const BoundingBox &MeshObject::getBoundingBox() const { - - if (m_boundingBox.isNull()) - // As the shape of MeshObject is immutable, we need only calculate - // bounding box, if the cached bounding box is null. - if (numberOfVertices() > 0) { - // Initial extents to be overwritten by loop - constexpr double huge = 1e10; - double minX, maxX, minY, maxY, minZ, maxZ; - minX = minY = minZ = huge; - maxX = maxY = maxZ = -huge; - - // Loop over all vertices and determine minima and maxima on each axis - for (const auto &vertex : m_vertices) { - auto vx = vertex.X(); - auto vy = vertex.Y(); - auto vz = vertex.Z(); - - minX = std::min(minX, vx); - maxX = std::max(maxX, vx); - minY = std::min(minY, vy); - maxY = std::max(maxY, vy); - minZ = std::min(minZ, vz); - maxZ = std::max(maxZ, vz); - } - // Cache bounding box, so we do not need to repeat calculation - m_boundingBox = BoundingBox(maxX, maxY, maxZ, minX, minY, minZ); - } - - return m_boundingBox; + return MeshObjectCommon::getBoundingBox(m_vertices, m_boundingBox); } /** @@ -489,8 +365,9 @@ int MeshObject::getPointInObject(Kernel::V3D &point) const { * @param maxAttempts The maximum number of attempts at generating a point * @return The generated point */ -V3D MeshObject::generatePointInObject(Kernel::PseudoRandomNumberGenerator &rng, - const size_t maxAttempts) const { +Kernel::V3D +MeshObject::generatePointInObject(Kernel::PseudoRandomNumberGenerator &rng, + const size_t maxAttempts) const { const auto &bbox = getBoundingBox(); if (bbox.isNull()) { throw std::runtime_error("Object::generatePointInObject() - Invalid " @@ -509,9 +386,10 @@ V3D MeshObject::generatePointInObject(Kernel::PseudoRandomNumberGenerator &rng, * @param maxAttempts The maximum number of attempts at generating a point * @return The newly generated point */ -V3D MeshObject::generatePointInObject(Kernel::PseudoRandomNumberGenerator &rng, - const BoundingBox &activeRegion, - const size_t maxAttempts) const { +Kernel::V3D +MeshObject::generatePointInObject(Kernel::PseudoRandomNumberGenerator &rng, + const BoundingBox &activeRegion, + const size_t maxAttempts) const { size_t attempts(0); while (attempts < maxAttempts) { const double r1 = rng.nextValue(); @@ -540,9 +418,9 @@ bool MeshObject::searchForObject(Kernel::V3D &point) const { // if (isValid(point)) return true; - for (const auto &dir : - {V3D(1., 0., 0.), V3D(-1., 0., 0.), V3D(0., 1., 0.), V3D(0., -1., 0.), - V3D(0., 0., 1.), V3D(0., 0., -1.)}) { + for (const auto &dir : {Kernel::V3D(1., 0., 0.), Kernel::V3D(-1., 0., 0.), + Kernel::V3D(0., 1., 0.), Kernel::V3D(0., -1., 0.), + Kernel::V3D(0., 0., 1.), Kernel::V3D(0., 0., -1.)}) { Geometry::Track tr(point, dir); if (this->interceptSurface(tr) > 0) { point = tr.cbegin()->entryPoint; @@ -611,13 +489,7 @@ size_t MeshObject::numberOfTriangles() const { return m_triangles.size() / 3; } * get faces */ std::vector<uint32_t> MeshObject::getTriangles() const { - std::vector<uint32_t> faces; - size_t nFaceCorners = m_triangles.size(); - faces.resize(static_cast<std::size_t>(nFaceCorners)); - for (size_t i = 0; i < nFaceCorners; ++i) { - faces[i] = static_cast<int>(m_triangles[i]); - } - return faces; + return MeshObjectCommon::getTriangles_uint32(m_triangles); } /** @@ -631,18 +503,7 @@ size_t MeshObject::numberOfVertices() const { * get vertices */ std::vector<double> MeshObject::getVertices() const { - std::vector<double> points; - size_t nPoints = m_vertices.size(); - if (nPoints > 0) { - points.resize(static_cast<std::size_t>(nPoints) * 3); - for (size_t i = 0; i < nPoints; ++i) { - const auto &pnt = m_vertices[i]; - points[i * 3 + 0] = pnt.X(); - points[i * 3 + 1] = pnt.Y(); - points[i * 3 + 2] = pnt.Z(); - } - } - return points; + return MeshObjectCommon::getVertices(m_vertices); } /** diff --git a/Framework/Geometry/src/Objects/MeshObject2D.cpp b/Framework/Geometry/src/Objects/MeshObject2D.cpp index b3c41fadbc5722f2acd0d24a63a148a9c0213512..b0d255cd02812758c6388b53df919bfdd14fafc1 100644 --- a/Framework/Geometry/src/Objects/MeshObject2D.cpp +++ b/Framework/Geometry/src/Objects/MeshObject2D.cpp @@ -6,6 +6,7 @@ // SPDX - License - Identifier: GPL - 3.0 + #include "MantidGeometry/Objects/MeshObject2D.h" #include "MantidGeometry/Objects/IObject.h" +#include "MantidGeometry/Objects/MeshObjectCommon.h" #include "MantidGeometry/Objects/Track.h" #include "MantidGeometry/Rendering/GeometryHandler.h" #include "MantidKernel/Material.h" @@ -18,12 +19,12 @@ namespace Mantid { namespace Geometry { namespace CoplanarChecks { -bool sufficientPoints(const std::vector<Mantid::Kernel::V3D> &vertices) { +bool sufficientPoints(const std::vector<Kernel::V3D> &vertices) { return vertices.size() >= 3; // Not a plane with < 3 points } /** - * Establish the first surface normal. Tries to establish normal fron + * Establish the first surface normal. Tries to establish normal from * non-colinear points * @param vertices : All vertices * @return surface normal, or 0,0,0 if not found @@ -79,8 +80,7 @@ bool allCoplanar(const std::vector<Kernel::V3D> &vertices, * @param vertices : all vertices to consider * @return : normal to surface formed by points */ -Kernel::V3D -validatePointsCoplanar(const std::vector<Mantid::Kernel::V3D> &vertices) { +Kernel::V3D validatePointsCoplanar(const std::vector<Kernel::V3D> &vertices) { if (!sufficientPoints(vertices)) throw std::invalid_argument("Insufficient vertices to create a plane"); @@ -97,100 +97,43 @@ validatePointsCoplanar(const std::vector<Mantid::Kernel::V3D> &vertices) { return normal; } } // namespace CoplanarChecks - namespace { -using Mantid::Kernel::V3D; - /** - * Find the solid angle of a triangle defined by vectors a,b,c from point - *"observer" - * - * formula (Oosterom) O=2atan([a,b,c]/(abc+(a.b)c+(a.c)b+(b.c)a)) - * - * @param a :: first point of triangle - * @param b :: second point of triangle - * @param c :: third point of triangle - * @param observer :: point from which solid angle is required - * @return :: solid angle of triangle in Steradians. - * - * This duplicates code in CSGOjbect both need a place to be merged. - * To aid this, this function has been defined as a non-member. + * Get a triangle - For iterating over triangles + * @param index :: Index of triangle in MeshObject + * @param triangles :: indices into vertices 3 consecutive form triangle + * @param vertices :: Vertices to lookup + * @param vertex1 :: First vertex of triangle + * @param vertex2 :: Second vertex of triangle + * @param vertex3 :: Third vertex of triangle + * @returns true if the specified triangle exists */ -double getTriangleSolidAngle(const V3D &a, const V3D &b, const V3D &c, - const V3D &observer) { - const V3D ao = a - observer; - const V3D bo = b - observer; - const V3D co = c - observer; - const double modao = ao.norm(); - const double modbo = bo.norm(); - const double modco = co.norm(); - const double aobo = ao.scalar_prod(bo); - const double aoco = ao.scalar_prod(co); - const double boco = bo.scalar_prod(co); - const double scalTripProd = ao.scalar_prod(bo.cross_prod(co)); - const double denom = - modao * modbo * modco + modco * aobo + modbo * aoco + modao * boco; - if (denom != 0.0) - return 2.0 * atan2(scalTripProd, denom); - else - return 0.0; // not certain this is correct +bool getTriangle(const size_t index, const std::vector<uint16_t> &triangles, + const std::vector<Kernel::V3D> &vertices, Kernel::V3D &vertex1, + Kernel::V3D &vertex2, Kernel::V3D &vertex3) { + bool triangleExists = index < triangles.size() / 3; + if (triangleExists) { + vertex1 = vertices[triangles[3 * index]]; + vertex2 = vertices[triangles[3 * index + 1]]; + vertex3 = vertices[triangles[3 * index + 2]]; + } + return triangleExists; } } // namespace const double MeshObject2D::MinThickness = 0.001; const std::string MeshObject2D::Id = "MeshObject2D"; -/** - * @brief isOnTriangle - * @param point : point to test - * @param a : first vertex of triangle - * @param b : second vertex of triangle - * @param c : thrid vertex of triangle - * @return True only point if is on triangle - */ -bool MeshObject2D::isOnTriangle(const Kernel::V3D &point, const Kernel::V3D &a, - const Kernel::V3D &b, const Kernel::V3D &c) { - - // in change of basis, barycentric coordinates p = A + u*v0 + v*v1. v0 and - // v1 are basis vectors - // rewrite as v0 = u*v0 + v*v1 - // i) v0.v0 = u*v0.v0 + v*v1.v0 - // ii) v0.v1 = u*v0.v1 + v*v1.v1 - // solve for u, v and check u and v >= 0 and u+v <=1 - - // TODO see MeshObject::rayIntersectsTriangle and compare! - - auto v0 = c - a; - auto v1 = b - a; - auto v2 = point - a; - - // Compute dot products - auto dot00 = v0.scalar_prod(v0); - auto dot01 = v0.scalar_prod(v1); - auto dot02 = v0.scalar_prod(v2); - auto dot11 = v1.scalar_prod(v1); - auto dot12 = v1.scalar_prod(v2); - - // Compute barycentric coordinates - auto invDenom = 1 / (dot00 * dot11 - dot01 * dot01); - auto u = (dot11 * dot02 - dot01 * dot12) * invDenom; - auto v = (dot00 * dot12 - dot01 * dot02) * invDenom; - - // Check if point is in or on triangle - return (u >= 0) && (v >= 0) && (u + v <= 1); -} - /** * Estalish if points are coplanar. * @param vertices : All vertices to consider * @return : Return True only if all coplanar */ -bool MeshObject2D::pointsCoplanar( - const std::vector<Mantid::Kernel::V3D> &vertices) { +bool MeshObject2D::pointsCoplanar(const std::vector<Kernel::V3D> &vertices) { if (!CoplanarChecks::sufficientPoints(vertices)) return false; - Mantid::Kernel::V3D normal = CoplanarChecks::surfaceNormal(vertices); + Kernel::V3D normal = CoplanarChecks::surfaceNormal(vertices); // Check that a valid normal was found amongst collection of vertices if (normal.norm2() == 0) { // If all points are colinear. Not a plane. @@ -239,12 +182,7 @@ void MeshObject2D::initialize() { parameters.p0 = v0; m_planeParameters = parameters; - if (m_vertices.size() > - std::numeric_limits<typename decltype(m_triangles)::value_type>::max()) { - throw std::invalid_argument( - "Too many vertices (" + std::to_string(m_vertices.size()) + - "). MeshObject cannot have more than 65535 vertices."); - } + MeshObjectCommon::checkVertexLimit(m_vertices.size()); m_handler = boost::make_shared<GeometryHandler>(*this); } bool MeshObject2D::hasValidShape() const { @@ -270,8 +208,9 @@ bool MeshObject2D::isValid(const Kernel::V3D &point) const { static const double tolerance = 1e-9; if (distanceToPlane(point) < tolerance) { for (size_t i = 0; i < m_vertices.size(); i += 3) { - if (isOnTriangle(point, m_vertices[i], m_vertices[i + 1], - m_vertices[i + 2])) + + if (MeshObjectCommon::isOnTriangle(point, m_vertices[i], + m_vertices[i + 1], m_vertices[i + 2])) return true; } } @@ -301,14 +240,16 @@ int MeshObject2D::interceptSurface(Geometry::Track &ut) const { m_planeParameters.p0.scalar_prod(norm)) / ut.direction().scalar_prod(norm); - // Intersects infinite plane + // Intersects infinite plane. No point evaluating individual segements if this + // fails if (t >= 0) { - auto pIntersects = ut.startPoint() + ut.direction(); + Kernel::V3D intersection; + int entryExit; for (size_t i = 0; i < m_vertices.size(); i += 3) { - // Need to know that this corresponds to a finite segment - if (isOnTriangle(pIntersects, m_vertices[i], m_vertices[i + 1], - m_vertices[i + 2])) { - ut.addPoint(-1 /*HACK as exit*/, pIntersects, *this); + if (MeshObjectCommon::rayIntersectsTriangle( + ut.startPoint(), ut.direction(), m_vertices[i], m_vertices[i + 1], + m_vertices[i + 2], intersection, entryExit)) { + ut.addPoint(entryExit, intersection, *this); ut.buildLink(); // All vertices on plane. So only one triangle intersection possible break; @@ -335,32 +276,42 @@ int MeshObject2D::getName() const { // where this is used. } +/** + * Solid angle only considers triangle facing sample. Back faces do NOT + * contribute. + * + * This is tantamount to defining an object that is opaque to neutrons. Note + * that it is still possible to define a facing surface which is obscured by + * another. In that case there would still be a solid angle contribution as + * there is no way of detecting the shadowing. + * + * @param observer + * @return + */ double MeshObject2D::solidAngle(const Kernel::V3D &observer) const { - double solidAngleSum(0), solidAngleNegativeSum(0); - for (size_t i = 0; i < m_vertices.size(); i += 3) { - auto sa = getTriangleSolidAngle(m_vertices[m_triangles[i]], - m_vertices[m_triangles[i + 1]], - m_vertices[m_triangles[i + 2]], observer); - if (sa > 0.0) { + double solidAngleSum(0); + Kernel::V3D vertex1, vertex2, vertex3; + for (size_t i = 0; + getTriangle(i, m_triangles, m_vertices, vertex1, vertex2, vertex3); + ++i) { + double sa = MeshObjectCommon::getTriangleSolidAngle(vertex1, vertex2, + vertex3, observer); + if (sa > 0) { solidAngleSum += sa; - } else { - solidAngleNegativeSum += sa; } } - return 0.5 * (solidAngleSum - solidAngleNegativeSum); + return solidAngleSum; } double MeshObject2D::solidAngle(const Kernel::V3D &observer, const Kernel::V3D &scaleFactor) const { - std::vector<V3D> scaledVertices; + std::vector<Kernel::V3D> scaledVertices; scaledVertices.reserve(m_vertices.size()); for (const auto &vertex : m_vertices) { - scaledVertices.emplace_back(scaleFactor.X() * vertex.X(), - scaleFactor.Y() * vertex.Y(), - scaleFactor.Z() * vertex.Z()); + scaledVertices.emplace_back(scaleFactor * vertex); } - MeshObject2D scaledObject(m_triangles, scaledVertices, m_material); - return scaledObject.solidAngle(observer); + MeshObject2D meshScaled(m_triangles, scaledVertices, m_material); + return meshScaled.solidAngle(observer); } bool MeshObject2D::operator==(const MeshObject2D &other) const { @@ -386,49 +337,14 @@ double MeshObject2D::volume() const { * @returns A reference to a bounding box for this shape. */ const BoundingBox &MeshObject2D::getBoundingBox() const { - - if (m_boundingBox.isNull()) { - double minX, maxX, minY, maxY, minZ, maxZ; - minX = minY = minZ = std::numeric_limits<double>::max(); - maxX = maxY = maxZ = std::numeric_limits<double>::min(); - - // Loop over all vertices and determine minima and maxima on each axis - for (const auto &vertex : m_vertices) { - auto vx = vertex.X(); - auto vy = vertex.Y(); - auto vz = vertex.Z(); - - minX = std::min(minX, vx); - maxX = std::max(maxX, vx); - minY = std::min(minY, vy); - maxY = std::max(maxY, vy); - minZ = std::min(minZ, vz); - maxZ = std::max(maxZ, vz); - } - if (minX == maxX) - maxX += MinThickness; - if (minY == maxY) - maxY += MinThickness; - if (minZ == maxZ) - maxZ += MinThickness; - - // Cache bounding box, so we do not need to repeat calculation - m_boundingBox = BoundingBox(maxX, maxY, maxZ, minX, minY, minZ); - } - - return m_boundingBox; + return MeshObjectCommon::getBoundingBox(m_vertices, m_boundingBox); } void MeshObject2D::getBoundingBox(double &xmax, double &ymax, double &zmax, double &xmin, double &ymin, double &zmin) const { - auto bb = this->getBoundingBox(); - xmax = bb.xMax(); - xmin = bb.xMin(); - ymax = bb.yMax(); - ymin = bb.yMin(); - zmax = bb.zMax(); - zmin = bb.zMin(); + return MeshObjectCommon::getBoundingBox(m_vertices, m_boundingBox, xmax, ymax, + zmax, xmin, ymin, zmin); } /** @@ -474,28 +390,12 @@ size_t MeshObject2D::numberOfTriangles() const { } std::vector<double> MeshObject2D::getVertices() const { - std::vector<double> points; - size_t nPoints = m_vertices.size(); - points.resize(static_cast<std::size_t>(nPoints) * 3); - for (size_t i = 0; i < nPoints; ++i) { - const auto &pnt = m_vertices[i]; - points[i * 3] = pnt.X(); - points[i * 3 + 1] = pnt.Y(); - points[i * 3 + 2] = pnt.Z(); - } - return points; + return MeshObjectCommon::getVertices(m_vertices); } std::vector<uint32_t> MeshObject2D::getTriangles() const { - std::vector<uint32_t> faces; - size_t nFaceCorners = m_triangles.size(); - if (nFaceCorners > 0) { - faces.resize(static_cast<std::size_t>(nFaceCorners)); - for (size_t i = 0; i < nFaceCorners; ++i) { - faces[i] = static_cast<int>(m_triangles[i]); - } - } - return faces; + + return MeshObjectCommon::getTriangles_uint32(m_triangles); } void MeshObject2D::GetObjectGeom(detail::ShapeInfo::GeometryShape &, diff --git a/Framework/Geometry/src/Objects/MeshObjectCommon.cpp b/Framework/Geometry/src/Objects/MeshObjectCommon.cpp new file mode 100644 index 0000000000000000000000000000000000000000..368cff0fa3426ede810b886b5d82b8d42062666f --- /dev/null +++ b/Framework/Geometry/src/Objects/MeshObjectCommon.cpp @@ -0,0 +1,286 @@ +#include "MantidGeometry/Objects/MeshObjectCommon.h" +#include "MantidGeometry/Objects/BoundingBox.h" +#include <limits> +#include <string> + +namespace Mantid { +namespace Geometry { + +namespace MeshObjectCommon { +/** + * getVertices converts vector Kernel::V3D to vector doubles. 3x size of input. + * ordered x,y,z,x,y,z... + * @param vertices : input vector of Kernel::V3D + * @return: vector of doubles. Elements of input. + */ +std::vector<double> getVertices(const std::vector<Kernel::V3D> &vertices) { + std::vector<double> points; + size_t nPoints = vertices.size(); + if (nPoints > 0) { + points.resize(static_cast<std::size_t>(nPoints) * 3); + for (size_t i = 0; i < nPoints; ++i) { + const auto &pnt = vertices[i]; + points[i * 3] = pnt.X(); + points[i * 3 + 1] = pnt.Y(); + points[i * 3 + 2] = pnt.Z(); + } + } + return points; +} +/** + * Find the solid angle of a triangle defined by vectors a,b,c from point + *"observer" + * + * formula (Oosterom) O=2atan([a,b,c]/(abc+(a.b)c+(a.c)b+(b.c)a)) + * + * @param a :: first point of triangle + * @param b :: second point of triangle + * @param c :: third point of triangle + * @param observer :: point from which solid angle is required + * @return :: solid angle of triangle in Steradians. + * + * This duplicates code in CSGOjbect both need a place to be merged. + * To aid this, this function has been defined as a non-member. + */ +double getTriangleSolidAngle(const Kernel::V3D &a, const Kernel::V3D &b, + const Kernel::V3D &c, + const Kernel::V3D &observer) { + const Kernel::V3D ao = a - observer; + const Kernel::V3D bo = b - observer; + const Kernel::V3D co = c - observer; + const double modao = ao.norm(); + const double modbo = bo.norm(); + const double modco = co.norm(); + const double aobo = ao.scalar_prod(bo); + const double aoco = ao.scalar_prod(co); + const double boco = bo.scalar_prod(co); + const double scalTripProd = ao.scalar_prod(bo.cross_prod(co)); + const double denom = + modao * modbo * modco + modco * aobo + modbo * aoco + modao * boco; + if (denom != 0.0) + return 2.0 * atan2(scalTripProd, denom); + else + return 0.0; // not certain this is correct +} + +/** + * @brief isOnTriangle + * @param point : point to test + * @param v1 : first vertex of triangle + * @param v2 : second vertex of triangle + * @param v3 : thrid vertex of triangle + * @return True only point if is on triangle + */ +bool isOnTriangle(const Kernel::V3D &point, const Kernel::V3D &v1, + const Kernel::V3D &v2, const Kernel::V3D &v3) { + + // p = w*p0 + u*p1 + v*p2, where numbered p refers to vertices of triangle + // w+u+v == 1, so w = 1-u-v + // p = (1-u-v)p0 + u*p1 + v*p2, rearranging ... + // p = u(p1 - p0) + v(p2 - p0) + p0 + // in change of basis, barycentric coordinates p = p0 + u*e0 + v*e1. e0 and + // e1 are basis vectors. e2 = (p - p0) + // rewrite as e2 = u*e0 + v*e1 + // i) e2.e0 = u*e0.e0 + v*e1.e0 + // ii) e2.e1 = u*e0.e1 + v*e1.e1 + // solve for u, v and check u and v >= 0 and u+v <=1 + + auto e0 = v3 - v1; + auto e1 = v2 - v1; + auto e2 = point - v1; + + // Compute dot products + auto dot00 = e0.scalar_prod(e0); + auto dot01 = e0.scalar_prod(e1); + auto dot02 = e0.scalar_prod(e2); + auto dot11 = e1.scalar_prod(e1); + auto dot12 = e1.scalar_prod(e2); + + /* in matrix form + M = e0.e0 e1.e0 + e0.e1 e1.e1 + U = u + v + R = e2.e0 + e2.e1 + U = R*(M^-1) + */ + + // Compute barycentric coordinates + auto invDenom = 1 / (dot00 * dot11 - dot01 * dot01); + auto u = (dot11 * dot02 - dot01 * dot12) * invDenom; + auto v = (dot00 * dot12 - dot01 * dot02) * invDenom; + + // Check if point is in or on triangle + return (u >= 0) && (v >= 0) && (u + v <= 1); +} + +/** + * Get intersection points and their in out directions on the given ray + * @param start :: Start point of ray + * @param direction :: Direction of ray + * @param v1 :: First vertex of triangle + * @param v2 :: Second vertex of triangle + * @param v3 :: Third vertex of triangle + * @param intersection :: Intersection point + * @param entryExit :: 1 if intersection is entry, -1 if exit + * intersection + * @returns true if there is an intersection + */ +bool rayIntersectsTriangle(const Kernel::V3D &start, + const Kernel::V3D &direction, const Kernel::V3D &v1, + const Kernel::V3D &v2, const Kernel::V3D &v3, + Kernel::V3D &intersection, int &entryExit) { + // Implements Moller Trumbore intersection algorithm + + // Eq line x = x0 + tV + // + // p = w*p0 + u*p1 + v*p2, where numbered p refers to vertices of triangle + // w+u+v == 1, so w = 1-u-v + // p = (1-u-v)p0 + u*p1 + v*p2, rearranging ... + // p = u(p1 - p0) + v(p2 - p0) + p0 + // in change of basis, barycentric coordinates p = p0 + u*v0 + v*v1. v0 and + // v1 are basis vectors. + + // For line to pass through triangle... + // (x0 + tV) = u(p1 - p0) + v(p2 - p0) + p0, yields + // (x0 - p0) = -tV + u(p1 - p0) + v(p2 - p0) + + // rest is just to solve for u, v, t and check u and v are both >= 0 and <= 1 + // and u+v <=1 + + auto edge1 = v2 - v1; + auto edge2 = v3 - v1; + auto h = direction.cross_prod(edge2); + auto a = edge1.scalar_prod(h); + + const double EPSILON = 0.0000001 * edge1.norm(); + if (a > -EPSILON && a < EPSILON) + return false; // Ray in or parallel to plane of triangle + auto f = 1 / a; + auto s = start - v1; + // Barycentric coordinate offset u + auto u = f * (s.scalar_prod(h)); + if (u < 0.0 || u > 1.0) + return false; // Intersection with plane outside triangle + auto q = s.cross_prod(edge1); + // Barycentric coordinate offset v + auto v = f * direction.scalar_prod(q); + if (v < 0.0 || u + v > 1.0) + return false; // Intersection with plane outside triangle + + // At this stage we can compute t to find out where the intersection point is + // on the line. + auto t = f * edge2.scalar_prod(q); + if (t >= -EPSILON) // ray intersection + { + intersection = start + direction * t; + + // determine entry exit assuming anticlockwise triangle view from outside + Kernel::V3D normalDirection = edge1.cross_prod(edge2); + if (normalDirection.scalar_prod(direction) > 0.0) { + entryExit = -1; // exit + } else { + entryExit = 1; // entry + } + return true; + } + // The triangle is behind the start point. Forward ray does not intersect. + return false; +} + +void checkVertexLimit(size_t nVertices) { + if (nVertices > std::numeric_limits<uint16_t>::max()) { + throw std::invalid_argument( + "Too many vertices (" + std::to_string(nVertices) + + "). MeshObject cannot have more than 65535 vertices."); + } +} + +/** + * Converts triangle indices from unit16 to unit32 + * @param input + * @return indices as unit32 vector + */ +std::vector<uint32_t> getTriangles_uint32(const std::vector<uint16_t> &input) { + std::vector<uint32_t> faces; + size_t nFaceCorners = input.size(); + if (nFaceCorners > 0) { + faces.resize(static_cast<std::size_t>(nFaceCorners)); + for (size_t i = 0; i < nFaceCorners; ++i) { + faces[i] = static_cast<int>(input[i]); + } + } + return faces; +} + +/** + * Takes input vertices and calculates bounding box. Returns the bounding box. + * + * @param vertices :: vertices to create BB from + * @param cacheBB :: mutable BB object to write to. + */ +const BoundingBox &getBoundingBox(const std::vector<Kernel::V3D> &vertices, + BoundingBox &cacheBB) { + + if (cacheBB.isNull()) { + static const double MinThickness = 0.001; + double minX, maxX, minY, maxY, minZ, maxZ; + minX = minY = minZ = std::numeric_limits<double>::max(); + maxX = maxY = maxZ = std::numeric_limits<double>::min(); + + // Loop over all vertices and determine minima and maxima on each axis + for (const auto &vertex : vertices) { + auto vx = vertex.X(); + auto vy = vertex.Y(); + auto vz = vertex.Z(); + + minX = std::min(minX, vx); + maxX = std::max(maxX, vx); + minY = std::min(minY, vy); + maxY = std::max(maxY, vy); + minZ = std::min(minZ, vz); + maxZ = std::max(maxZ, vz); + } + if (minX == maxX) + maxX += MinThickness; + if (minY == maxY) + maxY += MinThickness; + if (minZ == maxZ) + maxZ += MinThickness; + + // Cache bounding box, so we do not need to repeat calculation + cacheBB = BoundingBox(maxX, maxY, maxZ, minX, minY, minZ); + } + + return cacheBB; +} + +/** + * From vertices calculates the bounding box. Returns them back in max and min + * points as well as mutated BB object. + * + * @param vertices :: vertices to create BB from + * @param cacheBB :: mutable BB object to write to. + * @param xmax :: Maximum value for the bounding box in x direction + * @param ymax :: Maximum value for the bounding box in y direction + * @param zmax :: Maximum value for the bounding box in z direction + * @param xmin :: Minimum value for the bounding box in x direction + * @param ymin :: Minimum value for the bounding box in y direction + * @param zmin :: Minimum value for the bounding box in z direction + */ +void getBoundingBox(const std::vector<Kernel::V3D> &vertices, + BoundingBox &cacheBB, double &xmax, double &ymax, + double &zmax, double &xmin, double &ymin, double &zmin) { + auto bb = getBoundingBox(vertices, cacheBB); + xmax = bb.xMax(); + xmin = bb.xMin(); + ymax = bb.yMax(); + ymin = bb.yMin(); + zmax = bb.zMax(); + zmin = bb.zMin(); +} + +} // namespace MeshObjectCommon +} // namespace Geometry +} // namespace Mantid diff --git a/Framework/Geometry/test/ComponentInfoBankHelpersTest.h b/Framework/Geometry/test/ComponentInfoBankHelpersTest.h new file mode 100644 index 0000000000000000000000000000000000000000..76b0486d3cb0a14ef59e8e2c1da009c95d3fed3a --- /dev/null +++ b/Framework/Geometry/test/ComponentInfoBankHelpersTest.h @@ -0,0 +1,75 @@ +#ifndef MANTID_GEOMETRY_COMPONENTINFOBANKHELPERSTEST_H_ +#define MANTID_GEOMETRY_COMPONENTINFOBANKHELPERSTEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "MantidGeometry/Instrument/ComponentInfo.h" +#include "MantidGeometry/Instrument/ComponentInfoBankHelpers.h" +#include "MantidGeometry/Instrument/DetectorInfo.h" +#include "MantidGeometry/Instrument/GridDetector.h" +#include "MantidGeometry/Instrument/InstrumentVisitor.h" +#include "MantidTestHelpers/ComponentCreationHelper.h" +#include <boost/make_shared.hpp> + +using namespace Mantid; +using namespace Mantid::Kernel; +using namespace Mantid::Geometry; +using namespace Mantid::Geometry::ComponentInfoBankHelpers; + +class ComponentInfoBankHelpersTest : public CxxTest::TestSuite { +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static ComponentInfoBankHelpersTest *createSuite() { + return new ComponentInfoBankHelpersTest(); + } + static void destroySuite(ComponentInfoBankHelpersTest *suite) { + delete suite; + } + + void test_DetectorFixedInBankTrueForRectangularBank() { + auto rectInstr = + ComponentCreationHelper::createTestInstrumentRectangular(1, 2); + auto wrappers = InstrumentVisitor::makeWrappers(*rectInstr); + const auto &componentInfo = *wrappers.first; + + TS_ASSERT(isDetectorFixedInBank(componentInfo, 0)); + } + + void test_DetectorFixedInBankFalseForNonStructuredBank() { + auto instr = ComponentCreationHelper::createTestInstrumentCylindrical(1); + auto wrappers = InstrumentVisitor::makeWrappers(*instr); + const auto &componentInfo = *wrappers.first; + + TS_ASSERT(!isDetectorFixedInBank(componentInfo, 0)); + } + + void test_DetectorFixedInBankFalseForMonitor() { + auto instr = ComponentCreationHelper::createTestInstrumentCylindrical(1); + auto det = new Detector("MyTestMonitor", 10, instr.get()); + auto shape = ComponentCreationHelper::createCuboid(0.001, 0.001, 0.001); + det->setShape(shape); + det->setPos(V3D(0, 0, 0)); + det->setRot(Quat()); + + instr->add(det); + instr->markAsMonitor(det); + + auto wrappers = InstrumentVisitor::makeWrappers(*instr); + const auto &componentInfo = *wrappers.first; + + auto index = componentInfo.indexOfAny("MyTestMonitor"); + + TS_ASSERT(!isDetectorFixedInBank(componentInfo, index)); + } + + void test_DetectorFixedInBankFalseForNonDetectorComponent() { + auto instr = ComponentCreationHelper::createTestInstrumentCylindrical(1); + auto wrappers = InstrumentVisitor::makeWrappers(*instr); + const auto &componentInfo = *wrappers.first; + + TS_ASSERT(!isDetectorFixedInBank(componentInfo, componentInfo.root())); + } +}; + +#endif /* MANTID_GEOMETRY_COMPONENTINFOBANKHELPERSTEST_H_ */ \ No newline at end of file diff --git a/Framework/Geometry/test/ComponentInfoTest.h b/Framework/Geometry/test/ComponentInfoTest.h index d8083b7ff381502aa6e0a1277af6e9d184332c05..4848ee9d560ba8c15233d5e6c3ebffa7bddf06df 100644 --- a/Framework/Geometry/test/ComponentInfoTest.h +++ b/Framework/Geometry/test/ComponentInfoTest.h @@ -656,7 +656,7 @@ public: infoScan1->merge(*infoScan2); - TS_ASSERT_EQUALS(infoScan1->scanSize(), + TS_ASSERT_EQUALS(infoScan1->size() * infoScan1->scanCount(), infoScan1->size() + infoScan2->size()); TS_ASSERT_EQUALS(infoScan1->position({0 /*detector index*/, 0}), V3D(0, 0, 0)); diff --git a/Framework/Geometry/test/DetectorInfoIteratorTest.h b/Framework/Geometry/test/DetectorInfoIteratorTest.h index d955d91fe2070d2ba69e46644c4f5dce483781eb..bcf5aadeaa39594ba2f03c25317cf64ebda3ee8e 100644 --- a/Framework/Geometry/test/DetectorInfoIteratorTest.h +++ b/Framework/Geometry/test/DetectorInfoIteratorTest.h @@ -69,31 +69,30 @@ public: return InstrumentVisitor::makeWrappers(*visitee, nullptr).second; } - void test_iterator_begin() { + void test_iterator_cbegin() { // Get the DetectorInfo object auto detectorInfo = create_detector_info_object(); - auto iter = detectorInfo->begin(); - + auto iter = detectorInfo->cbegin(); // Check we start at the correct place - TS_ASSERT(iter != detectorInfo->end()); + TS_ASSERT(iter != detectorInfo->cend()); } - void test_iterator_end() { + void test_iterator_cend() { // Get the DetectorInfo object auto detectorInfo = create_detector_info_object(); - auto iter = detectorInfo->end(); + auto iter = detectorInfo->cend(); // Check we start at the correct place - TS_ASSERT(iter != detectorInfo->begin()); + TS_ASSERT(iter != detectorInfo->cbegin()); } void test_iterator_increment_and_positions() { // Get the DetectorInfo object auto detectorInfo = create_detector_info_object(); - auto iter = detectorInfo->begin(); + auto iter = detectorInfo->cbegin(); // Check that we start at the beginning - TS_ASSERT(iter == detectorInfo->begin()); + TS_ASSERT(iter == detectorInfo->cbegin()); // Doubles for values in the V3D position object double xValue = 0.0; @@ -114,16 +113,16 @@ public: } // Check we've reached the end - TS_ASSERT(iter == detectorInfo->end()); + TS_ASSERT(iter == detectorInfo->cend()); } void test_iterator_decrement_and_positions() { // Get the DetectorInfo object auto detectorInfo = create_detector_info_object(); - auto iter = detectorInfo->end(); + auto iter = detectorInfo->cend(); // Check that we start at the end - TS_ASSERT(iter == detectorInfo->end()); + TS_ASSERT(iter == detectorInfo->cend()); // Doubles for values in the V3D position object double xValue = 0.0; @@ -144,13 +143,13 @@ public: } // Check we've reached the beginning - TS_ASSERT(iter == detectorInfo->begin()); + TS_ASSERT(iter == detectorInfo->cbegin()); } void test_iterator_advance_and_positions() { // Get the DetectorInfo object auto detectorInfo = create_detector_info_object(); - auto iter = detectorInfo->begin(); + auto iter = detectorInfo->cbegin(); // Store the expected X value double xValue = 0.0; @@ -167,16 +166,16 @@ public: // Go to the start std::advance(iter, -4); - TS_ASSERT(iter == detectorInfo->begin()); + TS_ASSERT(iter == detectorInfo->cbegin()); } void test_copy_iterator_and_positions() { // Get the DetectorInfo object auto detectorInfo = create_detector_info_object(); - auto iter = detectorInfo->begin(); + auto iter = detectorInfo->cbegin(); // Create a copy - auto iterCopy = DetectorInfoIterator(iter); + auto iterCopy = DetectorInfoConstIt(iter); // Check TS_ASSERT_EQUALS(iter->position().X(), 11.0); @@ -190,6 +189,13 @@ public: TS_ASSERT_EQUALS(iter->position().X(), 12.0); TS_ASSERT_EQUALS(iterCopy->position().X(), 12.0); } + + void test_non_const() { + auto detectorInfo = create_detector_info_object(); + auto it = detectorInfo->begin(); + it->setMasked(true); + TS_ASSERT(it->isMasked()); + } }; #endif /* MANTID_GEOMETRY_DETECTORINFOITERATORTEST_H_ */ diff --git a/Framework/Geometry/test/MeshObject2DTest.h b/Framework/Geometry/test/MeshObject2DTest.h index e9cc20805bba67f87942f10a4259fccf8923b7d0..7d0f1a2e79b0eb6a83b060929baf37a40e4bde67 100644 --- a/Framework/Geometry/test/MeshObject2DTest.h +++ b/Framework/Geometry/test/MeshObject2DTest.h @@ -202,6 +202,68 @@ public: TS_ASSERT_EQUALS(1.0, mesh.distanceToPlane(V3D{0, 0.5, 1})); } + void test_solidAngle_side_on() { + using namespace Mantid::Kernel; + auto mesh = makeSimpleTriangleMesh(); + auto solidAngle = mesh.solidAngle( + V3D{0, 2, 0}); // observer is in plane of triangle, outside the triangle + TS_ASSERT_EQUALS(solidAngle, 0); // seen side-on solid angle is 0 + } + void test_square_solid_angle() { + + // Unit square inside unit cube. Any cube face will have solid angle 1/6th + // of total 4pi steradians. Observer at origin. + + double expected = 2.0 * M_PI / 3.0; // 4pi/6 + // Unit square at distance cos(M_PI / 4) from observer + double unitSphereRadius = 1; + double halfSideLength = unitSphereRadius * sin(M_PI / 4); + double observerDistance = unitSphereRadius * cos(M_PI / 4); + std::vector<V3D> vertices = { + V3D{-halfSideLength, -halfSideLength, observerDistance}, + V3D{-halfSideLength, halfSideLength, observerDistance}, + V3D{halfSideLength, halfSideLength, observerDistance}, + V3D{halfSideLength, -halfSideLength, observerDistance}}; + std::vector<uint16_t> triangles{2, 1, 0, 0, 3, 2}; + MeshObject2D mesh(triangles, vertices, Mantid::Kernel::Material{}); + double solidAngle = mesh.solidAngle(V3D{0, 0, 0}); + TS_ASSERT_DELTA(solidAngle, expected, 1e-3); + + // Only the positive solid angle is counted. Observe from the other side and + // solid angle is zero. + solidAngle = mesh.solidAngle(V3D{0, 0, 2 * observerDistance}); + TS_ASSERT_DELTA(solidAngle, 0, 1e-3); + } + + void test_solidAngle_scaled() { + // Unit square inside unit cube. Any cube face will have solid angle 1/6th + // of total 4pi steradians. Observer at origin. + + double expected = 2.0 * M_PI / 3.0; // 4pi/6 + // Unit square at distance 0.5 from observer + double unitSphereRadius = 1; + double halfSideLength = unitSphereRadius * sin(M_PI / 4); + double observerDistance = unitSphereRadius * cos(M_PI / 4); + std::vector<V3D> vertices = { + V3D{-halfSideLength, -halfSideLength, observerDistance}, + V3D{-halfSideLength, halfSideLength, observerDistance}, + V3D{halfSideLength, halfSideLength, observerDistance}, + V3D{halfSideLength, -halfSideLength, observerDistance}}; + std::vector<uint16_t> triangles{2, 1, 0, 0, 3, 2}; + // Scaling square uniformly (and reducing distance to origin by same + // factor), yields same angular area 4pi/6 + V3D scaleFactor{0.5, 0.5, 0.5}; + MeshObject2D mesh(triangles, vertices, Mantid::Kernel::Material{}); + double solidAngle = mesh.solidAngle(V3D{0, 0, 0}, scaleFactor); + TS_ASSERT_DELTA(solidAngle, expected, 1e-3); + + // Scaling square uniformly (and increasing distance to origin by same + // factor), yields same angular area 4pi/6 + scaleFactor = {2, 2, 2}; + solidAngle = mesh.solidAngle(V3D{0, 0, 0}, scaleFactor); + TS_ASSERT_DELTA(solidAngle, expected, 1e-3); + } + void test_isValid_multi_triangle() { // Make 2 Triangles bounded by the specified V3Ds @@ -216,36 +278,28 @@ public: TSM_ASSERT("Just outside", !mesh.isValid(V3D{1, 1 + delta, 0})); } - void test_isOnTriangle() { - auto p1 = V3D{-1, -1, 0}; - auto p2 = V3D{1, -1, 0}; - auto p3 = V3D{0, 1, 0}; - TS_ASSERT(MeshObject2D::isOnTriangle(V3D{0, 0, 0}, p1, p2, p3)); - TS_ASSERT(MeshObject2D::isOnTriangle(p1, p1, p2, p3)); - TS_ASSERT(MeshObject2D::isOnTriangle(p2, p1, p2, p3)); - TS_ASSERT(MeshObject2D::isOnTriangle(p3, p1, p2, p3)); - TS_ASSERT(!MeshObject2D::isOnTriangle(p1 - V3D(0.0001, 0, 0), p1, p2, p3)); - TS_ASSERT(!MeshObject2D::isOnTriangle(p1 - V3D(0, 0.0001, 0), p1, p2, p3)); - TS_ASSERT(!MeshObject2D::isOnTriangle(p2 + V3D(0.0001, 0, 0), p1, p2, p3)); - TS_ASSERT(!MeshObject2D::isOnTriangle(p2 - V3D(0, 0.0001, 0), p1, p2, p3)); - TS_ASSERT(!MeshObject2D::isOnTriangle(p3 + V3D(0.0001, 0, 0), p1, p2, p3)); - TS_ASSERT(!MeshObject2D::isOnTriangle(p3 + V3D(0, 0.0001, 0), p1, p2, p3)); - } - void test_interceptSurface() { auto mesh = makeSimpleTriangleMesh(); + // Track goes through triangle body Mantid::Geometry::Track onTargetTrack(V3D{0.5, 0.5, -1}, V3D{0, 0, 1} /*along z*/); TS_ASSERT_EQUALS(mesh.interceptSurface(onTargetTrack), 1); TS_ASSERT_EQUALS(onTargetTrack.count(), 1); + // Track completely misses Mantid::Geometry::Track missTargetTrack( V3D{50, 0.5, -1}, V3D{0, 0, 1} /*along z*/); // Intersects plane but no triangles TS_ASSERT_EQUALS(mesh.interceptSurface(missTargetTrack), 0); TS_ASSERT_EQUALS(missTargetTrack.count(), 0); + + // Track goes through edge + Mantid::Geometry::Track edgeTargetTrack( + V3D{0, 0, -1}, V3D{0, 0, 1} /*along z*/); // Passes through lower edge + TS_ASSERT_EQUALS(mesh.interceptSurface(edgeTargetTrack), 1); + TS_ASSERT_EQUALS(edgeTargetTrack.count(), 1); } void test_equals() { @@ -278,32 +332,6 @@ public: TS_ASSERT_EQUALS(a, *c); } - void test_solidAngle_side_on() { - using namespace Mantid::Kernel; - - auto mesh = makeSimpleTriangleMesh(); - auto solidAngle = mesh.solidAngle( - V3D{0, 2, 0}); // observer is in plane of triangle, outside the triangle - - TS_ASSERT_EQUALS(solidAngle, 0); // seen side-on solid angle is 0 - } - - void test_solidAngle() { - - double expected = M_PI / 3.0; // 0.5 * 4pi/6 - // Unit square at distance 0.5 from observer - double unitSphereRadius = 1; - double halfSideLength = unitSphereRadius * sin(M_PI / 4); - double observerDistance = unitSphereRadius * cos(M_PI / 4); - auto mesh = makeTrapezoidMesh( - V3D{-halfSideLength, -halfSideLength, observerDistance}, - V3D{-halfSideLength, halfSideLength, observerDistance}, - V3D{halfSideLength, halfSideLength, observerDistance}, - V3D{halfSideLength, -halfSideLength, observerDistance}); - double solidAngle = mesh.solidAngle(V3D{0, 0, 0}); - TS_ASSERT_DELTA(solidAngle, expected, 1e-3); - } - void test_boundingBox() { auto mesh = makeSimpleTriangleMesh(); // Lies in z plane auto boundingBox = mesh.getBoundingBox(); diff --git a/Framework/Geometry/test/MeshObjectCommonTest.h b/Framework/Geometry/test/MeshObjectCommonTest.h new file mode 100644 index 0000000000000000000000000000000000000000..6799b51410a03c67d7dd6798c74bd4fc360067d1 --- /dev/null +++ b/Framework/Geometry/test/MeshObjectCommonTest.h @@ -0,0 +1,123 @@ +#ifndef MANTID_GEOMETRY_MESHOBJECTCOMMONTEST_H_ +#define MANTID_GEOMETRY_MESHOBJECTCOMMONTEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "MantidGeometry/Objects/MeshObjectCommon.h" +#include "MantidKernel/V3D.h" + +using namespace Mantid::Geometry; +using Mantid::Kernel::V3D; + +class MeshObjectCommonTest : public CxxTest::TestSuite { +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static MeshObjectCommonTest *createSuite() { + return new MeshObjectCommonTest(); + } + static void destroySuite(MeshObjectCommonTest *suite) { delete suite; } + + void test_ray_intersect_triangle_simple() { + + const V3D start{0, 0, -1}; + const V3D direction{0, 0, 1}; + const V3D vertex1{-1, 0, 0}; + const V3D vertex2{1, 0, 0}; + const V3D vertex3{1, 1, 0}; + V3D intersectionPoint; + int entryExitFlag; + + // Direct intersection through triangle body + auto doesIntersect = MeshObjectCommon::rayIntersectsTriangle( + start, direction, vertex1, vertex2, vertex3, intersectionPoint, + entryExitFlag); + TS_ASSERT(doesIntersect); + TS_ASSERT_EQUALS(entryExitFlag, -1); + TS_ASSERT((start + (direction * 1) - intersectionPoint).norm2() < 1e-9); + } + + void test_v3d_to_array() { + std::vector<V3D> input{{1, 2, 3}, {4, 5, 6}}; + auto output = MeshObjectCommon::getVertices(input); + TS_ASSERT_EQUALS(output, (std::vector<double>{1, 2, 3, 4, 5, 6})); + } + + void test_ray_intersect_triangle_edge() { + + const V3D direction{0, 0, 1}; + const V3D vertex1{-1, 0, 0}; + const V3D vertex2{1, 0, 0}; + const V3D vertex3{1, 1, 0}; + V3D intersectionPoint; + int entryExitFlag; + + // Test ray going through vertex of triangle + V3D start = vertex1 - direction; + auto doesIntersect = MeshObjectCommon::rayIntersectsTriangle( + start, direction, vertex1, vertex2, vertex3, intersectionPoint, + entryExitFlag); + TS_ASSERT(doesIntersect); + + // Check another vertex + start = vertex3 - direction; + doesIntersect = MeshObjectCommon::rayIntersectsTriangle( + start, direction, vertex1, vertex2, vertex3, intersectionPoint, + entryExitFlag); + TS_ASSERT(doesIntersect); + + // Check an edge + start = (vertex1 + vertex2) / 2 - direction; // along edge + doesIntersect = MeshObjectCommon::rayIntersectsTriangle( + start, direction, vertex1, vertex2, vertex3, intersectionPoint, + entryExitFlag); + TS_ASSERT(doesIntersect); + + // Sanity check just outside. + start = (vertex1 + vertex2) / 2 - direction; // along edge + start += V3D(0, -1e-6, 0); // minor shift down in y + doesIntersect = MeshObjectCommon::rayIntersectsTriangle( + start, direction, vertex1, vertex2, vertex3, intersectionPoint, + entryExitFlag); + TS_ASSERT(!doesIntersect); + } + + void test_no_ray_intersect_triangle_when_triangle_behind() { + V3D start{0, 0, -1}; + V3D direction{0, 0, 1}; + const V3D vertex1{-1, 0, 0}; + const V3D vertex2{1, 0, 0}; + const V3D vertex3{1, 1, 0}; + V3D intersectionPoint; + int entryExitFlag; + // Triangle now behind start. Should not intersect + start = V3D{0, 0, 10}; + auto doesIntersect = MeshObjectCommon::rayIntersectsTriangle( + start, direction, vertex1, vertex2, vertex3, intersectionPoint, + entryExitFlag); + TS_ASSERT(!doesIntersect); + } + void test_isOnTriangle() { + auto p1 = V3D{-1, -1, 0}; + auto p2 = V3D{1, -1, 0}; + auto p3 = V3D{0, 1, 0}; + TS_ASSERT(MeshObjectCommon::isOnTriangle(V3D{0, 0, 0}, p1, p2, p3)); + TS_ASSERT(MeshObjectCommon::isOnTriangle(p1, p1, p2, p3)); + TS_ASSERT(MeshObjectCommon::isOnTriangle(p2, p1, p2, p3)); + TS_ASSERT(MeshObjectCommon::isOnTriangle(p3, p1, p2, p3)); + TS_ASSERT( + !MeshObjectCommon::isOnTriangle(p1 - V3D(0.0001, 0, 0), p1, p2, p3)); + TS_ASSERT( + !MeshObjectCommon::isOnTriangle(p1 - V3D(0, 0.0001, 0), p1, p2, p3)); + TS_ASSERT( + !MeshObjectCommon::isOnTriangle(p2 + V3D(0.0001, 0, 0), p1, p2, p3)); + TS_ASSERT( + !MeshObjectCommon::isOnTriangle(p2 - V3D(0, 0.0001, 0), p1, p2, p3)); + TS_ASSERT( + !MeshObjectCommon::isOnTriangle(p3 + V3D(0.0001, 0, 0), p1, p2, p3)); + TS_ASSERT( + !MeshObjectCommon::isOnTriangle(p3 + V3D(0, 0.0001, 0), p1, p2, p3)); + } +}; + +#endif /* MANTID_GEOMETRY_MESHOBJECTCOMMONTEST_H_ */ diff --git a/Framework/Geometry/test/MeshObjectTest.h b/Framework/Geometry/test/MeshObjectTest.h index 905425850597edf6103a3814f709f7a6be01c64e..3d1d8d7351919b4be687ee51f33e313275bc506f 100644 --- a/Framework/Geometry/test/MeshObjectTest.h +++ b/Framework/Geometry/test/MeshObjectTest.h @@ -912,8 +912,8 @@ public: Test solid angle calculation for a cube. */ { - auto geom_obj = createCube(1.0); - double satol = 1e-3; // tolerance for solid angle + auto geom_obj = createCube(1.0); // Cube centre at 0.5, 0.5, 0.5 + double satol = 1e-3; // tolerance for solid angle // solid angle at distance 0.5 should be 4pi/6 by symmetry TS_ASSERT_DELTA(geom_obj->solidAngle(V3D(1.5, 0.5, 0.5)), M_PI * 2.0 / 3.0, diff --git a/Framework/Geometry/test/PeakTransformSelectorTest.h b/Framework/Geometry/test/PeakTransformSelectorTest.h index 03c01a47a4591ea2739d21e86b534dd2399f985f..673cd628c1b991b5704c37c54b3fae6f856a8321 100644 --- a/Framework/Geometry/test/PeakTransformSelectorTest.h +++ b/Framework/Geometry/test/PeakTransformSelectorTest.h @@ -16,7 +16,6 @@ using namespace Mantid::Geometry; using namespace Mantid; using namespace testing; -using boost::regex; class PeakTransformSelectorTest : public CxxTest::TestSuite { private: diff --git a/Framework/Geometry/test/ReferenceFrameTest.h b/Framework/Geometry/test/ReferenceFrameTest.h index 05d40757ed4ce2cff882a81594b2f2b3ab533804..3a6f2d69c8aa9ba3671d7befa98737cb3714b5f2 100644 --- a/Framework/Geometry/test/ReferenceFrameTest.h +++ b/Framework/Geometry/test/ReferenceFrameTest.h @@ -146,6 +146,32 @@ public: TS_ASSERT_EQUALS(1, z_vec[2]); } + void testGetHorizontalDirectionVector() { + ReferenceFrame z1(Y, X, Right, "source"); + V3D z_vec = z1.vecPointingHorizontal(); + TS_ASSERT_EQUALS(0, z_vec[0]); + TS_ASSERT_EQUALS(0, z_vec[1]); + TS_ASSERT_EQUALS(1, z_vec[2]); + + ReferenceFrame z2(X, Y, Right, "source"); + z_vec = z2.vecPointingHorizontal(); + TS_ASSERT_EQUALS(0, z_vec[0]); + TS_ASSERT_EQUALS(0, z_vec[1]); + TS_ASSERT_EQUALS(1, z_vec[2]); + + ReferenceFrame y(X, Z, Right, "source"); + V3D y_vec = y.vecPointingHorizontal(); + TS_ASSERT_EQUALS(0, y_vec[0]); + TS_ASSERT_EQUALS(1, y_vec[1]); + TS_ASSERT_EQUALS(0, y_vec[2]); + + ReferenceFrame x(Y, Z, Right, "source"); + V3D x_vec = x.vecPointingHorizontal(); + TS_ASSERT_EQUALS(1, x_vec[0]); + TS_ASSERT_EQUALS(0, x_vec[1]); + TS_ASSERT_EQUALS(0, x_vec[2]); + } + void testGetThetaSignAxisVector() { // Default constructor, theta-sign axis is the same as up ReferenceFrame y(Y, X, Right, "source"); diff --git a/Framework/HistogramData/inc/MantidHistogramData/HistogramItem.h b/Framework/HistogramData/inc/MantidHistogramData/HistogramItem.h index ca47f52f5ca8c6481cc99308f78352482a87b7a3..d0da03dbd438c9f4e012304315306a9c996309b9 100644 --- a/Framework/HistogramData/inc/MantidHistogramData/HistogramItem.h +++ b/Framework/HistogramData/inc/MantidHistogramData/HistogramItem.h @@ -45,6 +45,11 @@ public: } } + double centerError() const { + const auto &dx = m_histogram.dx(); + return dx[m_index]; + } + double binWidth() const { const auto &x = m_histogram.x(); if (xModeIsPoints()) { diff --git a/Framework/HistogramData/test/HistogramIteratorTest.h b/Framework/HistogramData/test/HistogramIteratorTest.h index e6c201c3af19a0258651861beab95671e047487c..c9a050e23808736c6e09942329d4cfd9d50b6199 100644 --- a/Framework/HistogramData/test/HistogramIteratorTest.h +++ b/Framework/HistogramData/test/HistogramIteratorTest.h @@ -104,6 +104,16 @@ public: TS_ASSERT_EQUALS(std::distance(begin, end), 2); } + void test_iterate_over_histogram_x_errors() { + Histogram hist(Points{1.1, 1.2, 1.3}, Counts{5, 4, 6}); + hist.setPointStandardDeviations(PointStandardDeviations{0.1, 0.3, 0.5}); + const auto dX = hist.dataDx(); + TS_ASSERT(std::equal(hist.begin(), hist.end(), dX.begin(), + [](const HistogramItem &item, const double &dx) { + return item.centerError() == dx; + })); + } + void test_iterate_over_histogram_counts() { Counts expectedCounts{2, 3, 4}; Histogram hist(Points{1.1, 1.2, 1.4}, expectedCounts); diff --git a/Framework/Kernel/src/ConfigService.cpp b/Framework/Kernel/src/ConfigService.cpp index 9bcf001666afe81c27e0ec1e9417b3659c218f6c..08453e1f34d18d3b625dccebf99d6d8cd028d62b 100644 --- a/Framework/Kernel/src/ConfigService.cpp +++ b/Framework/Kernel/src/ConfigService.cpp @@ -51,7 +51,6 @@ #include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/trim.hpp> #include <boost/optional/optional.hpp> -#include <boost/regex.hpp> #include <algorithm> #include <cctype> diff --git a/Framework/Kernel/src/InternetHelper.cpp b/Framework/Kernel/src/InternetHelper.cpp index 170b6b87a0e7b79e18183618d0c947c0e3a72bab..55d54fd383593e99fa1bb6694fe55bc5f31c8fbf 100644 --- a/Framework/Kernel/src/InternetHelper.cpp +++ b/Framework/Kernel/src/InternetHelper.cpp @@ -9,6 +9,7 @@ #include "MantidKernel/DateAndTime.h" #include "MantidKernel/Exception.h" #include "MantidKernel/Logger.h" +#include "MantidKernel/MantidVersion.h" // Poco #include <Poco/Net/AcceptCertificateHandler.h> @@ -141,7 +142,9 @@ void InternetHelper::createRequest(Poco::URI &uri) { m_request->setContentType(m_contentType); } - m_request->set("User-Agent", "MANTID"); + m_request->set("User-Agent", + // Use standard User-Agent format as per MDN documentation. + std::string("Mantid/") + MantidVersion::version()); if (m_method == "POST") { // HTTP states that the 'Content-Length' header should not be included // if the 'Transfer-Encoding' header is set. UNKNOWN_CONTENT_LENGTH diff --git a/Framework/Kernel/src/Material.cpp b/Framework/Kernel/src/Material.cpp index c0d246292745a83c5cc9b523494eea95e7fea4ad..904fe0ad4dbd9ae5d552953d59d84d297052491f 100644 --- a/Framework/Kernel/src/Material.cpp +++ b/Framework/Kernel/src/Material.cpp @@ -10,7 +10,6 @@ #include <NeXusFile.hpp> #include <boost/lexical_cast.hpp> #include <boost/make_shared.hpp> -#include <boost/regex.hpp> #include <numeric> namespace Mantid { diff --git a/Framework/Kernel/src/TimeSeriesProperty.cpp b/Framework/Kernel/src/TimeSeriesProperty.cpp index df9dff13135eb640e5e097779ab4db35ad94ea54..8c5d1d4e4853a1f17be1aa0f13f17cb949bcaa73 100644 --- a/Framework/Kernel/src/TimeSeriesProperty.cpp +++ b/Framework/Kernel/src/TimeSeriesProperty.cpp @@ -1959,7 +1959,8 @@ template <typename TYPE> void TimeSeriesProperty<TYPE>::countSize() const { */ template <typename TYPE> bool TimeSeriesProperty<TYPE>::isTimeString(const std::string &str) { - boost::regex re("^[0-9]{4}.[0-9]{2}.[0-9]{2}.[0-9]{2}.[0-9]{2}.[0-9]{2}"); + static const boost::regex re( + "^[0-9]{4}.[0-9]{2}.[0-9]{2}.[0-9]{2}.[0-9]{2}.[0-9]{2}"); return boost::regex_search(str.begin(), str.end(), re); } diff --git a/Framework/Kernel/test/LogParserTest.h b/Framework/Kernel/test/LogParserTest.h index a29f5e6098895e14fd48f82caca34a0e5479d386..055bb00cdc5cee7c29fd6a042de4afa0f575ebd2 100644 --- a/Framework/Kernel/test/LogParserTest.h +++ b/Framework/Kernel/test/LogParserTest.h @@ -17,7 +17,6 @@ #include "MantidKernel/TimeSeriesProperty.h" #include "MantidKernel/make_unique.h" #include <boost/lexical_cast.hpp> -#include <boost/scoped_ptr.hpp> #include <Poco/File.h> diff --git a/Framework/Kernel/test/NDRandomNumberGeneratorTest.h b/Framework/Kernel/test/NDRandomNumberGeneratorTest.h index 0b8fdd617931dd2015b0af9db058a24b0cf82414..c057725ef90c5c08920cefb6b9ea8d0a81939ace 100644 --- a/Framework/Kernel/test/NDRandomNumberGeneratorTest.h +++ b/Framework/Kernel/test/NDRandomNumberGeneratorTest.h @@ -11,7 +11,6 @@ #include "MantidKernel/WarningSuppressions.h" #include <cxxtest/TestSuite.h> -#include <boost/scoped_ptr.hpp> #include <gmock/gmock.h> class NDRandomNumberGeneratorTest : public CxxTest::TestSuite { diff --git a/Framework/MDAlgorithms/CMakeLists.txt b/Framework/MDAlgorithms/CMakeLists.txt index f35029deb15a40a29f217bfb3a328d1b448de380..2466834c2b39827ea16971142a44ea2db31c0339 100644 --- a/Framework/MDAlgorithms/CMakeLists.txt +++ b/Framework/MDAlgorithms/CMakeLists.txt @@ -107,6 +107,7 @@ set ( SRC_FILES src/Quantification/ResolutionConvolvedCrossSection.cpp src/Quantification/SimulateResolutionConvolvedModel.cpp src/QueryMDWorkspace.cpp + src/RecalculateTrajectoriesExtents.cpp src/ReflectometryTransformKiKf.cpp src/ReflectometryTransformP.cpp src/ReflectometryTransformQxQz.cpp @@ -238,6 +239,7 @@ set ( INC_FILES inc/MantidMDAlgorithms/Quantification/ResolutionConvolvedCrossSection.h inc/MantidMDAlgorithms/Quantification/SimulateResolutionConvolvedModel.h inc/MantidMDAlgorithms/QueryMDWorkspace.h + inc/MantidMDAlgorithms/RecalculateTrajectoriesExtents.h inc/MantidMDAlgorithms/ReflectometryTransformKiKf.h inc/MantidMDAlgorithms/ReflectometryTransformP.h inc/MantidMDAlgorithms/ReflectometryTransformQxQz.h @@ -354,6 +356,7 @@ set ( TEST_FILES PowerMDTest.h PreprocessDetectorsToMDTest.h QueryMDWorkspaceTest.h + RecalculateTrajectoriesExtentsTest.h ReflectometryTransformKiKfTest.h ReflectometryTransformPTest.h ReflectometryTransformQxQzTest.h diff --git a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/RecalculateTrajectoriesExtents.h b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/RecalculateTrajectoriesExtents.h new file mode 100644 index 0000000000000000000000000000000000000000..9cee4af232a9d068cd7fba0af13aeac83cad9fee --- /dev/null +++ b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/RecalculateTrajectoriesExtents.h @@ -0,0 +1,38 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_MDALGORITHMS_RECALCULATETRAJECTORIESEXTENTS_H_ +#define MANTID_MDALGORITHMS_RECALCULATETRAJECTORIESEXTENTS_H_ + +#include "MantidAPI/Algorithm.h" +#include "MantidMDAlgorithms/DllConfig.h" + +namespace Mantid { +namespace MDAlgorithms { + +/** RecalculateTrajectoriesExtents : + */ +class MANTID_MDALGORITHMS_DLL RecalculateTrajectoriesExtents + : public API::Algorithm { +public: + const std::string name() const override; + int version() const override; + const std::string category() const override; + const std::string summary() const override; + const std::vector<std::string> seeAlso() const override { + return {"CropWorkspaceForMDNorm", "MDNormSCD", "MDNormDirectSC"}; + } + +private: + void init() override; + void exec() override; + std::map<std::string, std::string> validateInputs() override final; +}; + +} // namespace MDAlgorithms +} // namespace Mantid + +#endif /* MANTID_MDALGORITHMS_RECALCULATETRAJECTORIESEXTENTS_H_ */ diff --git a/Framework/MDAlgorithms/src/IntegrateMDHistoWorkspace.cpp b/Framework/MDAlgorithms/src/IntegrateMDHistoWorkspace.cpp index 29003d694df2d8ba87c66ebf414340bbbba4150b..4a18a25597e557728b5b0c7ee0fd0adb1f15c04b 100644 --- a/Framework/MDAlgorithms/src/IntegrateMDHistoWorkspace.cpp +++ b/Framework/MDAlgorithms/src/IntegrateMDHistoWorkspace.cpp @@ -24,7 +24,6 @@ #include <utility> #include <boost/make_shared.hpp> -#include <boost/scoped_ptr.hpp> using namespace Mantid::API; using namespace Mantid::Kernel; diff --git a/Framework/MDAlgorithms/src/RecalculateTrajectoriesExtents.cpp b/Framework/MDAlgorithms/src/RecalculateTrajectoriesExtents.cpp new file mode 100644 index 0000000000000000000000000000000000000000..088bcef735b74d22f4bb4a154c72d9b4a18ee255 --- /dev/null +++ b/Framework/MDAlgorithms/src/RecalculateTrajectoriesExtents.cpp @@ -0,0 +1,370 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + + +#include "MantidMDAlgorithms/RecalculateTrajectoriesExtents.h" +#include "MantidAPI/IMDEventWorkspace.h" +#include "MantidAPI/Run.h" +#include "MantidAPI/SpectrumInfo.h" +#include "MantidGeometry/Instrument.h" +#include "MantidGeometry/MDGeometry/QSample.h" +#include "MantidKernel/ArrayProperty.h" +#include "MantidKernel/ConfigService.h" +#include "MantidKernel/PhysicalConstants.h" +#include "MantidKernel/TimeSeriesProperty.h" + +namespace Mantid { +namespace MDAlgorithms { +using namespace Mantid::Kernel; +using namespace Mantid::API; +using VectorDoubleProperty = Kernel::PropertyWithValue<std::vector<double>>; + +// Register the algorithm into the AlgorithmFactory +DECLARE_ALGORITHM(RecalculateTrajectoriesExtents) + +//---------------------------------------------------------------------------------------------- + +/// Algorithms name for identification. @see Algorithm::name +const std::string RecalculateTrajectoriesExtents::name() const { + return "RecalculateTrajectoriesExtents"; +} + +/// Algorithm's version for identification. @see Algorithm::version +int RecalculateTrajectoriesExtents::version() const { return 1; } + +/// Algorithm's category for identification. @see Algorithm::category +const std::string RecalculateTrajectoriesExtents::category() const { + return "MDAlgorithms\\Normalisation"; +} + +/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary +const std::string RecalculateTrajectoriesExtents::summary() const { + return "Recalculates trajectory limits set by CropWorkspaceForMDNorm"; +} + +//---------------------------------------------------------------------------------------------- +/** Initialize the algorithm's properties. + */ +void RecalculateTrajectoriesExtents::init() { + declareProperty(make_unique<WorkspaceProperty<IMDEventWorkspace>>( + "InputWorkspace", "", Direction::Input), + "An input MDEventWorkspace. Must be in Q_sample frame."); + declareProperty(make_unique<WorkspaceProperty<IMDEventWorkspace>>( + "OutputWorkspace", "", Direction::Output), + "Copy of the input MDEventWorkspace with the corrected " + "trajectory extents."); +} + +/// Validate the input workspace @see Algorithm::validateInputs +std::map<std::string, std::string> +RecalculateTrajectoriesExtents::validateInputs() { + std::map<std::string, std::string> errorMessage; + + // Check for input workspace frame + Mantid::API::IMDEventWorkspace_sptr inputWS = + this->getProperty("InputWorkspace"); + if (inputWS->getNumDims() < 3) { + errorMessage.emplace("InputWorkspace", + "The input workspace must be at least 3D"); + } else { + for (size_t i = 0; i < 3; i++) { + if (inputWS->getDimension(i)->getMDFrame().name() != + Mantid::Geometry::QSample::QSampleName) { + errorMessage.emplace("InputWorkspace", + "The input workspace must be in Q_sample"); + } + } + } + // Check for property MDNorm_low and MDNorm_high + size_t nExperimentInfos = inputWS->getNumExperimentInfo(); + if (nExperimentInfos == 0) { + errorMessage.emplace("InputWorkspace", + "There must be at least one experiment info"); + } else { + for (size_t iExpInfo = 0; iExpInfo < nExperimentInfos; iExpInfo++) { + auto ¤tExptInfo = + *(inputWS->getExperimentInfo(static_cast<uint16_t>(iExpInfo))); + if (!currentExptInfo.run().hasProperty("MDNorm_low")) { + errorMessage.emplace("InputWorkspace", "Missing MDNorm_low log. Please " + "use CropWorkspaceForMDNorm " + "before converting to MD"); + } + if (!currentExptInfo.run().hasProperty("MDNorm_high")) { + errorMessage.emplace("InputWorkspace", + "Missing MDNorm_high log. Please use " + "CropWorkspaceForMDNorm before converting to MD"); + } + } + } + return errorMessage; +} + +//---------------------------------------------------------------------------------------------- +/** Execute the algorithm. + */ +void RecalculateTrajectoriesExtents::exec() { + IMDEventWorkspace_sptr inWS = getProperty("InputWorkspace"); + IMDEventWorkspace_sptr outWS = getProperty("OutputWorkspace"); + + // If input and output workspaces are not the same, create a new workspace for + // the output + if (outWS != inWS) { + outWS = inWS->clone(); + } + + // check if using diffraction or direct inelastic + bool diffraction(true); + double Ei(0.0); + if (outWS->getNumDims() > 3) { + if (outWS->getDimension(3)->getMDFrame().name() == "DeltaE") { + diffraction = false; + if (outWS->getExperimentInfo(0)->run().hasProperty("Ei")) { + Property *eiprop = outWS->getExperimentInfo(0)->run().getProperty("Ei"); + Ei = boost::lexical_cast<double>(eiprop->value()); + } else { + throw std::runtime_error( + "Workspace contains energy transfer axis, but no Ei." + "The MD normalization workflow is not implemented for " + "indirect geometry"); + } + } + } + + const double energyToK = 8.0 * M_PI * M_PI * PhysicalConstants::NeutronMass * + PhysicalConstants::meV * 1e-20 / + (PhysicalConstants::h * PhysicalConstants::h); + + auto convention = Kernel::ConfigService::Instance().getString("Q.convention"); + // get limits for all dimensions + double qxmin = outWS->getDimension(0)->getMinimum(); + double qxmax = outWS->getDimension(0)->getMaximum(); + double qymin = outWS->getDimension(1)->getMinimum(); + double qymax = outWS->getDimension(1)->getMaximum(); + double qzmin = outWS->getDimension(2)->getMinimum(); + double qzmax = outWS->getDimension(2)->getMaximum(); + double dEmin(0.0), dEmax(0.0); + size_t nqedims = 3; + if (!diffraction) { + nqedims = 4; + dEmin = outWS->getDimension(3)->getMinimum(); + dEmax = outWS->getDimension(3)->getMaximum(); + } + std::vector<double> otherDimsMin, otherDimsMax; + std::vector<std::string> otherDimsNames; + for (size_t i = nqedims; i < outWS->getNumDims(); i++) { + otherDimsMin.emplace_back(outWS->getDimension(i)->getMinimum()); + otherDimsMax.emplace_back(outWS->getDimension(i)->getMaximum()); + otherDimsNames.emplace_back(outWS->getDimension(i)->getName()); + } + + // Loop over experiment infos + size_t nExperimentInfos = outWS->getNumExperimentInfo(); + if (nExperimentInfos > 1) { + g_log.warning("More than one experiment info. On merged workspaces, the " + "limits recalculations might be wrong"); + } + + for (size_t iExpInfo = 0; iExpInfo < nExperimentInfos; iExpInfo++) { + auto ¤tExptInfo = + *(outWS->getExperimentInfo(static_cast<uint16_t>(iExpInfo))); + + const auto &spectrumInfo = currentExptInfo.spectrumInfo(); + const int64_t nspectra = static_cast<int64_t>(spectrumInfo.size()); + std::vector<double> lowValues, highValues; + + auto *lowValuesLog = dynamic_cast<VectorDoubleProperty *>( + currentExptInfo.getLog("MDNorm_low")); + lowValues = (*lowValuesLog)(); + + auto *highValuesLog = dynamic_cast<VectorDoubleProperty *>( + currentExptInfo.getLog("MDNorm_high")); + highValues = (*highValuesLog)(); + + // deal with other dimensions first + bool zeroWeights(false); + for (size_t iOtherDims = 0; iOtherDims < otherDimsNames.size(); + iOtherDims++) { + // check other dimensions. there might be no events, but if the first log + // value is not within limits, the weight should be zero as well + auto *otherDimsLog = dynamic_cast<Kernel::TimeSeriesProperty<double> *>( + currentExptInfo.run().getProperty(otherDimsNames[iOtherDims])); + if ((otherDimsLog->firstValue() < otherDimsMin[iOtherDims]) || + (otherDimsLog->firstValue() > otherDimsMax[iOtherDims])) { + zeroWeights = true; + g_log.warning() << "In experimentInfo " << iExpInfo << ", log " + << otherDimsNames[iOtherDims] << " is outside limits\n"; + continue; + } + } + if (zeroWeights) { + highValues = lowValues; + } else { + auto source = currentExptInfo.getInstrument()->getSource()->getPos(); + auto sample = currentExptInfo.getInstrument()->getSample()->getPos(); + auto beamDir = sample - source; + beamDir.normalize(); + auto gon = currentExptInfo.run().getGoniometerMatrix(); + gon.Invert(); + + // calculate limits in Q_sample + for (int64_t i = 0; i < nspectra; i++) { + if (!spectrumInfo.hasDetectors(i) || spectrumInfo.isMonitor(i) || + spectrumInfo.isMasked(i)) { + highValues[i] = lowValues[i]; + continue; + } + const auto &detector = spectrumInfo.detector(i); + double theta = detector.getTwoTheta(sample, beamDir); + double phi = detector.getPhi(); + V3D qout(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)), + qin(0., 0., 1.), qLabLow, qLabHigh, qSampleLow, qSampleHigh; + double kfmin, kfmax; + if (convention == "Crystallography") { + qout *= -1; + qin *= -1; + } + if (diffraction) { + // units of limits are momentum + kfmin = lowValues[i]; + kfmax = highValues[i]; + qLabLow = (qin - qout) * kfmin; + qLabHigh = (qin - qout) * kfmax; + } else { + if (dEmin > lowValues[i]) { + lowValues[i] = dEmin; + } + if (dEmax < highValues[i]) { + highValues[i] = dEmax; + } + if (lowValues[i] > Ei) { + lowValues[i] = Ei; + } + if (highValues[i] > Ei) { + highValues[i] = Ei; + } + + double ki = std::sqrt(energyToK * Ei); + kfmin = std::sqrt(energyToK * (Ei - lowValues[i])); + kfmax = std::sqrt(energyToK * (Ei - highValues[i])); + qLabLow = qin * ki - qout * kfmin; + qLabHigh = qin * ki - qout * kfmax; + } + qSampleLow = gon * qLabLow; + qSampleHigh = gon * qLabHigh; + // check intersection with the box + // completely outside the box -> no weight + if (((qSampleLow.X() < qxmin) && (qSampleHigh.X() < qxmin)) || + ((qSampleLow.X() > qxmax) && (qSampleHigh.X() > qxmax)) || + ((qSampleLow.Y() < qymin) && (qSampleHigh.Y() < qymin)) || + ((qSampleLow.Y() > qymax) && (qSampleHigh.Y() > qymax)) || + ((qSampleLow.Z() < qzmin) && (qSampleHigh.Z() < qzmin)) || + ((qSampleLow.Z() > qzmax) && (qSampleHigh.Z() > qzmax))) { + highValues[i] = lowValues[i]; + continue; + } + // either intersection or completely indide the box + if ((qxmin - qSampleLow.X()) * (qxmin - qSampleHigh.X()) < 0) { + double kfIntersection = (qxmin - qSampleLow.X()) * (kfmax - kfmin) / + (qSampleHigh.X() - qSampleLow.X()) + + kfmin; + if (!diffraction) { + kfIntersection = Ei - kfIntersection * kfIntersection / energyToK; + } + if ((qSampleLow.X() < qxmin) && (lowValues[i] < kfIntersection)) { + lowValues[i] = kfIntersection; + } + if ((qSampleHigh.X() < qxmin) && (highValues[i] > kfIntersection)) { + highValues[i] = kfIntersection; + } + } + + if ((qxmax - qSampleLow.X()) * (qxmax - qSampleHigh.X()) < 0) { + double kfIntersection = (qxmax - qSampleLow.X()) * (kfmax - kfmin) / + (qSampleHigh.X() - qSampleLow.X()) + + kfmin; + if (!diffraction) { + kfIntersection = Ei - kfIntersection * kfIntersection / energyToK; + } + if ((qSampleLow.X() > qxmax) && (lowValues[i] < kfIntersection)) { + lowValues[i] = kfIntersection; + } + if ((qSampleHigh.X() > qxmax) && (highValues[i] > kfIntersection)) { + highValues[i] = kfIntersection; + } + } + + if ((qymin - qSampleLow.Y()) * (qymin - qSampleHigh.Y()) < 0) { + double kfIntersection = (qymin - qSampleLow.Y()) * (kfmax - kfmin) / + (qSampleHigh.Y() - qSampleLow.Y()) + + kfmin; + if (!diffraction) { + kfIntersection = Ei - kfIntersection * kfIntersection / energyToK; + } + if ((qSampleLow.Y() < qymin) && (lowValues[i] < kfIntersection)) { + lowValues[i] = kfIntersection; + } + if ((qSampleHigh.Y() < qymin) && (highValues[i] > kfIntersection)) { + highValues[i] = kfIntersection; + } + } + + if ((qymax - qSampleLow.Y()) * (qymax - qSampleHigh.Y()) < 0) { + double kfIntersection = (qymax - qSampleLow.Y()) * (kfmax - kfmin) / + (qSampleHigh.Y() - qSampleLow.Y()) + + kfmin; + if (!diffraction) { + kfIntersection = Ei - kfIntersection * kfIntersection / energyToK; + } + if ((qSampleLow.Y() > qymax) && (lowValues[i] < kfIntersection)) { + lowValues[i] = kfIntersection; + } + if ((qSampleHigh.Y() > qymax) && (highValues[i] > kfIntersection)) { + highValues[i] = kfIntersection; + } + } + + if ((qzmin - qSampleLow.Z()) * (qzmin - qSampleHigh.Z()) < 0) { + double kfIntersection = (qzmin - qSampleLow.Z()) * (kfmax - kfmin) / + (qSampleHigh.Z() - qSampleLow.Z()) + + kfmin; + if (!diffraction) { + kfIntersection = Ei - kfIntersection * kfIntersection / energyToK; + } + if ((qSampleLow.Z() < qzmin) && (lowValues[i] < kfIntersection)) { + lowValues[i] = kfIntersection; + } + if ((qSampleHigh.Z() < qzmin) && (highValues[i] > kfIntersection)) { + highValues[i] = kfIntersection; + } + } + + if ((qzmax - qSampleLow.Z()) * (qzmax - qSampleHigh.Z()) < 0) { + double kfIntersection = (qzmax - qSampleLow.Z()) * (kfmax - kfmin) / + (qSampleHigh.Z() - qSampleLow.Z()) + + kfmin; + if (!diffraction) { + kfIntersection = Ei - kfIntersection * kfIntersection / energyToK; + } + if ((qSampleLow.Z() > qzmax) && (lowValues[i] < kfIntersection)) { + lowValues[i] = kfIntersection; + } + if ((qSampleHigh.Z() > qzmax) && (highValues[i] > kfIntersection)) { + highValues[i] = kfIntersection; + } + } + } // end loop over spectra + } + + // set the new values for the MDNorm_low and MDNorm_high + currentExptInfo.mutableRun().addProperty("MDNorm_low", lowValues, true); + currentExptInfo.mutableRun().addProperty("MDNorm_high", highValues, true); + } + + setProperty("OutputWorkspace", outWS); +} + +} // namespace MDAlgorithms +} // namespace Mantid diff --git a/Framework/MDAlgorithms/src/SmoothMD.cpp b/Framework/MDAlgorithms/src/SmoothMD.cpp index b8d633cfb39c77f82fa62bf4c02751cfadd98034..9c01020d61387a521318b512d1ced10fa50d74f2 100644 --- a/Framework/MDAlgorithms/src/SmoothMD.cpp +++ b/Framework/MDAlgorithms/src/SmoothMD.cpp @@ -20,7 +20,6 @@ #include <boost/bind.hpp> #include <boost/function.hpp> #include <boost/make_shared.hpp> -#include <boost/scoped_ptr.hpp> #include <boost/tuple/tuple.hpp> #include <limits> #include <map> diff --git a/Framework/MDAlgorithms/test/RecalculateTrajectoriesExtentsTest.h b/Framework/MDAlgorithms/test/RecalculateTrajectoriesExtentsTest.h new file mode 100644 index 0000000000000000000000000000000000000000..58102e261ceb9a8b771150cb687834a9d8ecaa85 --- /dev/null +++ b/Framework/MDAlgorithms/test/RecalculateTrajectoriesExtentsTest.h @@ -0,0 +1,141 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTID_MDALGORITHMS_RECALCULATETRAJECTORIESEXTENTSTEST_H_ +#define MANTID_MDALGORITHMS_RECALCULATETRAJECTORIESEXTENTSTEST_H_ + +#include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/IMDEventWorkspace.h" +#include "MantidAPI/Run.h" +#include "MantidAPI/SpectrumInfo.h" +#include "MantidGeometry/MDGeometry/QSample.h" +#include "MantidMDAlgorithms/CreateMDWorkspace.h" +#include "MantidMDAlgorithms/RecalculateTrajectoriesExtents.h" +#include "MantidTestHelpers/ComponentCreationHelper.h" +#include <cxxtest/TestSuite.h> + +using Mantid::MDAlgorithms::RecalculateTrajectoriesExtents; +using namespace Mantid::MDAlgorithms; +using namespace Mantid::Geometry; +using namespace Mantid::API; +using VectorDoubleProperty = + Mantid::Kernel::PropertyWithValue<std::vector<double>>; + +class RecalculateTrajectoriesExtentsTest : public CxxTest::TestSuite { +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static RecalculateTrajectoriesExtentsTest *createSuite() { + return new RecalculateTrajectoriesExtentsTest(); + } + static void destroySuite(RecalculateTrajectoriesExtentsTest *suite) { + delete suite; + } + + IMDEventWorkspace_sptr create_workspace(std::vector<double> extents, + std::string name) { + // ---- empty MDEW ---- + TS_ASSERT_EQUALS(extents.size(), 6) + CreateMDWorkspace algC; + algC.initialize(); + algC.setProperty("Dimensions", "3"); + algC.setProperty("Extents", extents); + std::string frames = Mantid::Geometry::QSample::QSampleName + "," + + Mantid::Geometry::QSample::QSampleName + "," + + Mantid::Geometry::QSample::QSampleName; + algC.setProperty("Frames", frames); + algC.setPropertyValue("Names", "x,y,z"); + algC.setPropertyValue("Units", "m,mm,um"); + algC.setPropertyValue("OutputWorkspace", name); + algC.execute(); + IMDEventWorkspace_sptr out = boost::dynamic_pointer_cast<IMDEventWorkspace>( + AnalysisDataService::Instance().retrieve(name)); + TS_ASSERT(out); + + std::vector<double> L2{1, 1, 1}, pol{0.1, 0.2, 0.3}, azi{0, 1, 2}; + Instrument_sptr inst = + ComponentCreationHelper::createCylInstrumentWithDetInGivenPositions( + L2, pol, azi); + inst->setName("Test"); + + ExperimentInfo_sptr ei = + ExperimentInfo_sptr(new Mantid::API::ExperimentInfo()); + ei->setInstrument(inst); + std::vector<double> high(3, 3), low(3, 1); + ei->mutableRun().addProperty("MDNorm_high", high); + ei->mutableRun().addProperty("MDNorm_low", low); + out->addExperimentInfo(ei); + TS_ASSERT_EQUALS(out->getNumExperimentInfo(), 1) + return out; + } + void test_Init() { + RecalculateTrajectoriesExtents alg; + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + } + + void do_test(std::string name, std::vector<double> extents) { + IMDEventWorkspace_sptr inputWS = create_workspace(extents, name); + + RecalculateTrajectoriesExtents alg; + // Don't put output in ADS by default + alg.setChild(true); + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspace", inputWS)); + TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("OutputWorkspace", name)); + TS_ASSERT_THROWS_NOTHING(alg.execute();); + TS_ASSERT(alg.isExecuted()); + + // Retrieve the workspace from the algorithm. The type here will probably + // need to change. It should be the type using in declareProperty for the + // "OutputWorkspace" type. We can't use auto as it's an implicit conversion. + IMDEventWorkspace_sptr outputWS = alg.getProperty("OutputWorkspace"); + TS_ASSERT(outputWS); + auto ei = outputWS->getExperimentInfo(0); + TS_ASSERT(ei); + auto *lowValuesLog = + dynamic_cast<VectorDoubleProperty *>(ei->getLog("MDNorm_low")); + auto *highValuesLog = + dynamic_cast<VectorDoubleProperty *>(ei->getLog("MDNorm_high")); + const auto &spectrumInfo = ei->spectrumInfo(); + for (size_t i = 0; i < 3; i++) { + const auto &detector = spectrumInfo.detector(i); + double theta = detector.getTwoTheta(Mantid::Kernel::V3D(0, 0, 0), + Mantid::Kernel::V3D(0, 0, 1)); + double phi = detector.getPhi(); + double Qx = -sin(theta) * cos(phi); + double lamMin = (*lowValuesLog)()[i]; + double lamMax = ((*highValuesLog)()[i]); + // correct answer if the trajectory is all outside the box + // otherwise both ends must be in or on the box + if (lamMin != lamMax) { + // float has 7 digits of precision + TS_ASSERT_LESS_THAN_EQUALS((Qx * lamMin - extents[0]) * + (Qx * lamMin - extents[1]), + 1e-7 * (extents[1] - extents[0])) + TS_ASSERT_LESS_THAN_EQUALS((Qx * lamMax - extents[0]) * + (Qx * lamMax - extents[1]), + 1e-7 * (extents[1] - extents[0])) + } + } + AnalysisDataService::Instance().remove(name); + } + + void test_exec_no_cut() { + std::vector<double> extents{-10, 10, -10, 10, -10, 10}; + std::string name("RecalculateTrajectoriesExtents_no_cut_test"); + do_test(name, extents); + } + + void test_exec_cut() { + std::vector<double> extents{-0.2, 10, -10, 10, -10, 10}; + std::string name("RecalculateTrajectoriesExtents_cut_test"); + do_test(name, extents); + } +}; + +#endif /* MANTID_MDALGORITHMS_RECALCULATETRAJECTORIESEXTENTSTEST_H_ */ diff --git a/Framework/Muon/inc/MantidMuon/MuonAlgorithmHelper.h b/Framework/Muon/inc/MantidMuon/MuonAlgorithmHelper.h index ad0fd6d09f081d4875c9b6d831dc2d250f97f48b..f076ee23cd75da124d4c8c91fb821ed256744b33 100644 --- a/Framework/Muon/inc/MantidMuon/MuonAlgorithmHelper.h +++ b/Framework/Muon/inc/MantidMuon/MuonAlgorithmHelper.h @@ -123,6 +123,22 @@ DLLExport bool checkValidPair(const std::string &name1, /// Check whether a group or pair name is valid DLLExport bool checkValidGroupPairName(const std::string &name); + +DLLExport Mantid::API::MatrixWorkspace_sptr +sumPeriods(const Mantid::API::WorkspaceGroup_sptr &inputWS, + const std::vector<int> &periodsToSum); + +DLLExport Mantid::API::MatrixWorkspace_sptr +subtractWorkspaces(const Mantid::API::MatrixWorkspace_sptr &lhs, + const Mantid::API::MatrixWorkspace_sptr &rhs); + +DLLExport Mantid::API::MatrixWorkspace_sptr +extractSpectrum(const Mantid::API::Workspace_sptr &inputWS, const int index); + +DLLExport void addSampleLog(Mantid::API::MatrixWorkspace_sptr workspace, + const std::string &logName, + const std::string &logValue); + // ///// Saves grouping to the XML file specified // DLLExport std::string groupingToXML(const Mantid::API::Grouping &grouping); diff --git a/Framework/Muon/src/EstimateMuonAsymmetryFromCounts.cpp b/Framework/Muon/src/EstimateMuonAsymmetryFromCounts.cpp index fd5831ba40c3d13c081deae67a40e212fc2aa967..138d69f8464cf80a18e8521bf487873da6c98531 100644 --- a/Framework/Muon/src/EstimateMuonAsymmetryFromCounts.cpp +++ b/Framework/Muon/src/EstimateMuonAsymmetryFromCounts.cpp @@ -15,9 +15,9 @@ #include "MantidAPI/Run.h" #include "MantidAPI/WorkspaceFactory.h" #include "MantidAPI/Workspace_fwd.h" - #include "MantidKernel/ArrayProperty.h" #include "MantidKernel/PhysicalConstants.h" +#include "MantidMuon/MuonAlgorithmHelper.h" #include <cmath> #include <numeric> @@ -74,7 +74,8 @@ void EstimateMuonAsymmetryFromCounts::init() { declareProperty( make_unique<API::WorkspaceProperty<API::ITableWorkspace>>( - "NormalizationTable", "", Direction::InOut), + "NormalizationTable", "", Direction::InOut, + API::PropertyMode::Optional), "Name of the table containing the normalizations for the asymmetries."); } @@ -213,11 +214,21 @@ void EstimateMuonAsymmetryFromCounts::exec() { } // update table Mantid::API::ITableWorkspace_sptr table = getProperty("NormalizationTable"); - updateNormalizationTable(table, wsNames, norm, methods); - setProperty("NormalizationTable", table); + if (table) { + updateNormalizationTable(table, wsNames, norm, methods); + setProperty("NormalizationTable", table); + } // Update Y axis units outputWS->setYUnit("Asymmetry"); + std::string normString = std::accumulate( + norm.begin() + 1, norm.end(), std::to_string(norm[0]), + [](const std::string ¤tString, double valueToAppend) { + return currentString + ',' + std::to_string(valueToAppend); + }); + MuonAlgorithmHelper::addSampleLog(outputWS, "analysis_asymmetry_norm", + normString); + setProperty("OutputWorkspace", outputWS); } } // namespace Algorithms diff --git a/Framework/Muon/src/MuonAlgorithmHelper.cpp b/Framework/Muon/src/MuonAlgorithmHelper.cpp index 75e3a1cca2f1543b3d78a9a909808643b7075676..498a1e5fb1db175f0614e04a18ce6432da17f6ba 100644 --- a/Framework/Muon/src/MuonAlgorithmHelper.cpp +++ b/Framework/Muon/src/MuonAlgorithmHelper.cpp @@ -522,5 +522,90 @@ bool checkValidGroupPairName(const std::string &name) { return true; } +/** + * Sums the specified periods of the input workspace group + * @param periodsToSum :: [input] List of period indexes (1-based) to be summed + * @returns Workspace containing the sum + */ +MatrixWorkspace_sptr sumPeriods(const WorkspaceGroup_sptr &inputWS, + const std::vector<int> &periodsToSum) { + MatrixWorkspace_sptr outWS; + if (!periodsToSum.empty()) { + auto LHSWorkspace = inputWS->getItem(periodsToSum[0] - 1); + outWS = boost::dynamic_pointer_cast<MatrixWorkspace>(LHSWorkspace); + if (outWS != nullptr && periodsToSum.size() > 1) { + int numPeriods = static_cast<int>(periodsToSum.size()); + for (int i = 1; i < numPeriods; i++) { + auto RHSWorkspace = inputWS->getItem(periodsToSum[i] - 1); + IAlgorithm_sptr alg = AlgorithmManager::Instance().create("Plus"); + alg->setChild(true); + alg->setRethrows(true); + alg->setProperty("LHSWorkspace", outWS); + alg->setProperty("RHSWorkspace", RHSWorkspace); + alg->setProperty("OutputWorkspace", "__NotUsed__"); + alg->execute(); + outWS = alg->getProperty("OutputWorkspace"); + } + } + } + return outWS; +} + +/** + * Subtracts one workspace from another: lhs - rhs. + * @param lhs :: [input] Workspace on LHS of subtraction + * @param rhs :: [input] Workspace on RHS of subtraction + * @returns Result of the subtraction + */ +MatrixWorkspace_sptr subtractWorkspaces(const MatrixWorkspace_sptr &lhs, + const MatrixWorkspace_sptr &rhs) { + MatrixWorkspace_sptr outWS; + if (lhs && rhs) { + IAlgorithm_sptr alg = AlgorithmManager::Instance().create("Minus"); + alg->setChild(true); + alg->setRethrows(true); + alg->setProperty("LHSWorkspace", lhs); + alg->setProperty("RHSWorkspace", rhs); + alg->setProperty("OutputWorkspace", "__NotUsed__"); + alg->execute(); + outWS = alg->getProperty("OutputWorkspace"); + } + return outWS; +} + +/** + * Extracts a single spectrum from the given workspace. + * @param inputWS :: [input] Workspace to extract spectrum from + * @param index :: [input] Index of spectrum to extract + * @returns Result of the extraction + */ +MatrixWorkspace_sptr extractSpectrum(const Workspace_sptr &inputWS, + const int index) { + MatrixWorkspace_sptr outWS; + if (inputWS) { + IAlgorithm_sptr alg = + AlgorithmManager::Instance().create("ExtractSingleSpectrum"); + alg->setChild(true); + alg->setRethrows(true); + alg->setProperty("InputWorkspace", inputWS); + alg->setProperty("WorkspaceIndex", index); + alg->setProperty("OutputWorkspace", "__NotUsed__"); + alg->execute(); + outWS = alg->getProperty("OutputWorkspace"); + } + return outWS; +} + +void addSampleLog(MatrixWorkspace_sptr workspace, const std::string &logName, + const std::string &logValue) { + IAlgorithm_sptr alg = AlgorithmManager::Instance().create("AddSampleLog"); + alg->setChild(true); + alg->setRethrows(true); + alg->setProperty("Workspace", workspace); + alg->setProperty("LogName", logName); + alg->setProperty("LogText", logValue); + alg->execute(); +} + } // namespace MuonAlgorithmHelper } // namespace Mantid diff --git a/Framework/Muon/src/MuonAsymmetryHelper.cpp b/Framework/Muon/src/MuonAsymmetryHelper.cpp index b70b054af9f36ca465306e579adde7a14fa95753..eb368c452f8a0ca1b51937f94603a10929062795 100644 --- a/Framework/Muon/src/MuonAsymmetryHelper.cpp +++ b/Framework/Muon/src/MuonAsymmetryHelper.cpp @@ -12,6 +12,7 @@ #include "MantidAPI/MatrixWorkspace.h" #include "MantidAPI/Progress.h" #include "MantidAPI/Run.h" +#include "MantidAPI/WorkspaceFactory.h" #include "MantidAPI/Workspace_fwd.h" #include "MantidAPI/AnalysisDataService.h" diff --git a/Framework/PythonInterface/core/inc/MantidPythonInterface/core/CallMethod.h b/Framework/PythonInterface/core/inc/MantidPythonInterface/core/CallMethod.h index 5b712b2e972457e8f9da211f477bfaeab8c096bc..ec7a815b05a7571154d7a54fd889234551584b7b 100644 --- a/Framework/PythonInterface/core/inc/MantidPythonInterface/core/CallMethod.h +++ b/Framework/PythonInterface/core/inc/MantidPythonInterface/core/CallMethod.h @@ -41,13 +41,7 @@ ReturnType callMethodImpl(PyObject *obj, const char *methodName, return boost::python::call_method<ReturnType, Args...>(obj, methodName, args...); } catch (boost::python::error_already_set &) { - PyObject *exception = PyErr_Occurred(); - assert(exception); - if (PyErr_GivenExceptionMatches(exception, PyExc_RuntimeError)) { - throw PythonRuntimeError(); - } else { - throw PythonException(); - } + throw PythonException(); } } } // namespace detail @@ -68,6 +62,23 @@ ReturnType callMethodNoCheck(PyObject *obj, const char *methodName, return detail::callMethodImpl<ReturnType, Args...>(obj, methodName, args...); } +/** + * Wrapper around boost::python::call_method to acquire GIL for duration + * of call. If the call raises a Python error then this is translated to + * a C++ exception object inheriting from std::exception or std::runtime_error + * depending on the type of Python error. Overload for boost::python::object + * @param obj Reference to boost.python.object wrapper + * @param methodName Name of the method call + * @param args A list of arguments to forward to call_method + */ +template <typename ReturnType, typename... Args> +ReturnType callMethodNoCheck(const boost::python::object &obj, + const char *methodName, const Args &... args) { + GlobalInterpreterLock gil; + return detail::callMethodImpl<ReturnType, Args...>(obj.ptr(), methodName, + args...); +} + /** * Wrapper around boost::python::call_method to acquire GIL for duration * of call. If the attribute does not exist then an UndefinedAttributeError diff --git a/Framework/PythonInterface/core/inc/MantidPythonInterface/core/ErrorHandling.h b/Framework/PythonInterface/core/inc/MantidPythonInterface/core/ErrorHandling.h index f7addec0c2f8ed33c7ee53659b423af11adc8561..b56894170f9e0ecfa96309045095c8238a0eed9d 100644 --- a/Framework/PythonInterface/core/inc/MantidPythonInterface/core/ErrorHandling.h +++ b/Framework/PythonInterface/core/inc/MantidPythonInterface/core/ErrorHandling.h @@ -13,7 +13,7 @@ /** * This file defines error handling code that transforms - * a Python error state to C++ exceptions. + * a Python error state to a C++ exception. */ namespace Mantid { namespace PythonInterface { @@ -22,22 +22,10 @@ namespace PythonInterface { * Exception type that captures the current Python error state * as a generic C++ exception for any general Python exception */ -class MANTID_PYTHONINTERFACE_CORE_DLL PythonException : public std::exception { -public: - PythonException(bool withTrace = true); - - const char *what() const noexcept override { return m_msg.c_str(); } - -private: - std::string m_msg; -}; - -/// Exception type that captures the current Python error state -/// as a C++ std::runtime exception -class MANTID_PYTHONINTERFACE_CORE_DLL PythonRuntimeError +class MANTID_PYTHONINTERFACE_CORE_DLL PythonException : public std::runtime_error { public: - PythonRuntimeError(bool withTrace = true); + PythonException(bool withTrace = true); }; } // namespace PythonInterface diff --git a/Framework/PythonInterface/core/src/ErrorHandling.cpp b/Framework/PythonInterface/core/src/ErrorHandling.cpp index 129240f51c48fc3c7fcc8666cd36f6d1f6deb0fb..6a4573bab1543a686ed425966ea669f1c8485862 100644 --- a/Framework/PythonInterface/core/src/ErrorHandling.cpp +++ b/Framework/PythonInterface/core/src/ErrorHandling.cpp @@ -97,18 +97,6 @@ std::string exceptionToString(bool withTrace) { * @param withTrace If true, include the full traceback in the message */ PythonException::PythonException(bool withTrace) - : std::exception(), m_msg(exceptionToString(withTrace)) {} - -// ----------------------------------------------------------------------------- -// PythonRuntimeError -// ----------------------------------------------------------------------------- - -/** - * Construct an exception object where the message is populated from the - * current Python exception state. - * @param withTrace If true, include the full traceback in the message - */ -PythonRuntimeError::PythonRuntimeError(bool withTrace) : std::runtime_error(exceptionToString(withTrace)) {} } // namespace PythonInterface diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/api/DetectorInfoPythonIterator.h b/Framework/PythonInterface/inc/MantidPythonInterface/api/DetectorInfoPythonIterator.h index 7ff41c8aa63e276e9f3ce393a719fcd9f0d8be33..ee194aaaa3ef3a5aa5332263cead2f8c3fe64e1a 100644 --- a/Framework/PythonInterface/inc/MantidPythonInterface/api/DetectorInfoPythonIterator.h +++ b/Framework/PythonInterface/inc/MantidPythonInterface/api/DetectorInfoPythonIterator.h @@ -38,11 +38,11 @@ without the need for indexes. class DetectorInfoPythonIterator { public: - explicit DetectorInfoPythonIterator(const DetectorInfo &detectorInfo) + explicit DetectorInfoPythonIterator(DetectorInfo &detectorInfo) : m_begin(detectorInfo.begin()), m_end(detectorInfo.end()), m_firstOrDone(true) {} - const DetectorInfoItem &next() { + const DetectorInfoItem<DetectorInfo> &next() { if (!m_firstOrDone) ++m_begin; else @@ -55,8 +55,8 @@ public: } private: - DetectorInfoIterator m_begin; - DetectorInfoIterator m_end; + DetectorInfoIterator<DetectorInfo> m_begin; + DetectorInfoIterator<DetectorInfo> m_end; bool m_firstOrDone; }; diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/api/SpectrumInfoPythonIterator.h b/Framework/PythonInterface/inc/MantidPythonInterface/api/SpectrumInfoPythonIterator.h index ad1f034fde7e02b36d3727895160c92efe05b804..25000e11500aef01edbae39b91abb1fb5076e204 100644 --- a/Framework/PythonInterface/inc/MantidPythonInterface/api/SpectrumInfoPythonIterator.h +++ b/Framework/PythonInterface/inc/MantidPythonInterface/api/SpectrumInfoPythonIterator.h @@ -42,11 +42,11 @@ without the need for indexes. class SpectrumInfoPythonIterator { public: - explicit SpectrumInfoPythonIterator(const SpectrumInfo &spectrumInfo) + explicit SpectrumInfoPythonIterator(SpectrumInfo &spectrumInfo) : m_begin(spectrumInfo.begin()), m_end(spectrumInfo.end()), m_firstOrDone(true) {} - const SpectrumInfoItem &next() { + const SpectrumInfoItem<SpectrumInfo> &next() { if (!m_firstOrDone) ++m_begin; else @@ -59,8 +59,8 @@ public: } private: - SpectrumInfoIterator m_begin; - SpectrumInfoIterator m_end; + SpectrumInfoIterator<SpectrumInfo> m_begin; + SpectrumInfoIterator<SpectrumInfo> m_end; bool m_firstOrDone; }; diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Converters/PyObjectToString.h b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Converters/PyObjectToString.h index cd73c456ee8da47a911dbf4a5db13996b35a094b..774cfcd81ce1860ef81b1ff9d3ddcc91832a4827 100644 --- a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Converters/PyObjectToString.h +++ b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Converters/PyObjectToString.h @@ -20,6 +20,9 @@ namespace Converters { */ DLLExport std::string pyObjToStr(const boost::python::object &value); +/// Return true if the supplied object can be converted to a string. +DLLExport bool pyObjIsStr(const boost::python::object &value); + } // namespace Converters } // namespace PythonInterface } // namespace Mantid diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Policies/VectorToNumpy.h b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Policies/VectorToNumpy.h index 2001d95836cc8b288577051340a781cfa5a3ebb2..2591c86c37cb42e9373d235d5c8cfc54e927a8ed 100644 --- a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Policies/VectorToNumpy.h +++ b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/Policies/VectorToNumpy.h @@ -72,8 +72,7 @@ template <typename ConversionPolicy> struct VectorRefToNumpy { typename std::remove_reference<T>::type>::type; // MPL compile-time check that T is a reference to a std::vector using type = typename boost::mpl::if_c< - boost::mpl::and_<std::is_reference<T>, - is_std_vector<non_const_type>>::value, + is_std_vector<non_const_type>::value, VectorRefToNumpyImpl<non_const_type, ConversionPolicy>, VectorRefToNumpy_Requires_Reference_To_StdVector_Return_Type<T>>::type; }; @@ -96,13 +95,12 @@ template <typename VectorType> struct VectorToNumpyImpl { inline PyTypeObject const *get_pytype() const { return ndarrayType(); } }; -template <typename T> -struct VectorToNumpy_Requires_StdVector_Return_By_Value {}; +template <typename T> struct VectorToNumpy_Requires_StdVector_Return {}; } // namespace /** * Implements a return value policy that - * returns a numpy array from a function returning a std::vector by value + * returns a numpy array from a function returning a std::vector by ref or value * * It is only possible to clone these types since a wrapper would wrap temporary */ @@ -110,12 +108,13 @@ struct VectorToNumpy { // The boost::python framework calls return_value_policy::apply<T>::type template <class T> struct apply { // Typedef that removes any const from the type - using non_const_type = typename std::remove_const<T>::type; + using non_const_type = typename std::remove_const< + typename std::remove_reference<T>::type>::type; // MPL compile-time check that T is a std::vector using type = typename boost::mpl::if_c< is_std_vector<non_const_type>::value, VectorRefToNumpyImpl<non_const_type, Converters::Clone>, - VectorToNumpy_Requires_StdVector_Return_By_Value<T>>::type; + VectorToNumpy_Requires_StdVector_Return<T>>::type; }; }; } // namespace Policies diff --git a/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp b/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp index 1e74e44ffdd4dad47e4a0829c8fac4a27c8dcaf4..b965ffa6e014f86a3315f3eaedace9b52e655af1 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp @@ -233,6 +233,10 @@ void export_IFunction() { .def("getConstraints", &IFunction::writeConstraints, arg("self"), "Returns the list of current constraints as a string") + .def("setConstraintPenaltyFactor", &IFunction::setConstraintPenaltyFactor, + (arg("self"), arg("name"), arg("value")), + "Set the constraint penalty factor for named parameter") + .def("getNumberDomains", &IFunction::getNumberDomains, (arg("self")), "Get number of domains of a multi-domain function") diff --git a/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp b/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp index 3a938ebbfd1144762e67350b611f9f38da5d470e..eca9d6253fcb579db433020ace2f76db1d636a4e 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp @@ -370,7 +370,9 @@ void export_MatrixWorkspace() { "some subsequent algorithms may expect it to be " "monitor workspace later.") .def("clearMonitorWorkspace", &clearMonitorWorkspace, args("self"), - "Forget about monitor workspace, attached to the current workspace"); + "Forget about monitor workspace, attached to the current workspace") + .def("isCommonBins", &MatrixWorkspace::isCommonBins, + "Returns true if the workspace has common X bins."); RegisterWorkspacePtrToPython<MatrixWorkspace>(); } diff --git a/Framework/PythonInterface/mantid/api/src/Exports/SpectrumDefinition.cpp b/Framework/PythonInterface/mantid/api/src/Exports/SpectrumDefinition.cpp index 54175e42c25a7671b313315f1cf1106d9ba1da51..d4f21658fb579a6ae6625b5fe4fae7ade13c86f3 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/SpectrumDefinition.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/SpectrumDefinition.cpp @@ -26,6 +26,10 @@ void export_SpectrumDefinition() { "Returns the pair of detector index and time index at given index " "of spectrum definition.") + .def("__len__", &SpectrumDefinition::size, arg("self"), + "Returns the size of the SpectrumDefinition i.e. the number of " + "detectors for the spectrum.") + .def("size", &SpectrumDefinition::size, arg("self"), "Returns the size of the SpectrumDefinition i.e. the number of " "detectors for the spectrum.") diff --git a/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfo.cpp b/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfo.cpp index 2abd3e64f5bf6c59da5eaa7b0ac46dc9dd571924..4fa354cfd32dbade436ad3c9f764880670a3f9a7 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfo.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfo.cpp @@ -23,7 +23,7 @@ using Mantid::SpectrumDefinition; using namespace boost::python; // Helper method to make the python iterator -SpectrumInfoPythonIterator make_pyiterator(const SpectrumInfo &spectrumInfo) { +SpectrumInfoPythonIterator make_pyiterator(SpectrumInfo &spectrumInfo) { return SpectrumInfoPythonIterator(spectrumInfo); } diff --git a/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfoItem.cpp b/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfoItem.cpp index 098b2702608cb943babbd3f22e33dab8a93eef66..322e5d1706613e4580a3faf37871448151336905 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfoItem.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfoItem.cpp @@ -7,9 +7,11 @@ #include "MantidAPI/SpectrumInfoItem.h" #include "MantidKernel/V3D.h" +#include "MantidAPI/SpectrumInfo.h" #include <boost/python/class.hpp> #include <boost/python/module.hpp> +using Mantid::API::SpectrumInfo; using Mantid::API::SpectrumInfoItem; using Mantid::Kernel::V3D; using namespace boost::python; @@ -18,15 +20,20 @@ using namespace boost::python; void export_SpectrumInfoItem() { // Export to Python - class_<SpectrumInfoItem>("SpectrumInfoItem", no_init) - .add_property("isMonitor", &SpectrumInfoItem::isMonitor) - .add_property("isMasked", &SpectrumInfoItem::isMasked) - .add_property("twoTheta", &SpectrumInfoItem::twoTheta) - .add_property("signedTwoTheta", &SpectrumInfoItem::signedTwoTheta) - .add_property("l2", &SpectrumInfoItem::l2) - .add_property("hasUniqueDetector", &SpectrumInfoItem::hasUniqueDetector) - .add_property("spectrumDefinition", - make_function(&SpectrumInfoItem::spectrumDefinition, - return_internal_reference<>())) - .add_property("position", &SpectrumInfoItem::position); + class_<SpectrumInfoItem<SpectrumInfo>>("SpectrumInfoItem", no_init) + .add_property("isMonitor", &SpectrumInfoItem<SpectrumInfo>::isMonitor) + .add_property("isMasked", &SpectrumInfoItem<SpectrumInfo>::isMasked) + .add_property("twoTheta", &SpectrumInfoItem<SpectrumInfo>::twoTheta) + .add_property("signedTwoTheta", + &SpectrumInfoItem<SpectrumInfo>::signedTwoTheta) + .add_property("l2", &SpectrumInfoItem<SpectrumInfo>::l2) + .add_property("hasUniqueDetector", + &SpectrumInfoItem<SpectrumInfo>::hasUniqueDetector) + .add_property( + "spectrumDefinition", + make_function(&SpectrumInfoItem<SpectrumInfo>::spectrumDefinition, + return_internal_reference<>())) + .add_property("position", &SpectrumInfoItem<SpectrumInfo>::position) + .def("setMasked", &SpectrumInfoItem<SpectrumInfo>::setMasked, + (arg("self"), arg("masked")), "Set the mask flag for the spectrum"); } diff --git a/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfoIterator.cpp b/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfoIterator.cpp index 455f0075ef0cb71dc80169f0c76ce448f3a1f78b..3b49a932a7245ac0df652f0f76275d9d3555b6cb 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfoIterator.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfoIterator.cpp @@ -5,10 +5,12 @@ // & Institut Laue - Langevin // SPDX - License - Identifier: GPL - 3.0 + #include "MantidAPI/SpectrumInfoIterator.h" +#include "MantidAPI/SpectrumInfo.h" #include <boost/python/class.hpp> #include <boost/python/module.hpp> +using Mantid::API::SpectrumInfo; using Mantid::API::SpectrumInfoIterator; using namespace boost::python; @@ -16,5 +18,5 @@ using namespace boost::python; void export_SpectrumInfoIterator() { // Export to Python - class_<SpectrumInfoIterator>("SpectrumInfoIterator", no_init); + class_<SpectrumInfoIterator<SpectrumInfo>>("SpectrumInfoIterator", no_init); } diff --git a/Framework/PythonInterface/mantid/api/src/FitFunctions/IFunctionAdapter.cpp b/Framework/PythonInterface/mantid/api/src/FitFunctions/IFunctionAdapter.cpp index 183527dbc20b69fb9e2f0f9ca7a1512275de9bc4..230e4b1c2bed3a06e5022c12f8005deb5c16129d 100644 --- a/Framework/PythonInterface/mantid/api/src/FitFunctions/IFunctionAdapter.cpp +++ b/Framework/PythonInterface/mantid/api/src/FitFunctions/IFunctionAdapter.cpp @@ -328,7 +328,7 @@ void IFunctionAdapter::evaluateDerivative(API::Jacobian *out, PyObject_CallMethod(getSelf(), const_cast<char *>(m_derivName.c_str()), const_cast<char *>("(OO)"), xvals, jacobian); if (PyErr_Occurred()) - throw PythonRuntimeError(); + throw PythonException(); } } // namespace PythonInterface } // namespace Mantid diff --git a/Framework/PythonInterface/mantid/fitfunctions.py b/Framework/PythonInterface/mantid/fitfunctions.py index ee7dace2df3d09f2b705492176285343b479da54..55b3c78a001a3d3569b5c3d4ce737f16ffc2321b 100644 --- a/Framework/PythonInterface/mantid/fitfunctions.py +++ b/Framework/PythonInterface/mantid/fitfunctions.py @@ -23,7 +23,7 @@ class FunctionWrapper(object): return wrapper(fun, *args, **kwargs) return FunctionWrapper(fun, **kwargs) - def __init__ (self, name, **kwargs): + def __init__(self, name, **kwargs): """ Called when creating an instance diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfo.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfo.cpp index 2d45bb2894735dfddd6ffaebafbeb42b76457183..f348ac26520887c9dfb1adfeeaa6fc775b4fba08 100644 --- a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfo.cpp +++ b/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfo.cpp @@ -10,7 +10,8 @@ #include "MantidKernel/Quat.h" #include "MantidKernel/V3D.h" #include "MantidPythonInterface/api/DetectorInfoPythonIterator.h" - +#include "MantidPythonInterface/core/Converters/WrapWithNDArray.h" +#include "MantidPythonInterface/kernel/Policies/VectorToNumpy.h" #include <boost/iterator/iterator_facade.hpp> #include <boost/python/class.hpp> #include <boost/python/copy_const_reference.hpp> @@ -28,11 +29,17 @@ using Mantid::PythonInterface::DetectorInfoPythonIterator; using Mantid::Kernel::Quat; using Mantid::Kernel::V3D; using namespace boost::python; +using namespace Mantid::PythonInterface; +namespace { // Helper method to make the python iterator -DetectorInfoPythonIterator make_pyiterator(const DetectorInfo &detectorInfo) { +DetectorInfoPythonIterator make_pyiterator(DetectorInfo &detectorInfo) { return DetectorInfoPythonIterator(detectorInfo); } +/// return_value_policy for read-only numpy array +using return_readonly_numpy = + return_value_policy<Policies::VectorRefToNumpy<Converters::WrapReadOnly>>; +} // namespace // Export DetectorInfo void export_DetectorInfo() { @@ -96,5 +103,7 @@ void export_DetectorInfo() { .def("rotation", rotation, (arg("self"), arg("index")), "Returns the absolute rotation of the detector where the detector " - "is identified by 'index'."); + "is identified by 'index'.") + .def("detectorIDs", &DetectorInfo::detectorIDs, return_readonly_numpy(), + "Returns all detector ids sorted by detector index"); } diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoItem.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoItem.cpp index 15aab516630c737b93fbe207b3c0043aadcadeec..09445dbb303b113314cbe711cb0ce91842e914d3 100644 --- a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoItem.cpp +++ b/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoItem.cpp @@ -5,11 +5,14 @@ // & Institut Laue - Langevin // SPDX - License - Identifier: GPL - 3.0 + #include "MantidGeometry/Instrument/DetectorInfoItem.h" +#include "MantidGeometry/Instrument/DetectorInfo.h" +#include "MantidKernel/Quat.h" #include "MantidKernel/V3D.h" #include <boost/python/class.hpp> #include <boost/python/module.hpp> +using Mantid::Geometry::DetectorInfo; using Mantid::Geometry::DetectorInfoItem; using Mantid::Kernel::V3D; using namespace boost::python; @@ -18,10 +21,12 @@ using namespace boost::python; void export_DetectorInfoItem() { // Export to Python - class_<DetectorInfoItem>("DetectorInfoItem", no_init) - .add_property("isMonitor", &DetectorInfoItem::isMonitor) - .add_property("isMasked", &DetectorInfoItem::isMasked) - .add_property("twoTheta", &DetectorInfoItem::twoTheta) - .add_property("position", &DetectorInfoItem::position) - .add_property("rotation", &DetectorInfoItem::rotation); + class_<DetectorInfoItem<DetectorInfo>>("DetectorInfoItem", no_init) + .add_property("isMonitor", &DetectorInfoItem<DetectorInfo>::isMonitor) + .add_property("isMasked", &DetectorInfoItem<DetectorInfo>::isMasked) + .add_property("twoTheta", &DetectorInfoItem<DetectorInfo>::twoTheta) + .add_property("position", &DetectorInfoItem<DetectorInfo>::position) + .add_property("rotation", &DetectorInfoItem<DetectorInfo>::rotation) + .def("setMasked", &DetectorInfoItem<DetectorInfo>::setMasked, + (arg("self"), arg("masked")), "Set the mask flag for the detector"); } diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoIterator.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoIterator.cpp index f6b73a30bb61f5af46895abad91c533d7a20630d..9f099aff92a1c60986ba11c48adce3fca83d8d2f 100644 --- a/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoIterator.cpp +++ b/Framework/PythonInterface/mantid/geometry/src/Exports/DetectorInfoIterator.cpp @@ -5,10 +5,12 @@ // & Institut Laue - Langevin // SPDX - License - Identifier: GPL - 3.0 + #include "MantidGeometry/Instrument/DetectorInfoIterator.h" +#include "MantidGeometry/Instrument/DetectorInfo.h" #include <boost/python/class.hpp> #include <boost/python/module.hpp> +using Mantid::Geometry::DetectorInfo; using Mantid::Geometry::DetectorInfoIterator; using namespace boost::python; @@ -16,5 +18,5 @@ using namespace boost::python; void export_DetectorInfoIterator() { // Export to Python - class_<DetectorInfoIterator>("DetectorInfoIterator", no_init); + class_<DetectorInfoIterator<DetectorInfo>>("DetectorInfoIterator", no_init); } diff --git a/Framework/PythonInterface/mantid/kernel/src/Converters/PyObjectToString.cpp b/Framework/PythonInterface/mantid/kernel/src/Converters/PyObjectToString.cpp index 6ace6cb986316edfa4de434b2eb261a6e4eacc76..cd5b2e15d6bca01686c99f08c691ec5b19eb0ce2 100644 --- a/Framework/PythonInterface/mantid/kernel/src/Converters/PyObjectToString.cpp +++ b/Framework/PythonInterface/mantid/kernel/src/Converters/PyObjectToString.cpp @@ -31,6 +31,19 @@ std::string pyObjToStr(const python::object &value) { return valuestr; } +bool pyObjIsStr(const boost::python::object &value) { + python::extract<std::string> extractor(value); + if (extractor.check()) { + return true; +#if PY_VERSION_HEX < 0x03000000 + } else if (PyUnicode_Check(value.ptr())) { + return true; +#endif + } + + return false; +} + } // namespace Converters } // namespace PythonInterface } // namespace Mantid diff --git a/Framework/PythonInterface/mantid/kernel/src/Exports/ConfigService.cpp b/Framework/PythonInterface/mantid/kernel/src/Exports/ConfigService.cpp index 5bba89e394e4b27f8ee398480c7ab025ce38039a..451445db7934b39dc8134b13ea0c63ef69759634 100644 --- a/Framework/PythonInterface/mantid/kernel/src/Exports/ConfigService.cpp +++ b/Framework/PythonInterface/mantid/kernel/src/Exports/ConfigService.cpp @@ -6,36 +6,89 @@ // SPDX - License - Identifier: GPL - 3.0 + #include "MantidKernel/ConfigService.h" #include "MantidKernel/FacilityInfo.h" +#include "MantidKernel/InstrumentInfo.h" #include "MantidKernel/WarningSuppressions.h" +#include "MantidPythonInterface/kernel/Converters/PyObjectToString.h" #include "MantidPythonInterface/kernel/Converters/PySequenceToVector.h" #include "MantidPythonInterface/kernel/GetPointer.h" #include "MantidPythonInterface/kernel/StlExportDefinitions.h" #include <boost/python/class.hpp> #include <boost/python/copy_const_reference.hpp> #include <boost/python/def.hpp> -#include <boost/python/list.hpp> #include <boost/python/overloads.hpp> #include <boost/python/reference_existing_object.hpp> using Mantid::Kernel::ConfigService; using Mantid::Kernel::ConfigServiceImpl; using Mantid::Kernel::FacilityInfo; +using Mantid::Kernel::InstrumentInfo; +using Mantid::PythonInterface::Converters::PySequenceToVector; +using Mantid::PythonInterface::Converters::pyObjIsStr; +using Mantid::PythonInterface::Converters::pyObjToStr; + using namespace boost::python; GET_POINTER_SPECIALIZATION(ConfigServiceImpl) namespace { /// Set directories from a python list -void setDataSearchDirs(ConfigServiceImpl &self, - const boost::python::list &paths) { - using namespace Mantid::PythonInterface; - self.setDataSearchDirs(Converters::PySequenceToVector<std::string>(paths)()); +void setDataSearchDirs(ConfigServiceImpl &self, const object &paths) { + if (pyObjIsStr(paths)) { + self.setDataSearchDirs(pyObjToStr(paths)); + } else { + self.setDataSearchDirs(PySequenceToVector<std::string>(paths)()); + } } /// Forward call from __getitem__ to getString with use_cache_true -std::string getStringUsingCache(ConfigServiceImpl &self, - const std::string &key) { - return self.getString(key, true); +std::string getStringUsingCache(ConfigServiceImpl &self, const object &key) { + return self.getString(pyObjToStr(key), true); +} + +bool hasProperty(ConfigServiceImpl &self, const object &name) { + return self.hasProperty(pyObjToStr(name)); +} + +const FacilityInfo &getFacility(ConfigServiceImpl &self, const object &name) { + return self.getFacility(pyObjToStr(name)); +} + +void setFacility(ConfigServiceImpl &self, const object &name) { + return self.setFacility(pyObjToStr(name)); +} + +void updateFacilities(ConfigServiceImpl &self, const object &filename) { + return self.updateFacilities(pyObjToStr(filename)); +} + +const InstrumentInfo &getInstrument(ConfigServiceImpl &self, + const object &name = object()) { + if (name.ptr() == Py_None) + return self.getInstrument(); + else + return self.getInstrument(pyObjToStr(name)); +} + +std::string getString(ConfigServiceImpl &self, const object &name, + bool use_cache = true) { + return self.getString(pyObjToStr(name), use_cache); +} + +void setString(ConfigServiceImpl &self, const object &key, + const object &value) { + self.setString(pyObjToStr(key), pyObjToStr(value)); +} + +void appendDataSearchDir(ConfigServiceImpl &self, const object &path) { + self.appendDataSearchDir(pyObjToStr(path)); +} + +void appendDataSearchSubDir(ConfigServiceImpl &self, const object &subdir) { + self.appendDataSearchSubDir(pyObjToStr(subdir)); +} + +void saveConfig(ConfigServiceImpl &self, const object &filename) { + self.saveConfig(pyObjToStr(filename)); } GNU_DIAG_OFF("unused-local-typedef") @@ -43,10 +96,9 @@ GNU_DIAG_OFF("unused-local-typedef") // Seen with GCC 7.1.1 and Boost 1.63.0 GNU_DIAG_OFF("conversion") /// Overload generator for getInstrument -BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(getInstrument_Overload, getInstrument, 0, - 1) +BOOST_PYTHON_FUNCTION_OVERLOADS(getInstrument_Overload, getInstrument, 1, 2) /// Overload generator for getString -BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(getString_Overload, getString, 1, 2) +BOOST_PYTHON_FUNCTION_OVERLOADS(getString_Overload, getString, 2, 3) GNU_DIAG_ON("conversion") GNU_DIAG_ON("unused-local-typedef") @@ -60,127 +112,85 @@ void export_ConfigService() { class_<ConfigServiceImpl, boost::noncopyable>("ConfigServiceImpl", no_init) .def("reset", &ConfigServiceImpl::reset, arg("self"), "Clears all user settings and removes the user properties file") - .def("getAppDataDirectory", &ConfigServiceImpl::getAppDataDir, arg("self"), "Returns the path to Mantid's application directory") - .def("getLocalFilename", &ConfigServiceImpl::getLocalFilename, arg("self"), "Returns the path to the system wide properties file.") - .def("getUserFilename", &ConfigServiceImpl::getUserFilename, arg("self"), "Returns the path to the user properties file") - .def("getUserPropertiesDir", &ConfigServiceImpl::getUserPropertiesDir, arg("self"), "Returns the directory to use to write out Mantid information") - .def("getInstrumentDirectory", &ConfigServiceImpl::getInstrumentDirectory, arg("self"), "Returns the directory used for the instrument definitions") - .def("getInstrumentDirectories", &ConfigServiceImpl::getInstrumentDirectories, arg("self"), return_value_policy<reference_existing_object>(), "Returns the list of directories searched for the instrument " "definitions") - .def("getFacilityNames", &ConfigServiceImpl::getFacilityNames, arg("self"), "Returns the default facility") - .def("getFacilities", &ConfigServiceImpl::getFacilities, arg("self"), "Returns the default facility") - .def("getFacility", (const FacilityInfo &(ConfigServiceImpl::*)() const) & ConfigServiceImpl::getFacility, arg("self"), return_value_policy<reference_existing_object>(), "Returns the default facility") - - .def("getFacility", - (const FacilityInfo &( - ConfigServiceImpl::*)(const std::string &)const) & - ConfigServiceImpl::getFacility, - (arg("self"), arg("facilityName")), + .def("getFacility", &getFacility, (arg("self"), arg("facilityName")), return_value_policy<reference_existing_object>(), "Returns the named facility. Raises an RuntimeError if it does not " "exist") - - .def("setFacility", &ConfigServiceImpl::setFacility, - (arg("self"), arg("facilityName")), + .def("setFacility", &setFacility, (arg("self"), arg("facilityName")), "Sets the current facility to the given name") - - .def("updateFacilities", &ConfigServiceImpl::updateFacilities, + .def("updateFacilities", &updateFacilities, (arg("self"), arg("fileName")), "Loads facility information from a provided file") - - .def("getInstrument", &ConfigServiceImpl::getInstrument, + .def("getInstrument", &getInstrument, getInstrument_Overload( "Returns the named instrument. If name = \"\" then the " "default.instrument is returned", - (arg("self"), - arg("instrumentName") = - ""))[return_value_policy<copy_const_reference>()]) - - .def("getString", &ConfigServiceImpl::getString, + (arg("self"), arg("instrumentName") = boost::python::object())) + [return_value_policy<copy_const_reference>()]) + .def("getString", &getString, getString_Overload( "Returns the named key's value. If use_cache = " "true [default] then relative paths->absolute", (arg("self"), arg("key"), arg("use_cache") = true))) - - .def("setString", &ConfigServiceImpl::setString, - (arg("self"), arg("key"), arg("value")), + .def("setString", &setString, (arg("self"), arg("key"), arg("value")), "Set the given property name. " "If it does not exist it is added to the current configuration") - - .def("hasProperty", &ConfigServiceImpl::hasProperty, - (arg("self"), arg("rootName"))) - + .def("hasProperty", &hasProperty, (arg("self"), arg("rootName"))) .def("getDataSearchDirs", &ConfigServiceImpl::getDataSearchDirs, arg("self"), return_value_policy<copy_const_reference>(), "Return the current list of data search paths") - - .def("appendDataSearchDir", &ConfigServiceImpl::appendDataSearchDir, + .def("appendDataSearchDir", &appendDataSearchDir, (arg("self"), arg("path")), "Append a directory to the current list of data search paths") - - .def("appendDataSearchSubDir", &ConfigServiceImpl::appendDataSearchSubDir, + .def("appendDataSearchSubDir", &appendDataSearchSubDir, (arg("self"), arg("subdir")), "Appends a sub-directory to each data search directory " "and appends the new paths back to datasearch directories") - - .def("setDataSearchDirs", - (void (ConfigServiceImpl::*)(const std::string &)) & - ConfigServiceImpl::setDataSearchDirs, - (arg("self"), arg("searchDirs")), - "Set the whole datasearch.directories property from a single " - "string. Entries should be separated by a ; character") - .def("setDataSearchDirs", &setDataSearchDirs, (arg("self"), arg("searchDirs")), - "Set the datasearch.directories property from a list of strings.") - - .def("saveConfig", &ConfigServiceImpl::saveConfig, - (arg("self"), arg("filename")), + "Set the datasearch.directories property from a list of strings or " + "a single ';' separated string.") + .def("saveConfig", &saveConfig, (arg("self"), arg("filename")), "Saves the keys that have changed from their default to the given " "filename") - .def("setLogLevel", &ConfigServiceImpl::setLogLevel, (arg("self"), arg("logLevel"), arg("quiet") = false), "Sets the log level priority for all the log channels, logLevel " "1 = Fatal, 6 = information, 7 = Debug") - .def("keys", &ConfigServiceImpl::keys, arg("self")) // Treat this as a dictionary .def("__getitem__", &getStringUsingCache, (arg("self"), arg("key"))) - .def("__setitem__", &ConfigServiceImpl::setString, - (arg("self"), arg("key"), arg("value"))) - .def("__contains__", &ConfigServiceImpl::hasProperty, - (arg("self"), arg("key"))) + .def("__setitem__", &setString, (arg("self"), arg("key"), arg("value"))) + .def("__contains__", &hasProperty, (arg("self"), arg("key"))) .def("Instance", &ConfigService::Instance, return_value_policy<reference_existing_object>(), "Returns a reference to the ConfigService") - .staticmethod("Instance") - - ; + .staticmethod("Instance"); } diff --git a/Framework/PythonInterface/mantid/kernel/src/Exports/Logger.cpp b/Framework/PythonInterface/mantid/kernel/src/Exports/Logger.cpp index c48a32d03fac05d689d705ba75e7b1746599f631..064e67dfbca5834361ea7643f362f41a60a029de 100644 --- a/Framework/PythonInterface/mantid/kernel/src/Exports/Logger.cpp +++ b/Framework/PythonInterface/mantid/kernel/src/Exports/Logger.cpp @@ -5,13 +5,16 @@ // & Institut Laue - Langevin // SPDX - License - Identifier: GPL - 3.0 + #include "MantidKernel/Logger.h" +#include "MantidPythonInterface/kernel/Converters/PyObjectToString.h" #include <boost/make_shared.hpp> #include <boost/python/class.hpp> +#include <boost/python/make_constructor.hpp> #include <boost/python/reference_existing_object.hpp> #include <boost/python/register_ptr_to_python.hpp> using Mantid::Kernel::Logger; using namespace boost::python; +namespace Converters = Mantid::PythonInterface::Converters; namespace { /** @@ -24,43 +27,64 @@ boost::shared_ptr<Logger> getLogger(const std::string &name) { "Simply use Logger(\"name\") instead"); return boost::make_shared<Logger>(name); } + +boost::shared_ptr<Logger> create(const boost::python::object &name) { + return boost::make_shared<Logger>(Converters::pyObjToStr(name)); +} + +void fatal(Logger *self, const boost::python::object &message) { + self->fatal(Converters::pyObjToStr(message)); +} + +void error(Logger *self, const boost::python::object &message) { + self->error(Converters::pyObjToStr(message)); +} + +void warning(Logger *self, const boost::python::object &message) { + self->warning(Converters::pyObjToStr(message)); +} + +void notice(Logger *self, const boost::python::object &message) { + self->notice(Converters::pyObjToStr(message)); +} + +void information(Logger *self, const boost::python::object &message) { + self->information(Converters::pyObjToStr(message)); +} + +void debug(Logger *self, const boost::python::object &message) { + self->debug(Converters::pyObjToStr(message)); +} } // namespace void export_Logger() { register_ptr_to_python<boost::shared_ptr<Logger>>(); - // To distinguish between the overloaded functions - using LogLevelFunction = void (Logger::*)(const std::string &); - class_<Logger, boost::noncopyable>( "Logger", init<std::string>((arg("self"), arg("name")))) - .def("fatal", (LogLevelFunction)&Logger::fatal, - (arg("self"), arg("message")), + .def("__init__", + make_constructor(&create, default_call_policies(), args("name"))) + .def("fatal", &fatal, (arg("self"), arg("message")), "Send a message at fatal priority: " "An unrecoverable error has occured and the application will " "terminate") - .def("error", (LogLevelFunction)&Logger::error, - (arg("self"), arg("message")), + .def("error", &error, (arg("self"), arg("message")), "Send a message at error priority: " "An error has occured but the framework is able to handle it and " "continue") - .def("warning", (LogLevelFunction)&Logger::warning, - (arg("self"), arg("message")), + .def("warning", &warning, (arg("self"), arg("message")), "Send a message at warning priority: " "Something was wrong but the framework was able to continue despite " "the problem.") - .def("notice", (LogLevelFunction)&Logger::notice, - (arg("self"), arg("message")), + .def("notice", ¬ice, (arg("self"), arg("message")), "Sends a message at notice priority: " "Really important information that should be displayed to the user, " "this should be minimal. The default logging level is set here " "unless it is altered.") - .def("information", (LogLevelFunction)&Logger::information, - (arg("self"), arg("message")), + .def("information", &information, (arg("self"), arg("message")), "Send a message at information priority: " "Useful but not vital information to be relayed back to the user.") - .def("debug", (LogLevelFunction)&Logger::debug, - (arg("self"), arg("message")), + .def("debug", &debug, (arg("self"), arg("message")), "Send a message at debug priority:" ". Anything that may be useful to understand what the code has been " "doing for debugging purposes.") diff --git a/Framework/PythonInterface/mantid/kernel/src/Exports/UsageService.cpp b/Framework/PythonInterface/mantid/kernel/src/Exports/UsageService.cpp index 48b790430835158b86238021584e9eaab262361c..7bfa57ce02af5ac8214c5dc54c105772c04cadb2 100644 --- a/Framework/PythonInterface/mantid/kernel/src/Exports/UsageService.cpp +++ b/Framework/PythonInterface/mantid/kernel/src/Exports/UsageService.cpp @@ -5,54 +5,57 @@ // & Institut Laue - Langevin // SPDX - License - Identifier: GPL - 3.0 + #include "MantidKernel/UsageService.h" +#include "MantidPythonInterface/kernel/Converters/PyObjectToString.h" #include "MantidPythonInterface/kernel/GetPointer.h" #include <boost/python/class.hpp> #include <boost/python/reference_existing_object.hpp> using Mantid::Kernel::UsageService; using Mantid::Kernel::UsageServiceImpl; +using Mantid::PythonInterface::Converters::pyObjToStr; using namespace boost::python; GET_POINTER_SPECIALIZATION(UsageServiceImpl) +namespace { +void setApplication(UsageServiceImpl *self, const object &name) { + self->setApplication(pyObjToStr(name)); +} + +void registerFeatureUsage(UsageServiceImpl *self, const object &type, + const object &name, const bool internal) { + self->registerFeatureUsage(pyObjToStr(type), pyObjToStr(name), internal); +} + +} // anonymous namespace + void export_UsageService() { class_<UsageServiceImpl, boost::noncopyable>("UsageServiceImpl", no_init) .def("flush", &UsageServiceImpl::flush, arg("self"), "Sends any pending usage information.") - .def("shutdown", &UsageServiceImpl::shutdown, arg("self"), "Sends any pending usage information, and disables the usage " "service.") - .def("getUpTime", &UsageServiceImpl::getUpTime, arg("self"), "Returns the time that the instance of mantid has been running") - .def("isEnabled", &UsageServiceImpl::isEnabled, arg("self"), "Returns if the usage service is enabled.") - .def("setEnabled", &UsageServiceImpl::setEnabled, (arg("self"), arg("enabled")), "Enables or disables the usage service.") - .def("setInterval", &UsageServiceImpl::setEnabled, (arg("self"), arg("seconds")), "Sets the interval that the timer checks for tasks.") - - .def("setApplication", &UsageServiceImpl::setApplication, - (arg("self"), arg("name")), + .def("setApplication", &setApplication, (arg("self"), arg("name")), "Sets the application name that has invoked Mantid.") - .def("getApplication", &UsageServiceImpl::getApplication, arg("self"), "Gets the application name that has invoked Mantid.") - .def("registerStartup", &UsageServiceImpl::registerStartup, arg("self"), "Registers the startup of Mantid.") - - .def("registerFeatureUsage", &UsageServiceImpl::registerFeatureUsage, + .def("registerFeatureUsage", ®isterFeatureUsage, (arg("self"), arg("type"), arg("name"), arg("internal")), "Registers the use of a feature in Mantid.") - .def("Instance", &UsageService::Instance, return_value_policy<reference_existing_object>(), "Returns a reference to the UsageService") diff --git a/Framework/PythonInterface/mantid/plots/__init__.py b/Framework/PythonInterface/mantid/plots/__init__.py index dee1a4596f8637ef0ecd57f7a01196bc7fe6bbbf..f9103522c14813117c3b80daf06d3dbe0f806591 100644 --- a/Framework/PythonInterface/mantid/plots/__init__.py +++ b/Framework/PythonInterface/mantid/plots/__init__.py @@ -63,7 +63,7 @@ class MantidAxes(Axes): For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.plot`. ''' - if mantid.plots.helperfunctions.validate_args(*args): + if mantid.plots.helperfunctions.validate_args(*args, **kwargs): mantid.kernel.logger.debug('using mantid.plots.plotfunctions') return mantid.plots.plotfunctions.plot(self, *args, **kwargs) else: @@ -73,19 +73,19 @@ class MantidAxes(Axes): ''' If the **mantid** projection is chosen, it can be used the same as :py:meth:`matplotlib.axes.Axes.scatter` for arrays, - or it can be used to plot :class:`mantid.api.MatrixWorkspace` + or it can be used to plot :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace`. You can have something like:: - + import matplotlib.pyplot as plt from mantid import plots - + ... - + fig, ax = plt.subplots(subplot_kw={'projection':'mantid'}) ax.scatter(workspace,'rs',specNum=1) #for workspaces ax.scatter(x,y,'bo') #for arrays fig.show() - + For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.scatter` ''' if mantid.plots.helperfunctions.validate_args(*args): @@ -98,19 +98,19 @@ class MantidAxes(Axes): ''' If the **mantid** projection is chosen, it can be used the same as :py:meth:`matplotlib.axes.Axes.errorbar` for arrays, - or it can be used to plot :class:`mantid.api.MatrixWorkspace` + or it can be used to plot :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace`. You can have something like:: - + import matplotlib.pyplot as plt from mantid import plots - + ... - + fig, ax = plt.subplots(subplot_kw={'projection':'mantid'}) ax.errorbar(workspace,'rs',specNum=1) #for workspaces ax.errorbar(x,y,yerr,'bo') #for arrays fig.show() - + For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.errorbar` ''' if mantid.plots.helperfunctions.validate_args(*args): @@ -123,19 +123,19 @@ class MantidAxes(Axes): ''' If the **mantid** projection is chosen, it can be used the same as :py:meth:`matplotlib.axes.Axes.pcolor` for arrays, - or it can be used to plot :class:`mantid.api.MatrixWorkspace` + or it can be used to plot :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace`. You can have something like:: - + import matplotlib.pyplot as plt from mantid import plots - + ... - + fig, ax = plt.subplots(subplot_kw={'projection':'mantid'}) ax.pcolor(workspace) #for workspaces ax.pcolor(x,y,C) #for arrays fig.show() - + For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.pcolor` ''' if mantid.plots.helperfunctions.validate_args(*args): @@ -148,19 +148,19 @@ class MantidAxes(Axes): ''' If the **mantid** projection is chosen, it can be used the same as :py:meth:`matplotlib.axes.Axes.pcolorfast` for arrays, - or it can be used to plot :class:`mantid.api.MatrixWorkspace` + or it can be used to plot :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace`. You can have something like:: - + import matplotlib.pyplot as plt from mantid import plots - + ... - + fig, ax = plt.subplots(subplot_kw={'projection':'mantid'}) ax.pcolorfast(workspace) #for workspaces ax.pcolorfast(x,y,C) #for arrays fig.show() - + For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.pcolorfast` ''' if mantid.plots.helperfunctions.validate_args(*args): @@ -173,19 +173,19 @@ class MantidAxes(Axes): ''' If the **mantid** projection is chosen, it can be used the same as :py:meth:`matplotlib.axes.Axes.pcolormesh` for arrays, - or it can be used to plot :class:`mantid.api.MatrixWorkspace` + or it can be used to plot :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace`. You can have something like:: - + import matplotlib.pyplot as plt from mantid import plots - + ... - + fig, ax = plt.subplots(subplot_kw={'projection':'mantid'}) ax.pcolormesh(workspace) #for workspaces ax.pcolormesh(x,y,C) #for arrays fig.show() - + For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.pcolormesh` ''' if mantid.plots.helperfunctions.validate_args(*args): @@ -194,23 +194,48 @@ class MantidAxes(Axes): else: return Axes.pcolormesh(self, *args, **kwargs) + def imshow(self, *args, **kwargs): + ''' + If the **mantid** projection is chosen, it can be + used the same as :py:meth:`matplotlib.axes.Axes.imshow` for arrays, + or it can be used to plot :class:`mantid.api.MatrixWorkspace` + or :class:`mantid.api.IMDHistoWorkspace`. You can have something like:: + + import matplotlib.pyplot as plt + from mantid import plots + + ... + + fig, ax = plt.subplots(subplot_kw={'projection':'mantid'}) + ax.imshow(workspace) #for workspaces + ax.imshow(C) #for arrays + fig.show() + + For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.imshow` + ''' + if mantid.plots.helperfunctions.validate_args(*args): + mantid.kernel.logger.debug('using mantid.plots.plotfunctions') + return mantid.plots.plotfunctions.imshow(self, *args, **kwargs) + else: + return Axes.imshow(self, *args, **kwargs) + def contour(self, *args, **kwargs): ''' If the **mantid** projection is chosen, it can be used the same as :py:meth:`matplotlib.axes.Axes.contour` for arrays, - or it can be used to plot :class:`mantid.api.MatrixWorkspace` + or it can be used to plot :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace`. You can have something like:: - + import matplotlib.pyplot as plt from mantid import plots - + ... - + fig, ax = plt.subplots(subplot_kw={'projection':'mantid'}) ax.contour(workspace) #for workspaces ax.contour(x,y,z) #for arrays fig.show() - + For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.contour` ''' if mantid.plots.helperfunctions.validate_args(*args): @@ -223,19 +248,19 @@ class MantidAxes(Axes): ''' If the **mantid** projection is chosen, it can be used the same as :py:meth:`matplotlib.axes.Axes.contourf` for arrays, - or it can be used to plot :class:`mantid.api.MatrixWorkspace` + or it can be used to plot :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace`. You can have something like:: - + import matplotlib.pyplot as plt from mantid import plots - + ... - + fig, ax = plt.subplots(subplot_kw={'projection':'mantid'}) ax.contourf(workspace) #for workspaces ax.contourf(x,y,z) #for arrays fig.show() - + For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.contourf` ''' if mantid.plots.helperfunctions.validate_args(*args): @@ -248,19 +273,19 @@ class MantidAxes(Axes): ''' If the **mantid** projection is chosen, it can be used the same as :py:meth:`matplotlib.axes.Axes.tripcolor` for arrays, - or it can be used to plot :class:`mantid.api.MatrixWorkspace` + or it can be used to plot :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace`. You can have something like:: - + import matplotlib.pyplot as plt from mantid import plots - + ... - + fig, ax = plt.subplots(subplot_kw={'projection':'mantid'}) ax.tripcolor(workspace) #for workspaces ax.tripcolor(x,y,C) #for arrays fig.show() - + For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.tripcolor` ''' if mantid.plots.helperfunctions.validate_args(*args): @@ -273,19 +298,19 @@ class MantidAxes(Axes): ''' If the **mantid** projection is chosen, it can be used the same as :py:meth:`matplotlib.axes.Axes.tricontour` for arrays, - or it can be used to plot :class:`mantid.api.MatrixWorkspace` + or it can be used to plot :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace`. You can have something like:: - + import matplotlib.pyplot as plt from mantid import plots - + ... - + fig, ax = plt.subplots(subplot_kw={'projection':'mantid'}) ax.tricontour(workspace) #for workspaces ax.tricontour(x,y,z) #for arrays fig.show() - + For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.tricontour` ''' if mantid.plots.helperfunctions.validate_args(*args): @@ -298,19 +323,19 @@ class MantidAxes(Axes): ''' If the **mantid** projection is chosen, it can be used the same as :py:meth:`matplotlib.axes.Axes.tricontourf` for arrays, - or it can be used to plot :class:`mantid.api.MatrixWorkspace` + or it can be used to plot :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace`. You can have something like:: - + import matplotlib.pyplot as plt from mantid import plots - + ... - + fig, ax = plt.subplots(subplot_kw={'projection':'mantid'}) ax.tricontourf(workspace) #for workspaces ax.tricontourf(x,y,z) #for arrays fig.show() - + For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.tricontourf` ''' if mantid.plots.helperfunctions.validate_args(*args): diff --git a/Framework/PythonInterface/mantid/plots/helperfunctions.py b/Framework/PythonInterface/mantid/plots/helperfunctions.py index ad760e08c3ce44e988b7a6e4f8b710c96d320c5d..0b8fccd31a6e8a001a234b1e6bfcb9f79a3f9c95 100644 --- a/Framework/PythonInterface/mantid/plots/helperfunctions.py +++ b/Framework/PythonInterface/mantid/plots/helperfunctions.py @@ -12,6 +12,7 @@ from __future__ import (absolute_import, division, print_function) import numpy import datetime from mantid.dataobjects import EventWorkspace, Workspace2D, MDHistoWorkspace +from mantid.api import MultipleExperimentInfos import mantid.kernel, mantid.api @@ -21,10 +22,11 @@ import mantid.kernel, mantid.api # ==================================================== # Validation # ==================================================== -def validate_args(*args): +def validate_args(*args, **kwargs): return len(args) > 0 and (isinstance(args[0], EventWorkspace) or isinstance(args[0], Workspace2D) or - isinstance(args[0], MDHistoWorkspace)) + isinstance(args[0], MDHistoWorkspace) or + isinstance(args[0], MultipleExperimentInfos) and "LogName" in kwargs) # ==================================================== @@ -370,7 +372,7 @@ def get_data_uneven_flag(workspace, **kwargs): def get_sample_log(workspace, **kwargs): LogName = kwargs.pop('LogName') ExperimentInfo = kwargs.pop('ExperimentInfo',0) - if isinstance(workspace, MDHistoWorkspace): + if isinstance(workspace, MultipleExperimentInfos): run = workspace.getExperimentInfo(ExperimentInfo).run() else: run = workspace.run() @@ -378,8 +380,10 @@ def get_sample_log(workspace, **kwargs): raise ValueError('The workspace does not contain the {} sample log'.format(LogName)) tsp = run[LogName] units = tsp.units - if not isinstance(tsp, mantid.kernel.FloatTimeSeriesProperty): - raise RuntimeError('This function can only plot FloatTimeSeriesProperty objects') + if not isinstance(tsp, (mantid.kernel.FloatTimeSeriesProperty, + mantid.kernel.Int32TimeSeriesProperty, + mantid.kernel.Int64TimeSeriesProperty)): + raise RuntimeError('This function can only plot Float or Int TimeSeriesProperties objects') times = tsp.times.astype('datetime64[us]') y = tsp.value FullTime = kwargs.pop('FullTime', False) @@ -411,7 +415,7 @@ def get_axes_labels(workspace): :param workspace: :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace` """ - if isinstance(workspace, MDHistoWorkspace): + if isinstance(workspace, MultipleExperimentInfos): axes = ['Intensity'] dims = workspace.getNonIntegratedDimensions() for d in dims: diff --git a/Framework/PythonInterface/mantid/plots/plotfunctions.py b/Framework/PythonInterface/mantid/plots/plotfunctions.py index f345988a42a48b16d3c9197b96afa3c0960c2f09..5e797706b22f6b172d5f40d903e452c90c773700 100644 --- a/Framework/PythonInterface/mantid/plots/plotfunctions.py +++ b/Framework/PythonInterface/mantid/plots/plotfunctions.py @@ -357,6 +357,42 @@ def pcolormesh(axes, workspace, *args, **kwargs): return axes.pcolormesh(x, y, z, *args, **kwargs) +def imshow(axes, workspace, *args, **kwargs): + ''' + Essentially the same as :meth:`matplotlib.axes.Axes.pcolormesh`. + + :param axes: :class:`matplotlib.axes.Axes` object that will do the plotting + :param workspace: :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace` + to extract the data from + :param distribution: ``None`` (default) asks the workspace. ``False`` means + divide by bin width. ``True`` means do not divide by bin width. + Applies only when the the matrix workspace is a histogram. + :param normalization: ``None`` (default) ask the workspace. Applies to MDHisto workspaces. It can override + the value from displayNormalizationHisto. It checks only if + the normalization is mantid.api.MDNormalization.NumEventsNormalization + :param axisaligned: ``False`` (default). If ``True``, or if the workspace has a variable + number of bins, the polygons will be aligned with the axes + ''' + _setLabels2D(axes, workspace) + if isinstance(workspace, mantid.dataobjects.MDHistoWorkspace): + (normalization, kwargs) = get_normalization(workspace, **kwargs) + x, y, z = get_md_data2d_bin_bounds(workspace, normalization) + else: + (uneven_bins, kwargs) = get_data_uneven_flag(workspace, **kwargs) + (distribution, kwargs) = get_distribution(workspace, **kwargs) + if uneven_bins: + raise Exception('Variable number of bins is not supported by imshow.') + else: + (x, y, z) = get_matrix_2d_data(workspace, distribution, histogram2D=True) + + diffs = numpy.diff(x, axis=1) + x_spacing_equal = numpy.alltrue(diffs == diffs[0]) + diffs = numpy.diff(y, axis=0) + y_spacing_equal = numpy.alltrue(diffs == diffs[0]) + if not x_spacing_equal or not y_spacing_equal: + raise Exception('Unevenly spaced bins are not supported by imshow') + kwargs['extent'] = [x[0,0],x[0,-1],y[0,0],y[-1,0]] + return axes.imshow(z, *args, **kwargs) def tripcolor(axes, workspace, *args, **kwargs): ''' diff --git a/Framework/PythonInterface/plugins/algorithms/BASISPowderDiffraction.py b/Framework/PythonInterface/plugins/algorithms/BASISPowderDiffraction.py index ab2c5c97a47953b5e98e4e2645302cd658e867ab..f9d9181c59b72087b9d13e5d0f4e6eddb0ac1b22 100644 --- a/Framework/PythonInterface/plugins/algorithms/BASISPowderDiffraction.py +++ b/Framework/PythonInterface/plugins/algorithms/BASISPowderDiffraction.py @@ -21,7 +21,8 @@ from mantid.simpleapi import (DeleteWorkspace, LoadMask, LoadEventNexus, CropWorkspace, RenameWorkspace, LoadNexusMonitors, OneMinusExponentialCor, Scale, Divide, Rebin, MedianDetectorTest, - SumSpectra, Integration, CreateWorkspace) + SumSpectra, Integration, CreateWorkspace, + ScaleX, Plus) from mantid.kernel import (FloatArrayProperty, Direction) debug_flag = False # set to True to prevent erasing temporary workspaces @@ -76,6 +77,8 @@ class BASISPowderDiffraction(DataProcessorAlgorithm): _mask_file = '/SNS/BSS/shared/autoreduce/new_masks_08_12_2015/'\ 'BASIS_Mask_default_diff.xml' + # Consider only events with these wavelengths + _wavelength_bands = {'311': [3.07, 3.6], '111': [6.1, 6.6]} def __init__(self): DataProcessorAlgorithm.__init__(self) @@ -107,6 +110,25 @@ class BASISPowderDiffraction(DataProcessorAlgorithm): def seeAlso(): return ['BASISReduction', 'BASISCrystalDiffraction'] + @staticmethod + def add_previous_pulse(w): + """ + Duplicate the events but shift them by one pulse, then add to + input workspace + + Parameters + ---------- + w: Mantid.EventsWorkspace + + Returns + ------- + Mantid.EventsWorkspace + """ + pulse_width = 1.e6/60 # in micro-seconds + _t_w = ScaleX(w, Factor=-pulse_width, Operation='Add') + _t_w = Plus(w, _t_w, OutputWorkspace=w.name()) + return _t_w + def PyInit(self): # # Properties @@ -309,6 +331,7 @@ class BASISPowderDiffraction(DataProcessorAlgorithm): """ MaskDetectors(w, MaskedWorkspace=self._t_mask) _t_corr = ModeratorTzeroLinear(w) # delayed emission from moderator + _t_corr = self.add_previous_pulse(_t_corr) _t_corr = ConvertUnits(_t_corr, Target='Wavelength', Emode='Elastic') l_s, l_e = self._wavelength_band[0], self._wavelength_band[1] _t_corr = CropWorkspace(_t_corr, XMin=l_s, XMax=l_e) @@ -503,32 +526,18 @@ class BASISPowderDiffraction(DataProcessorAlgorithm): def _calculate_wavelength_band(self): """ - Calculate the wavelength band using the monitors from the sample runs - - Consider wavelenghts with an associated intensity above a certain - fraction of the maximum observed intensity. + Select the wavelength band examining the logs of the first sample """ - _t_w = self._load_monitors('sample') - _t_w = ConvertUnits(_t_w, Target='Wavelength', Emode='Elastic') - l_min, l_max = 0.0, 20.0 - _t_w = CropWorkspace(_t_w, XMin=l_min, XMax=l_max) - _t_w = OneMinusExponentialCor(_t_w, C='0.20749999999999999', - C1='0.001276') - _t_w = Scale(_t_w, Factor='1e-06', Operation='Multiply') - _t_w = Rebin(_t_w, Params=[l_min, self._wavelength_dl, l_max], - PreserveEvents=False) - y = _t_w.readY(0) - k = np.argmax(y) # y[k] is the maximum observed intensity - factor = 0.8 # 80% of the maximum intensity - i_s = k - 1 - while y[i_s] > factor * y[k]: - i_s -= 1 - i_e = k + 1 - while y[i_e] > factor * y[k]: - i_e += 1 - x = _t_w.readX(0) - self._wavelength_band = [x[i_s], x[i_e]] - + runs = self.getProperty('RunNumbers').value + run = self._run_lists(runs)[0] + file_name = "{0}_{1}_event.nxs".format(self._short_inst, str(run)) + _t_w = LoadEventNexus(Filename=file_name, NXentryName='entry-diff', + SingleBankPixelsOnly=False) + wavelength = np.mean(_t_w.getRun().getProperty('LambdaRequest').value) + wavs = self._wavelength_bands + midpoint = (wavs['111'][0] + wavs['311'][0]) / 2.0 + reflection = '111' if wavelength > midpoint else '311' + self._wavelength_band = self._wavelength_bands[reflection] # Register algorithm with Mantid. AlgorithmFactory.subscribe(BASISPowderDiffraction) diff --git a/Framework/PythonInterface/plugins/algorithms/CropWorkspaceForMDNorm.py b/Framework/PythonInterface/plugins/algorithms/CropWorkspaceForMDNorm.py index a1ae9d90f3177c23e1493d3ec938c8f650ac1ab6..8feb07972e8432eb2637ac93376fb74e9d6e6be2 100644 --- a/Framework/PythonInterface/plugins/algorithms/CropWorkspaceForMDNorm.py +++ b/Framework/PythonInterface/plugins/algorithms/CropWorkspaceForMDNorm.py @@ -33,9 +33,8 @@ class CropWorkspaceForMDNorm(PythonAlgorithm): """ return "CropWorkspaceForMDNorm" - #TODO: add here the new MDNorm and related - #def seeAlso(self): - # return [] + def seeAlso(self): + return ["RecalculateTrajectoriesExtents"] def summary(self): return "Crops an event workspace and store the information"+\ diff --git a/Framework/PythonInterface/plugins/algorithms/IntegratePeaksProfileFitting.py b/Framework/PythonInterface/plugins/algorithms/IntegratePeaksProfileFitting.py index 065e3c6bbcd7de7ceb6354f8bafff616ebdb449a..0238c745bf200a111c53bc4eb1711489825db3fd 100644 --- a/Framework/PythonInterface/plugins/algorithms/IntegratePeaksProfileFitting.py +++ b/Framework/PythonInterface/plugins/algorithms/IntegratePeaksProfileFitting.py @@ -47,8 +47,6 @@ class IntegratePeaksProfileFitting(PythonAlgorithm): direction=Direction.Input), doc='PeaksWorkspace with peaks to be integrated.') - self.declareProperty("RunNumber", defaultValue=0, - doc="Run Number to integrate") self.declareProperty(FileProperty(name="UBFile",defaultValue="",action=FileAction.OptionalLoad, extensions=[".mat"]), doc="File containing the UB Matrix in ISAW format. Leave blank to use loaded UB Matrix.") @@ -71,72 +69,13 @@ class IntegratePeaksProfileFitting(PythonAlgorithm): self.declareProperty("DQMax", defaultValue=0.15, doc="Largest total side length (in Angstrom) to consider for profile fitting.") self.declareProperty("PeakNumber", defaultValue=-1, doc="Which Peak to fit. Leave negative for all.") - def PyExec(self): - import ICCFitTools as ICCFT - import BVGFitTools as BVGFT - from mantid.simpleapi import LoadIsawUB + def initializeStrongPeakSettings(self, strongPeaksParamsFile, peaks_ws, sampleRun, forceCutoff, edgeCutoff, numDetRows, + numDetCols): import pickle - from scipy.ndimage.filters import convolve - MDdata = self.getProperty('InputWorkspace').value - peaks_ws = self.getProperty('PeaksWorkspace').value - fracStop = self.getProperty('FracStop').value - dQMax = self.getProperty('DQMax').value - UBFile = self.getProperty('UBFile').value - padeFile = self.getProperty('ModeratorCoefficientsFile').value - strongPeaksParamsFile = self.getProperty('StrongPeakParamsFile').value - forceCutoff = self.getProperty('IntensityCutoff').value - edgeCutoff = self.getProperty('EdgeCutoff').value - peakNumberToFit = self.getProperty('PeakNumber').value - pplmin_frac = self.getProperty('MinpplFrac').value - pplmax_frac = self.getProperty('MaxpplFrac').value - sampleRun = self.getProperty('RunNumber').value - - q_frame='lab' - mtd['MDdata'] = MDdata - zBG = 1.96 - neigh_length_m=3 - iccFitDict = ICCFT.parseConstraints(peaks_ws) #Contains constraints and guesses for ICC Fitting - padeCoefficients = ICCFT.getModeratorCoefficients(padeFile) - - # Load the UB Matrix if one is not already loaded - if UBFile == '' and peaks_ws.sample().hasOrientedLattice(): - logger.information("Using UB file already available in PeaksWorkspace") - else: - try: - LoadIsawUB(InputWorkspace=peaks_ws, FileName=UBFile) - except: - logger.error("peaks_ws does not have a UB matrix loaded. Must provide a file") - UBMatrix = peaks_ws.sample().getOrientedLattice().getUB() - - # There are a few instrument specific parameters that we define here. In some cases, - # it may improve fitting to set tweak these parameters, but for simplicity we define these here - # The default values are good for MaNDi - new instruments can be added by adding a different elif - # statement. - # If you change these values or add an instrument, documentation should also be changed. - try: - numDetRows = peaks_ws.getInstrument().getIntParameter("numDetRows")[0] - numDetCols = peaks_ws.getInstrument().getIntParameter("numDetCols")[0] - nPhi = peaks_ws.getInstrument().getIntParameter("numBinsPhi")[0] - nTheta = peaks_ws.getInstrument().getIntParameter("numBinsTheta")[0] - nPhi = peaks_ws.getInstrument().getIntParameter("numBinsPhi")[0] - mindtBinWidth = peaks_ws.getInstrument().getNumberParameter("mindtBinWidth")[0] - maxdtBinWidth = peaks_ws.getInstrument().getNumberParameter("maxdtBinWidth")[0] - fracHKL = peaks_ws.getInstrument().getNumberParameter("fracHKL")[0] - dQPixel = peaks_ws.getInstrument().getNumberParameter("dQPixel")[0] - peakMaskSize = peaks_ws.getInstrument().getIntParameter("peakMaskSize")[0] - except: - raise - logger.error("Cannot find all parameters in instrument parameters file.") - sys.exit(1) - - dQ = np.abs(ICCFT.getDQFracHKL(UBMatrix, frac=0.5)) - dQ[dQ>dQMax] = dQMax - qMask = ICCFT.getHKLMask(UBMatrix, frac=fracHKL, dQPixel=dQPixel,dQ=dQ) - # Strong peak profiles - we set up the workspace and determine which peaks we'll fit. strongPeakKeys = ['Phi', 'Theta', 'Scale3d', 'FitPhi', 'FitTheta', 'SigTheta', 'SigPhi', 'SigP', 'PeakNumber'] strongPeakDatatypes = ['float']*len(strongPeakKeys) - strongPeakParams_ws = CreateEmptyTableWorkspace() + strongPeakParams_ws = CreateEmptyTableWorkspace(OutputWorkspace='__StrongPeakParameters') for key, datatype in zip(strongPeakKeys,strongPeakDatatypes): strongPeakParams_ws.addColumn(datatype, key) @@ -162,9 +101,10 @@ class IntegratePeaksProfileFitting(PythonAlgorithm): canFitProfileIDX = np.where(~needsForcedProfile)[0] numPeaksCanFit = len(canFitProfileIDX) - # We can populate the strongPeakParams_ws now + # We can populate the strongPeakParams_ws now and use that for initial BVG guesses for row in strongPeakParams: strongPeakParams_ws.addRow(row) + else: generateStrongPeakParams = True #Figure out which peaks to fit without forcing a profile and set those to be fit first @@ -182,8 +122,109 @@ class IntegratePeaksProfileFitting(PythonAlgorithm): peaksToFit = np.append(canFitProfileIDX, needsForcedProfileIDX) #Will fit in this order peaksToFit = peaksToFit[runNumbers[peaksToFit]==sampleRun] - #Initialize our strong peaks dictionary + # Initialize our strong peaks dictionary. Set BVG Params to be None so that we fall back on + # instrument defaults until we have fit >=30 peaks. strongPeakParams = np.empty([numPeaksCanFit, 9]) + #sigX0Params, sigY0, sigP0Params = None, None, None + peaksToFit = np.append(peaksToFit, np.where(runNumbers!=sampleRun)[0]) + return generateStrongPeakParams, strongPeakParams, strongPeakParams_ws, needsForcedProfile,\ + needsForcedProfileIDX, canFitProfileIDX, numPeaksCanFit, peaksToFit + + def getBVGInitialGuesses(self, peaks_ws, strongPeakParams_ws, minNumberPeaks=30): + """ + Returns initial guesses for the BVG fit if strongPeakParams_ws contains more than + minNumberPeaks entries. If not, we return all None, which will fall back to the + instrument defaults. + """ + if strongPeakParams_ws.rowCount() > minNumberPeaks: + # First, along the scattering direction + theta = np.abs(strongPeakParams_ws.column('Theta')) + sigma_theta = np.abs(strongPeakParams_ws.column('SigTheta')) + + CreateWorkspace(DataX=theta, DataY=sigma_theta, OutputWorkspace='__ws_bvg0_scat') + Fit(Function='name=UserFunction,Formula=A/2.0*(exp(((x-x0)/b))+exp( -((x-x0)/b)))+BG,A=0.0025,x0=1.54,b=1,BG=-1.26408e-15', + InputWorkspace='__ws_bvg0_scat', Output='__fitSigX0', StartX=np.min(theta), EndX=np.max(theta)) + sigX0Params = mtd['__fitSigX0_Parameters'].column(1)[:-1] + # Second, along the azimuthal. This is just a constant. + sigY0 = np.median(strongPeakParams_ws.column('SigPhi')) + # Finally, the interaction term. This we just get from the instrument file. + try: + sigP0Params = peaks_ws.getInstrument().getStringParameter("sigP0Params") + sigP0Params = np.array(str(sigP0Params).strip('[]\'').split(),dtype=float) + except: + logger.warning('Cannot find sigP0Params. Will use defaults.') + sigP0Params = [0.1460775, 1.85816592, 0.26850086, -0.00725352] + return sigX0Params, sigY0, sigP0Params + else: + return None, None, None + + def getUBMatrix(self, peaks_ws, UBFile): + # Load the UB Matrix if one is not already loaded + if UBFile == '' and peaks_ws.sample().hasOrientedLattice(): + logger.information("Using UB file already available in PeaksWorkspace") + else: + try: + from mantid.simpleapi import LoadIsawUB + LoadIsawUB(InputWorkspace=peaks_ws, FileName=UBFile) + except: + logger.error("peaks_ws does not have a UB matrix loaded. Must provide a file") + UBMatrix = peaks_ws.sample().getOrientedLattice().getUB() + return UBMatrix + + def PyExec(self): + import ICCFitTools as ICCFT + import BVGFitTools as BVGFT + from scipy.ndimage.filters import convolve + MDdata = self.getProperty('InputWorkspace').value + peaks_ws = self.getProperty('PeaksWorkspace').value + fracStop = self.getProperty('FracStop').value + dQMax = self.getProperty('DQMax').value + UBFile = self.getProperty('UBFile').value + padeFile = self.getProperty('ModeratorCoefficientsFile').value + strongPeaksParamsFile = self.getProperty('StrongPeakParamsFile').value + forceCutoff = self.getProperty('IntensityCutoff').value + edgeCutoff = self.getProperty('EdgeCutoff').value + peakNumberToFit = self.getProperty('PeakNumber').value + pplmin_frac = self.getProperty('MinpplFrac').value + pplmax_frac = self.getProperty('MaxpplFrac').value + sampleRun = peaks_ws.getPeak(0).getRunNumber() + + q_frame='lab' + mtd['MDdata'] = MDdata + zBG = 1.96 + neigh_length_m=3 + iccFitDict = ICCFT.parseConstraints(peaks_ws) #Contains constraints and guesses for ICC Fitting + padeCoefficients = ICCFT.getModeratorCoefficients(padeFile) + + # There are a few instrument specific parameters that we define here. In some cases, + # it may improve fitting to set tweak these parameters, but for simplicity we define these here + # The default values are good for MaNDi - new instruments can be added by adding a different elif + # statement. + # If you change these values or add an instrument, documentation should also be changed. + try: + numDetRows = peaks_ws.getInstrument().getIntParameter("numDetRows")[0] + numDetCols = peaks_ws.getInstrument().getIntParameter("numDetCols")[0] + nPhi = peaks_ws.getInstrument().getIntParameter("numBinsPhi")[0] + nTheta = peaks_ws.getInstrument().getIntParameter("numBinsTheta")[0] + nPhi = peaks_ws.getInstrument().getIntParameter("numBinsPhi")[0] + mindtBinWidth = peaks_ws.getInstrument().getNumberParameter("mindtBinWidth")[0] + maxdtBinWidth = peaks_ws.getInstrument().getNumberParameter("maxdtBinWidth")[0] + fracHKL = peaks_ws.getInstrument().getNumberParameter("fracHKL")[0] + dQPixel = peaks_ws.getInstrument().getNumberParameter("dQPixel")[0] + peakMaskSize = peaks_ws.getInstrument().getIntParameter("peakMaskSize")[0] + except: + logger.error("Cannot find all parameters in instrument parameters file.") + raise + + UBMatrix = self.getUBMatrix(peaks_ws, UBFile) + dQ = np.abs(ICCFT.getDQFracHKL(UBMatrix, frac=0.5)) + dQ[dQ>dQMax] = dQMax + qMask = ICCFT.getHKLMask(UBMatrix, frac=fracHKL, dQPixel=dQPixel,dQ=dQ) + + generateStrongPeakParams, strongPeakParams, strongPeakParams_ws, needsForcedProfile, \ + needsForcedProfileIDX, canFitProfileIDX, numPeaksCanFit, peaksToFit = \ + self.initializeStrongPeakSettings(strongPeaksParamsFile, peaks_ws, sampleRun, forceCutoff, edgeCutoff, numDetRows, + numDetCols) if peakNumberToFit>-1: peaksToFit = [peakNumberToFit] @@ -201,10 +242,16 @@ class IntegratePeaksProfileFitting(PythonAlgorithm): peaks_ws_out = peaks_ws.clone() np.warnings.filterwarnings('ignore') # There can be a lot of warnings for bad solutions that get rejected. progress = Progress(self, 0.0, 1.0, len(peaksToFit)) + sigX0Params, sigY0, sigP0Params = self.getBVGInitialGuesses(peaks_ws, strongPeakParams_ws) for fitNumber, peakNumber in enumerate(peaksToFit):#range(peaks_ws.getNumberPeaks()): + peakNumber = int(peakNumber) peak = peaks_ws_out.getPeak(peakNumber) progress.report(' ') + if peak.getRunNumber() != MDdata.getExperimentInfo(0).getRunNumber(): + logger.warning('Peak number %i has run number %i but MDWorkspace is from run number %i. Skipping this peak.'%( + peakNumber, peak.getRunNumber(), MDdata.getExperimentInfo(0).getRunNumber())) + continue try: box = ICCFT.getBoxFracHKL(peak, peaks_ws, MDdata, UBMatrix, peakNumber, dQ, fracHKL=0.5, dQPixel=dQPixel, q_frame=q_frame) @@ -212,6 +259,7 @@ class IntegratePeaksProfileFitting(PythonAlgorithm): strongPeakParamsToSend = None else: strongPeakParamsToSend = strongPeakParams + # Will allow forced weak and edge peaks to be fit using a neighboring peak profile Y3D, goodIDX, pp_lambda, params = BVGFT.get3DPeak(peak, peaks_ws, box, padeCoefficients,qMask, nTheta=nTheta, nPhi=nPhi, plotResults=False, @@ -222,8 +270,8 @@ class IntegratePeaksProfileFitting(PythonAlgorithm): pplmin_frac=pplmin_frac, pplmax_frac=pplmax_frac, forceCutoff=forceCutoff, edgeCutoff=edgeCutoff, peakMaskSize=peakMaskSize, - iccFitDict=iccFitDict) - + iccFitDict=iccFitDict, sigX0Params=sigX0Params, + sigY0=sigY0, sigP0Params=sigP0Params, fitPenalty=1.e7) # First we get the peak intensity peakIDX = Y3D/Y3D.max() > fracStop intensity = np.sum(Y3D[peakIDX]) @@ -261,16 +309,28 @@ class IntegratePeaksProfileFitting(PythonAlgorithm): if generateStrongPeakParams and ~needsForcedProfile[peakNumber]: qPeak = peak.getQLabFrame() - strongPeakParams[fitNumber, 0] = np.arctan2(qPeak[1], qPeak[0]) # phi - strongPeakParams[fitNumber, 1] = np.arctan2(qPeak[2], np.hypot(qPeak[0],qPeak[1])) #2theta - strongPeakParams[fitNumber, 2] = params['scale3d'] - strongPeakParams[fitNumber, 3] = params['MuTH'] - strongPeakParams[fitNumber, 4] = params['MuPH'] - strongPeakParams[fitNumber, 5] = params['SigX'] - strongPeakParams[fitNumber, 6] = params['SigY'] - strongPeakParams[fitNumber, 7] = params['SigP'] - strongPeakParams[fitNumber, 8] = peakNumber - strongPeakParams_ws.addRow(strongPeakParams[fitNumber]) + theta = np.arctan2(qPeak[2], np.hypot(qPeak[0],qPeak[1])) #2theta + try: + p = mtd['__fitSigX0_Parameters'].column(1)[:-1] + tol = 0.2 #We should have a good idea now - only allow 20% variation + except: + p = peaks_ws.getInstrument().getStringParameter("sigSC0Params") + p = np.array(str(p).strip('[]\'').split(),dtype=float) + tol = 5.0 #High tolerance since we don't know what the answer will be + predSigX = BVGFT.coshPeakWidthModel(theta, p[0],p[1],p[2],p[3]) + + if np.abs((params['SigX'] - predSigX)/1./predSigX) < tol: + strongPeakParams[fitNumber, 0] = np.arctan2(qPeak[1], qPeak[0]) # phi + strongPeakParams[fitNumber, 1] = np.arctan2(qPeak[2], np.hypot(qPeak[0],qPeak[1])) #theta + strongPeakParams[fitNumber, 2] = params['scale3d'] + strongPeakParams[fitNumber, 3] = params['MuTH'] + strongPeakParams[fitNumber, 4] = params['MuPH'] + strongPeakParams[fitNumber, 5] = params['SigX'] + strongPeakParams[fitNumber, 6] = params['SigY'] + strongPeakParams[fitNumber, 7] = params['SigP'] + strongPeakParams[fitNumber, 8] = peakNumber + strongPeakParams_ws.addRow(strongPeakParams[fitNumber]) + sigX0Params, sigY0, sigP0Params = self.getBVGInitialGuesses(peaks_ws, strongPeakParams_ws) except KeyboardInterrupt: np.warnings.filterwarnings('default') # Re-enable on exit @@ -278,6 +338,7 @@ class IntegratePeaksProfileFitting(PythonAlgorithm): except: #raise + logger.warning('Error fitting peak number ' + str(peakNumber)) peak.setIntensity(0.0) peak.setSigmaIntensity(1.0) diff --git a/Framework/PythonInterface/plugins/algorithms/MaskAngle.py b/Framework/PythonInterface/plugins/algorithms/MaskAngle.py index aaecceabbb6c86e0b10fe73198545234efa6aeeb..da80b6d9255e4a1c9936f91249d2c267bcaaf0be 100644 --- a/Framework/PythonInterface/plugins/algorithms/MaskAngle.py +++ b/Framework/PythonInterface/plugins/algorithms/MaskAngle.py @@ -9,11 +9,12 @@ from __future__ import (absolute_import, division, print_function) import mantid.simpleapi import mantid.kernel import mantid.api +import mantid.geometry import numpy class MaskAngle(mantid.api.PythonAlgorithm): - """ Class to generate grouping file + """ Mask detectors between specified angles based on angle type required """ def category(self): @@ -48,24 +49,29 @@ class MaskAngle(mantid.api.PythonAlgorithm): mantid.kernel.StringListValidator(['TwoTheta', 'Phi']), 'Which angle to use') self.declareProperty(mantid.kernel.IntArrayProperty(name="MaskedDetectors", direction=mantid.kernel.Direction.Output), - doc="List of detector masked, with scatterin angles between MinAngle and MaxAngle") + doc="List of detector masked, with scattering angles between MinAngle and MaxAngle") def validateInputs(self): issues = dict() - ws = self.getProperty("Workspace").value - - try: - if type(ws).__name__ == "WorkspaceGroup": - for w in ws: - w.getInstrument().getSource().getPos() - else: - ws.getInstrument().getSource().getPos() - except (RuntimeError, ValueError, AttributeError, TypeError): + hasInstrument = True + if type(ws).__name__ == "WorkspaceGroup" and len(ws) > 0: + for item in ws: + hasInstrument = hasInstrument and len(item.componentInfo()) > 0 + else: + hasInstrument = len(ws.componentInfo()) > 0 + if not hasInstrument: issues["Workspace"] = "Workspace must have an associated instrument." - return issues + def _get_phi(self, spectra_pos): + ''' + The implementation here assumes that z is the beam direction. + That assumption is not universally true, it depends on the geometry configuration. + This returns the phi spherical coordinate value + ''' + return numpy.fabs(numpy.arctan2(spectra_pos.Y(), spectra_pos.X())) + def PyExec(self): ws = self.getProperty("Workspace").value ttmin = numpy.radians(self.getProperty("MinAngle").value) @@ -73,36 +79,28 @@ class MaskAngle(mantid.api.PythonAlgorithm): if ttmin > ttmax : raise ValueError("MinAngle > MaxAngle, please check angle range for masking") - angle = self.getProperty('Angle').value - - detlist=[] - - numspec = ws.getNumberHistograms() - spectrumInfo = ws.spectrumInfo() - - if angle == 'Phi': - for i in range(numspec): - if not spectrumInfo.isMonitor(i): - det = ws.getDetector(i) - phi=abs(det.getPhi()) - if phi>= ttmin and phi<= ttmax: - detlist.append(det.getID()) - else: - source=ws.getInstrument().getSource().getPos() - sample=ws.getInstrument().getSample().getPos() - beam = sample-source - for i in range(numspec): - if not spectrumInfo.isMonitor(i): - det = ws.getDetector(i) - tt=det.getTwoTheta(sample,beam) - if tt>= ttmin and tt<= ttmax: - detlist.append(det.getID()) - - if len(detlist)> 0: - mantid.simpleapi.MaskDetectors(Workspace=ws,DetectorList=detlist) - else: + angle_phi = self.getProperty('Angle').value == 'Phi' + spectrum_info = ws.spectrumInfo() + detector_info = ws.detectorInfo() + det_ids = detector_info.detectorIDs() + masked_ids = list() + for spectrum in spectrum_info: + if not spectrum.isMonitor: + # Get the first detector of spectrum. Ignore time aspects. + if angle_phi: + val = self._get_phi(spectrum.position) + else: + # Two theta + val =spectrum.twoTheta + if val>= ttmin and val<= ttmax: + detectors = spectrum.spectrumDefinition + for j in range(len(detectors)): + masked_ids.append(det_ids[detectors[j][0]]) + if not masked_ids: self.log().information("no detectors within this range") - self.setProperty("MaskedDetectors", numpy.array(detlist)) + else: + mantid.simpleapi.MaskDetectors(Workspace=ws,DetectorList=numpy.array(masked_ids)) + self.setProperty("MaskedDetectors", numpy.array(masked_ids)) mantid.api.AlgorithmFactory.subscribe(MaskAngle) diff --git a/Framework/PythonInterface/plugins/algorithms/ReflectometryReductionOneLiveData.py b/Framework/PythonInterface/plugins/algorithms/ReflectometryReductionOneLiveData.py index 539573fe2469e415486a7f4265a366bcfcd221d3..9a03ee3a086b858914503a3433c02226c4ba09e1 100644 --- a/Framework/PythonInterface/plugins/algorithms/ReflectometryReductionOneLiveData.py +++ b/Framework/PythonInterface/plugins/algorithms/ReflectometryReductionOneLiveData.py @@ -25,28 +25,30 @@ class ReflectometryReductionOneLiveData(DataProcessorAlgorithm): return 'Run the reflectometry reduction algorithm on live data' def seeAlso(self): - return [ "ReflectometryReductionOneAuto", "StartLiveData" ] + return ["ReflectometryReductionOneAuto", "StartLiveData"] def PyInit(self): instruments = ['CRISP', 'INTER', 'OFFSPEC', 'POLREF', 'SURF'] - instrument = defaultInstrument if config.getInstrument() in instruments else '' - self.declareProperty(name='Instrument', defaultValue=instrument, direction=Direction.Input, + defaultInstrument = str(config.getInstrument()) + defaultInstrument = defaultInstrument if defaultInstrument in instruments else instruments[0] + self.declareProperty(name='Instrument', defaultValue=defaultInstrument, direction=Direction.Input, validator=StringListValidator(instruments), doc='Instrument to find live value for.') - self.declareProperty(name='GetLiveValueAlgorithm', defaultValue='GetLiveInstrumentValue', direction=Direction.Input, + self.declareProperty(name='GetLiveValueAlgorithm', defaultValue='GetLiveInstrumentValue', + direction=Direction.Input, doc='The algorithm to use to get live values from the instrument') self._child_properties = [ - 'InputWorkspace', 'SummationType', 'ReductionType','IncludePartialBins', 'AnalysisMode', - 'ProcessingInstructions','CorrectDetectors', - 'DetectorCorrectionType','WavelengthMin','WavelengthMax','I0MonitorIndex', - 'MonitorBackgroundWavelengthMin','MonitorBackgroundWavelengthMax', - 'MonitorIntegrationWavelengthMin','MonitorIntegrationWavelengthMax', - 'NormalizeByIntegratedMonitors','FirstTransmissionRun', - 'SecondTransmissionRun','Params','StartOverlap','EndOverlap', - 'StrictSpectrumChecking','CorrectionAlgorithm','Polynomial','C0','C1', - 'MomentumTransferMin','MomentumTransferStep','MomentumTransferMax', - 'PolarizationAnalysis','Pp','Ap','Rho','Alpha','Debug','OutputWorkspace'] + 'InputWorkspace', 'SummationType', 'ReductionType', 'IncludePartialBins', + 'AnalysisMode', 'ProcessingInstructions', 'CorrectDetectors', + 'DetectorCorrectionType', 'WavelengthMin', 'WavelengthMax', 'I0MonitorIndex', + 'MonitorBackgroundWavelengthMin', 'MonitorBackgroundWavelengthMax', + 'MonitorIntegrationWavelengthMin', 'MonitorIntegrationWavelengthMax', + 'NormalizeByIntegratedMonitors', 'FirstTransmissionRun', + 'SecondTransmissionRun', 'Params', 'StartOverlap', 'EndOverlap', + 'CorrectionAlgorithm', 'Polynomial', 'C0', 'C1', + 'MomentumTransferMin', 'MomentumTransferStep', 'MomentumTransferMax', + 'PolarizationAnalysis', 'Pp', 'Ap', 'Rho', 'Alpha', 'Debug', 'OutputWorkspace'] self.copyProperties('ReflectometryReductionOneAuto', self._child_properties) def PyExec(self): @@ -83,20 +85,21 @@ class ReflectometryReductionOneLiveData(DataProcessorAlgorithm): """Create a workspace for the input/output to the reduction algorithm""" in_ws_name = self.getProperty("InputWorkspace").value.getName() self._ws_name = self.getPropertyValue("OutputWorkspace") - CloneWorkspace(InputWorkspace=in_ws_name,OutputWorkspace=self._ws_name) + CloneWorkspace(InputWorkspace=in_ws_name, OutputWorkspace=self._ws_name) def _setup_instrument(self): """Sets the instrument name and loads the instrument on the workspace""" self._instrument = self.getProperty('Instrument').value - LoadInstrument(Workspace=self._ws_name,RewriteSpectraMap=True,InstrumentName=self._instrument) + LoadInstrument(Workspace=self._ws_name, RewriteSpectraMap=True, + InstrumentName=self._instrument) def _setup_sample_logs(self, liveValues): """Set up the sample logs based on live values from the instrument""" logNames = [key for key in liveValues] logValues = [liveValues[key].value for key in liveValues] logUnits = [liveValues[key].unit for key in liveValues] - AddSampleLogMultiple(Workspace=self._ws_name,LogNames=logNames,LogValues=logValues, - LogUnits=logUnits) + AddSampleLogMultiple(Workspace=self._ws_name, LogNames=logNames, + LogValues=logValues, LogUnits=logUnits) def _setup_slits(self, liveValues): """Set up instrument parameters for the slits""" @@ -152,9 +155,9 @@ class ReflectometryReductionOneLiveData(DataProcessorAlgorithm): def _live_value_list(self): """Get the list of required live value names and their unit type""" - liveValues = {'Theta' : LiveValue(None, 'deg'), - self._s1vg_name() : LiveValue(None, 'm'), - self._s2vg_name() : LiveValue(None, 'm')} + liveValues = {'Theta': LiveValue(None, 'deg'), + self._s1vg_name(): LiveValue(None, 'm'), + self._s2vg_name(): LiveValue(None, 'm')} return liveValues def _get_block_value_from_instrument(self, logName): diff --git a/Framework/PythonInterface/plugins/algorithms/SNAPReduce.py b/Framework/PythonInterface/plugins/algorithms/SNAPReduce.py index 9788d6c3db5adf15c0eef77187129f378c12d009..445d2f5dddd1d8609bac91ca498bddf497a6ca67 100644 --- a/Framework/PythonInterface/plugins/algorithms/SNAPReduce.py +++ b/Framework/PythonInterface/plugins/algorithms/SNAPReduce.py @@ -268,8 +268,8 @@ class SNAPReduce(DataProcessorAlgorithm): maskFile = self.getProperty('MaskingFilename').value # TODO not reading the correct mask file geometry elif masking == 'Horizontal' or masking == 'Vertical': - maskWSname = masking + 'Mask' # append the work 'Mask' for the wksp name - if not mtd.doesExist(maskWSname): # only load if it isn't already loaded + maskWSname = masking + 'Mask' # append the work 'Mask' for the wksp name + if not mtd.doesExist(maskWSname): # only load if it isn't already loaded maskFile = '/SNS/SNAP/shared/libs/%s_Mask.xml' % masking if maskFile is not None: @@ -314,7 +314,7 @@ class SNAPReduce(DataProcessorAlgorithm): peak_clip_WS.setY(h, self.peak_clip(peak_clip_WS.readY(h), win=window, decrese=True, LLS=True, smooth_window=smooth_range)) return str(peak_clip_WS) - else: # other values are already held in normWS + else: # other values are already held in normWS return normWS def _save(self, saveDir, basename, outputWksp): @@ -348,20 +348,16 @@ class SNAPReduce(DataProcessorAlgorithm): return wsname def PyExec(self): - # Retrieve all relevant notice - in_Runs = self.getProperty("RunNumbers").value - maskWSname = self._getMaskWSname() - progress = Progress(self, 0., .25, 3) # default arguments for AlignAndFocusPowder - alignAndFocusArgs={'TMax':50000, - 'RemovePromptPulseWidth':1600, - 'PreserveEvents':False, - 'Dspacing':True, # binning parameters in d-space - 'Params':self.getProperty("Binning").value} + alignAndFocusArgs = {'TMax': 50000, + 'RemovePromptPulseWidth': 1600, + 'PreserveEvents': False, + 'Dspacing': True, # binning parameters in d-space + 'Params': self.getProperty("Binning").value} # workspace for loading metadata only to be used in LoadDiffCal and # CreateGroupingWorkspace @@ -412,7 +408,7 @@ class SNAPReduce(DataProcessorAlgorithm): progStart = .25 progDelta = (1.-progStart)/len(in_Runs) - for runnumber in in_Runs: + for i, runnumber in enumerate(in_Runs): self.log().notice("processing run %s" % runnumber) self.log().information(str(self.get_IPTS_Local(runnumber))) @@ -444,8 +440,8 @@ class SNAPReduce(DataProcessorAlgorithm): MaskWorkspace=maskWSname, # can be empty string GroupingWorkspace=group, UnfocussedWorkspace=unfocussedWksp, # can be empty string - startProgress = progStart, - endProgress = progStart + .5 * progDelta, + startProgress=progStart, + endProgress=progStart + .5 * progDelta, **alignAndFocusArgs) progStart += .5 * progDelta @@ -511,12 +507,17 @@ class SNAPReduce(DataProcessorAlgorithm): self.setProperty(propertyName, outputWksp) # declare some things as extra outputs in set-up - if Process_Mode != "Production": # TODO set them as workspace properties - propNames = ['OuputWorkspace_'+str(it) for it in ['d', 'norm', 'normalizer']] - wkspNames = ['%s_%s_d' % (new_Tag, runnumber), basename + '_red', '%s_%s_normalizer' % (new_Tag, runnumber)] + if Process_Mode != "Production": + prefix = 'OuputWorkspace_{:d}_'.format(i) + propNames = [prefix + it for it in ['d', 'norm', 'normalizer']] + wkspNames = ['%s_%s_d' % (new_Tag, runnumber), + basename + '_red', + '%s_%s_normalizer' % (new_Tag, runnumber)] for (propName, wkspName) in zip(propNames, wkspNames): if mtd.doesExist(wkspName): - self.declareProperty(WorkspaceProperty(propName, wkspName, Direction.Output)) + self.declareProperty(WorkspaceProperty(propName, + wkspName, + Direction.Output)) self.setProperty(propName, wkspName) diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/MSDFit.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/MSDFit.py index c2070b3f8379190e2cb2c970ece8d363ecdf41a2..3824447f5e1400ee7fb57850e62c91f2029e3023 100644 --- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/MSDFit.py +++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/MSDFit.py @@ -31,7 +31,8 @@ class MSDFit(DataProcessorAlgorithm): self.declareProperty(MatrixWorkspaceProperty('InputWorkspace', '', direction=Direction.Input), doc='Sample input workspace') self.declareProperty(name='Model', defaultValue='Gauss', - validator=StringListValidator(['Gauss', 'Peters', 'Yi']), + validator=StringListValidator( + ['Gauss', 'Peters', 'Yi']), doc='Model options : Gauss, Peters, Yi') self.declareProperty(name='XStart', defaultValue=0.0, @@ -100,9 +101,12 @@ class MSDFit(DataProcessorAlgorithm): progress = Progress(self, 0.0, 0.05, 3) self._original_ws = self._input_ws - rename_alg = self.createChildAlgorithm("RenameWorkspace", enableLogging=False) + rename_alg = self.createChildAlgorithm( + "RenameWorkspace", enableLogging=False) rename_alg.setProperty("InputWorkspace", self._input_ws) - rename_alg.setProperty("OutputWorkspace", self._input_ws + "_" + self._model) + rename_alg.setProperty( + "OutputWorkspace", + self._input_ws + "_" + self._model) rename_alg.execute() self._input_ws = self._input_ws + "_" + self._model input_params = [self._input_ws + ',i%d' % i for i in range(self._spec_range[0], @@ -111,19 +115,19 @@ class MSDFit(DataProcessorAlgorithm): # Fit line to each of the spectra if self._model == 'Gauss': logger.information('Model : Gaussian approximation') - function = 'name=MsdGauss, Height=1.0, MSD=0.1' - function += ',constraint=(Height>0.0, MSD>0.0)' - params_list = ['Height', 'MSD'] + function = 'name=MsdGauss, Height=1.0, Msd=0.1' + function += ',constraint=(Height>0.0, Msd>0.0)' + params_list = ['Height', 'Msd'] elif self._model == 'Peters': logger.information('Model : Peters & Kneller') - function = 'name=MsdPeters, Height=1.0, MSD=1.0, Beta=1.0' - function += ',constraint=(Height>0.0, MSD>0.0, 100.0>Beta>0.3)' - params_list = ['Height', 'MSD', 'Beta'] + function = 'name=MsdPeters, Height=1.0, Msd=1.0, Beta=1.0' + function += ',constraint=(Height>0.0, Msd>0.0, 100.0>Beta>0.3)' + params_list = ['Height', 'Msd', 'Beta'] elif self._model == 'Yi': logger.information('Model : Yi et al') - function = 'name=MsdYi, Height=1.0, MSD=1.0, Sigma=0.1' - function += ',constraint=(Height>0.0, MSD>0.0, Sigma>0.0)' - params_list = ['Height', 'MSD', 'Sigma'] + function = 'name=MsdYi, Height=1.0, Msd=1.0, Sigma=0.1' + function += ',constraint=(Height>0.0, Msd>0.0, Sigma>0.0)' + params_list = ['Height', 'Msd', 'Sigma'] else: raise ValueError('No Model defined') @@ -137,12 +141,20 @@ class MSDFit(DataProcessorAlgorithm): FitType='Sequential', CreateOutput=True) - delete_alg = self.createChildAlgorithm("DeleteWorkspace", enableLogging=False) - delete_alg.setProperty("Workspace", self._output_msd_ws + '_NormalisedCovarianceMatrices') + delete_alg = self.createChildAlgorithm( + "DeleteWorkspace", enableLogging=False) + delete_alg.setProperty( + "Workspace", + self._output_msd_ws + + '_NormalisedCovarianceMatrices') delete_alg.execute() - delete_alg.setProperty("Workspace", self._output_msd_ws + '_Parameters') + delete_alg.setProperty( + "Workspace", + self._output_msd_ws + + '_Parameters') delete_alg.execute() - rename_alg = self.createChildAlgorithm("RenameWorkspace", enableLogging=False) + rename_alg = self.createChildAlgorithm( + "RenameWorkspace", enableLogging=False) rename_alg.setProperty("InputWorkspace", self._output_msd_ws) rename_alg.setProperty("OutputWorkspace", self._output_param_ws) rename_alg.execute() @@ -154,31 +166,50 @@ class MSDFit(DataProcessorAlgorithm): for par in params_list: ws_name = self._output_msd_ws + '_' + par parameter_ws_group.append(ws_name) - convert_alg = self.createChildAlgorithm("ConvertTableToMatrixWorkspace", enableLogging=False) + convert_alg = self.createChildAlgorithm( + "ConvertTableToMatrixWorkspace", enableLogging=False) convert_alg.setProperty("InputWorkspace", self._output_param_ws) convert_alg.setProperty("OutputWorkspace", ws_name) convert_alg.setProperty("ColumnX", 'axis-1') convert_alg.setProperty("ColumnY", par) convert_alg.setProperty("ColumnE", par + '_Err') convert_alg.execute() - mtd.addOrReplace(ws_name, convert_alg.getProperty("OutputWorkspace").value) - - append_alg = self.createChildAlgorithm("AppendSpectra", enableLogging=False) - append_alg.setProperty("InputWorkspace1", self._output_msd_ws + '_' + params_list[0]) - append_alg.setProperty("InputWorkspace2", self._output_msd_ws + '_' + params_list[1]) + mtd.addOrReplace( + ws_name, convert_alg.getProperty("OutputWorkspace").value) + + append_alg = self.createChildAlgorithm( + "AppendSpectra", enableLogging=False) + append_alg.setProperty( + "InputWorkspace1", + self._output_msd_ws + + '_' + + params_list[0]) + append_alg.setProperty( + "InputWorkspace2", + self._output_msd_ws + + '_' + + params_list[1]) append_alg.setProperty("ValidateInputs", False) append_alg.setProperty("OutputWorkspace", self._output_msd_ws) append_alg.execute() - mtd.addOrReplace(self._output_msd_ws, append_alg.getProperty("OutputWorkspace").value) + mtd.addOrReplace( + self._output_msd_ws, + append_alg.getProperty("OutputWorkspace").value) if len(params_list) > 2: append_alg.setProperty("InputWorkspace1", self._output_msd_ws) - append_alg.setProperty("InputWorkspace2", self._output_msd_ws + '_' + params_list[2]) + append_alg.setProperty( + "InputWorkspace2", + self._output_msd_ws + + '_' + + params_list[2]) append_alg.setProperty("ValidateInputs", False) append_alg.setProperty("OutputWorkspace", self._output_msd_ws) append_alg.execute() - mtd.addOrReplace(self._output_msd_ws, append_alg.getProperty("OutputWorkspace").value) + mtd.addOrReplace(self._output_msd_ws, + append_alg.getProperty("OutputWorkspace").value) for par in params_list: - delete_alg.setProperty("Workspace", self._output_msd_ws + '_' + par) + delete_alg.setProperty( + "Workspace", self._output_msd_ws + '_' + par) delete_alg.execute() progress.report('Change axes') @@ -187,7 +218,9 @@ class MSDFit(DataProcessorAlgorithm): sort_alg.setProperty("InputWorkspace", self._output_msd_ws) sort_alg.setProperty("OutputWorkspace", self._output_msd_ws) sort_alg.execute() - mtd.addOrReplace(self._output_msd_ws, sort_alg.getProperty("OutputWorkspace").value) + mtd.addOrReplace( + self._output_msd_ws, + sort_alg.getProperty("OutputWorkspace").value) # Create a new x axis for the Q and Q**2 workspaces xunit = mtd[self._output_msd_ws].getAxis(0).setUnit('Label') xunit.setLabel('Temperature', 'K') @@ -200,7 +233,10 @@ class MSDFit(DataProcessorAlgorithm): # Rename fit workspace group original_fit_ws_name = self._output_msd_ws + '_Workspaces' if original_fit_ws_name != self._output_fit_ws: - rename_alg.setProperty("InputWorkspace", self._output_msd_ws + '_Workspaces') + rename_alg.setProperty( + "InputWorkspace", + self._output_msd_ws + + '_Workspaces') rename_alg.setProperty("OutputWorkspace", self._output_fit_ws) rename_alg.execute() @@ -213,7 +249,8 @@ class MSDFit(DataProcessorAlgorithm): copy_alg.setProperty("OutputWorkspace", self._output_fit_ws) copy_alg.execute() - rename_alg = self.createChildAlgorithm("RenameWorkspace", enableLogging=False) + rename_alg = self.createChildAlgorithm( + "RenameWorkspace", enableLogging=False) rename_alg.setProperty("InputWorkspace", self._input_ws) rename_alg.setProperty("OutputWorkspace", self._original_ws) rename_alg.execute() diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLConvertToQ.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLConvertToQ.py index f9a50e21ef998c0eea9196176a4ccd07aadbe600..124e0d779b64c386ac65f771c462f5144d336851 100644 --- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLConvertToQ.py +++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLConvertToQ.py @@ -8,7 +8,7 @@ from __future__ import (absolute_import, division, print_function) -from mantid.api import (AlgorithmFactory, DataProcessorAlgorithm, MatrixWorkspaceProperty, PropertyMode, WorkspaceUnitValidator) +from mantid.api import (AlgorithmFactory, DataProcessorAlgorithm, MatrixWorkspaceProperty, WorkspaceUnitValidator) from mantid.kernel import (Direction, FloatBoundedValidator, Property, StringListValidator) from mantid.simpleapi import (ConvertToPointData, CreateWorkspace, Divide, ReflectometryMomentumTransfer) import numpy @@ -17,13 +17,10 @@ import ReflectometryILL_common as common class Prop: CLEANUP = 'Cleanup' - DIRECT_WS = 'DirectBeamWorkspace' DIRECT_FOREGROUND_WS = 'DirectForegroundWorkspace' GROUPING_FRACTION = 'GroupingQFraction' INPUT_WS = 'InputWorkspace' OUTPUT_WS = 'OutputWorkspace' - REFLECTED_WS = 'ReflectedBeamWorkspace' - POLARIZED = 'Polarized' SUBALG_LOGGING = 'SubalgorithmLogging' @@ -63,101 +60,96 @@ class ReflectometryILLConvertToQ(DataProcessorAlgorithm): wsPrefix = self.getPropertyValue(Prop.OUTPUT_WS) self._names = common.WSNameSource(wsPrefix, cleanupMode) - ws, directWS, directForegroundWS = self._inputWS() + ws, directWS = self._inputWS() ws = self._correctForChopperOpenings(ws, directWS) - ws = self._convertToMomentumTransfer(ws, directWS) - if directForegroundWS is not None: - directForegroundWS = self._sameQAndDQ(ws, directForegroundWS, 'direct_') + ws = self._convertToMomentumTransfer(ws) + + sumInLambda = self._sumType(ws.run()) == 'SumInLambda' + if sumInLambda: + directWS = self._sameQAndDQ(ws, directWS, 'direct_') ws = self._toPointData(ws) ws = self._groupPoints(ws) - if directForegroundWS is not None: - directForegroundWS = self._toPointData(directForegroundWS, 'direct_') - directForegroundWS = self._groupPoints(directForegroundWS, 'direct_') - ws = self._divideByDirect(ws, directForegroundWS) + if sumInLambda: + directWS = self._toPointData(directWS, 'direct_') + directWS = self._groupPoints(directWS, 'direct_') + ws = self._divideByDirect(ws, directWS) self._finalize(ws) def PyInit(self): """Initialize the input and output properties of the algorithm.""" positiveFloat = FloatBoundedValidator(lower=0., exclusive=True) - self.declareProperty(MatrixWorkspaceProperty(Prop.INPUT_WS, - defaultValue='', - direction=Direction.Input, - validator=WorkspaceUnitValidator('Wavelength')), - doc='A reflectivity workspace in wavelength to be converted to Q.') - self.declareProperty(MatrixWorkspaceProperty(Prop.OUTPUT_WS, - defaultValue='', - direction=Direction.Output), - doc='The input workspace in momentum transfer.') - self.declareProperty(Prop.SUBALG_LOGGING, - defaultValue=SubalgLogging.OFF, - validator=StringListValidator([SubalgLogging.OFF, SubalgLogging.ON]), - doc='Enable or disable child algorithm logging.') - self.declareProperty(Prop.CLEANUP, - defaultValue=common.WSCleanup.ON, - validator=StringListValidator([common.WSCleanup.ON, common.WSCleanup.OFF]), - doc='Enable or disable intermediate workspace cleanup.') - self.declareProperty(MatrixWorkspaceProperty(Prop.REFLECTED_WS, - defaultValue='', - direction=Direction.Input, - validator=WorkspaceUnitValidator('Wavelength')), - doc='A non-summed reflected beam workspace, needed for Q resolution calculation.') - self.declareProperty(MatrixWorkspaceProperty(Prop.DIRECT_WS, - defaultValue='', - direction=Direction.Input, - validator=WorkspaceUnitValidator('Wavelength')), - doc='A non-summed direct beam workspace, needed for Q resolution calculation.') - self.declareProperty(MatrixWorkspaceProperty(Prop.DIRECT_FOREGROUND_WS, - defaultValue='', - direction=Direction.Input, - optional=PropertyMode.Optional, - validator=WorkspaceUnitValidator('Wavelength')), - doc='Summed direct beam workspace for finalizing the reflectivity calculation in SumInLambda case.') - self.declareProperty(Prop.POLARIZED, - defaultValue=False, - doc='True if input workspace has been corrected for polarization efficiencies.') - self.declareProperty(Prop.GROUPING_FRACTION, - defaultValue=Property.EMPTY_DBL, - validator=positiveFloat, - doc='If set, group the output by steps of this fraction multiplied by Q resolution') + self.declareProperty( + MatrixWorkspaceProperty( + Prop.INPUT_WS, + defaultValue='', + direction=Direction.Input, + validator=WorkspaceUnitValidator('Wavelength')), + doc='A reflectivity workspace in wavelength to be converted to Q.') + self.declareProperty( + MatrixWorkspaceProperty( + Prop.OUTPUT_WS, + defaultValue='', + direction=Direction.Output), + doc='The input workspace in momentum transfer.') + self.declareProperty( + Prop.SUBALG_LOGGING, + defaultValue=SubalgLogging.OFF, + validator=StringListValidator([SubalgLogging.OFF, SubalgLogging.ON]), + doc='Enable or disable child algorithm logging.') + self.declareProperty( + Prop.CLEANUP, + defaultValue=common.WSCleanup.ON, + validator=StringListValidator([common.WSCleanup.ON, common.WSCleanup.OFF]), + doc='Enable or disable intermediate workspace cleanup.') + self.declareProperty( + MatrixWorkspaceProperty( + Prop.DIRECT_FOREGROUND_WS, + defaultValue='', + direction=Direction.Input, + validator=WorkspaceUnitValidator('Wavelength')), + doc='Summed direct beam workspace.') + self.declareProperty( + Prop.GROUPING_FRACTION, + defaultValue=Property.EMPTY_DBL, + validator=positiveFloat, + doc='If set, group the output by steps of this fraction multiplied by Q resolution') def validateInputs(self): """Validate the input properties.""" issues = dict() inputWS = self.getProperty(Prop.INPUT_WS).value - run = inputWS.run() - if run.hasProperty(common.SampleLogs.SUM_TYPE): - sumType = run.getProperty(common.SampleLogs.SUM_TYPE).value - if sumType == 'SumInLambda' and self.getProperty(Prop.DIRECT_FOREGROUND_WS).isDefault: - issues[Prop.DIRECT_FOREGROUND_WS] = 'DirectForegroundWorkspace needed for SumInLambda mode.' if inputWS.getNumberHistograms() != 1: issues[Prop.INPUT_WS] = 'The workspace should have only a single histogram. Was foreground summation forgotten?' - if not self.getProperty(Prop.DIRECT_FOREGROUND_WS).isDefault: - directWS = self.getProperty(Prop.DIRECT_FOREGROUND_WS).value - if directWS.getNumberHistograms() != 1: - issues[Prop.DIRECT_FOREGROUND_WS] = 'The workspace should have only a single histogram. Was foreground summation forgotten?' - if directWS.blocksize() != inputWS.blocksize(): - issues[Prop.DIRECT_FOREGROUND_WS] = 'Number of bins does not match with InputWorkspace.' - directXs = directWS.readX(0) - inputXs = inputWS.readX(0) - if directXs[0] != inputXs[0] or directXs[-1] != inputXs[-1]: - issues[Prop.DIRECT_FOREGROUND_WS] = 'Binning does not match with InputWorkspace.' + directWS = self.getProperty(Prop.DIRECT_FOREGROUND_WS).value + if directWS.getNumberHistograms() != 1: + issues[Prop.DIRECT_FOREGROUND_WS] = 'The workspace should have only a single histogram. Was foreground summation forgotten?' + run = inputWS.run() + if not run.hasProperty(common.SampleLogs.SUM_TYPE): + issues[Prop.INPUT_WS] = "'" + common.SampleLogs.SUM_TYPE + "' entry missing in sample logs" + else: + sumType = run.getProperty(common.SampleLogs.SUM_TYPE).value + if sumType not in ['SumInLambda', 'SumInQ']: + issues[Prop.INPUT_WS] = "Unknown sum type in sample logs: '" + sumType + "'. Allowed values: 'SumInLambda' or 'SumInQ'." + else: + if sumType == 'SumInLambda': + if directWS.blocksize() != inputWS.blocksize(): + issues[Prop.DIRECT_FOREGROUND_WS] = 'Number of bins does not match with InputWorkspace.' + directXs = directWS.readX(0) + inputXs = inputWS.readX(0) + if directXs[0] != inputXs[0] or directXs[-1] != inputXs[-1]: + issues[Prop.DIRECT_FOREGROUND_WS] = 'Binning does not match with InputWorkspace.' return issues - def _convertToMomentumTransfer(self, ws, directWS): + def _convertToMomentumTransfer(self, ws): """Convert the X units of ws to momentum transfer.""" - reflectedWS = self.getProperty(Prop.REFLECTED_WS).value - reflectedForeground = self._foreground(reflectedWS.run()) - directForeground = self._foreground(directWS.run()) logs = ws.run() - instrumentName = ws.getInstrument().getName() - if instrumentName != 'D17' and instrumentName != 'FIGARO': - raise RuntimeError('Unrecognized instrument {}. Only D17 and FIGARO are supported.'.format(instrumentName)) + reflectedForeground = self._foreground(logs) + instrumentName = common.instrumentName(ws) sumType = logs.getProperty(common.SampleLogs.SUM_TYPE).value - polarized = self.getProperty(Prop.POLARIZED).value - pixelSize = 0.001195 if instrumentName == 'D17' else 0.0012 + pixelSize = common.pixelSize(instrumentName) detResolution = common.detectorResolution() chopperSpeed = common.chopperSpeed(logs, instrumentName) chopperOpening = common.chopperOpeningAngle(logs, instrumentName) @@ -170,12 +162,8 @@ class ReflectometryILLConvertToQ(DataProcessorAlgorithm): qWS = ReflectometryMomentumTransfer( InputWorkspace=ws, OutputWorkspace=qWSName, - ReflectedBeamWorkspace=reflectedWS, - ReflectedForeground=reflectedForeground, - DirectBeamWorkspace=directWS, - DirectForeground=directForeground, SummationType=sumType, - Polarized=polarized, + ReflectedForeground=reflectedForeground, PixelSize=pixelSize, DetectorResolution=detResolution, ChopperSpeed=chopperSpeed, @@ -202,14 +190,15 @@ class ReflectometryILLConvertToQ(DataProcessorAlgorithm): self._cleanup.cleanup(ws) self._cleanup.finalCleanup() - def _divideByDirect(self, ws, directForegroundWS): + def _divideByDirect(self, ws, directWS): """Divide ws by the direct beam.""" reflectivityWSName = self._names.withSuffix('reflectivity') - reflectivityWS = Divide(LHSWorkspace=ws, - RHSWorkspace=directForegroundWS, - OutputWorkspace=reflectivityWSName, - EnableLogging=self._subalgLogging) - self._cleanup.cleanup(directForegroundWS) + reflectivityWS = Divide( + LHSWorkspace=ws, + RHSWorkspace=directWS, + OutputWorkspace=reflectivityWSName, + EnableLogging=self._subalgLogging) + self._cleanup.cleanup(directWS) reflectivityWS.setYUnit('Reflectivity') reflectivityWS.setYUnitLabel('Reflectivity') # The X error data is lost in Divide. @@ -274,28 +263,28 @@ class ReflectometryILLConvertToQ(DataProcessorAlgorithm): """Return the input workspace.""" ws = self.getProperty(Prop.INPUT_WS).value self._cleanup.protect(ws) - directWS = self.getProperty(Prop.DIRECT_WS).value + directWS = self.getProperty(Prop.DIRECT_FOREGROUND_WS).value self._cleanup.protect(directWS) - directForegroundWS = None - if not self.getProperty(Prop.DIRECT_FOREGROUND_WS).isDefault: - directForegroundWS = self.getProperty(Prop.DIRECT_FOREGROUND_WS).value - self._cleanup.protect(directForegroundWS) - return ws, directWS, directForegroundWS - - def _sameQAndDQ(self, ws, directForegroundWS, extraLabel=''): - """Create a new workspace with Y and E from directForegroundWS and X and DX data from ws.""" + return ws, directWS + + def _sameQAndDQ(self, ws, directWS, extraLabel=''): + """Create a new workspace with Y and E from directWS and X and DX data from ws.""" qWSName = self._names.withSuffix(extraLabel + 'in_momentum_transfer') qWS = CreateWorkspace( OutputWorkspace=qWSName, DataX=ws.readX(0), - DataY=directForegroundWS.readY(0)[::-1], # Invert data because wavelength is inversely proportional to Q. - DataE=directForegroundWS.readE(0)[::-1], + DataY=directWS.readY(0)[::-1], # Invert data because wavelength is inversely proportional to Q. + DataE=directWS.readE(0)[::-1], Dx=ws.readDx(0), UnitX=ws.getAxis(0).getUnit().unitID(), - ParentWorkspace=directForegroundWS, + ParentWorkspace=directWS, EnableLogging=self._subalgLogging) return qWS + def _sumType(self, logs): + """Return the sum type applied to ws.""" + return logs.getProperty(common.SampleLogs.SUM_TYPE).value + def _TOFChannelWidth(self, sampleLogs): """Return the time of flight bin width.""" return sampleLogs.getProperty('PSD.time_of_flight_0').value diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLSumForeground.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLSumForeground.py index 0ecc5ce8d69833204efecb44d771d2535c102fbc..fcc7f244ee7899705ad1aa714d1e807cc5073328 100644 --- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLSumForeground.py +++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLSumForeground.py @@ -11,21 +11,16 @@ from __future__ import (absolute_import, division, print_function) from mantid.api import (AlgorithmFactory, DataProcessorAlgorithm, MatrixWorkspaceProperty, PropertyMode, WorkspaceUnitValidator) from mantid.kernel import (CompositeValidator, Direction, FloatArrayBoundedValidator, FloatArrayProperty, IntArrayBoundedValidator, IntArrayLengthValidator, IntArrayProperty, Property, StringListValidator) -from mantid.simpleapi import (AddSampleLog, CropWorkspace, Divide, ExtractSingleSpectrum, RebinToWorkspace, ReflectometrySumInQ) +from mantid.simpleapi import (AddSampleLog, CropWorkspace, Divide, ExtractSingleSpectrum, RebinToWorkspace,ReflectometryBeamStatistics, + ReflectometrySumInQ) import numpy import ReflectometryILL_common as common -class Sample: - AUTO = 'Sample Flatness AUTO' # To be used in the future. - BENT = 'Bent Sample' - FLAT = 'Flat Sample' - - class Prop: CLEANUP = 'Cleanup' + DIRECT_WS = 'DirectBeamWorkspace' DIRECT_FOREGROUND_WS = 'DirectForegroundWorkspace' - FLAT_SAMPLE = 'FlatSample' FOREGROUND_INDICES = 'Foreground' INPUT_WS = 'InputWorkspace' OUTPUT_WS = 'OutputWorkspace' @@ -75,17 +70,21 @@ class ReflectometryILLSumForeground(DataProcessorAlgorithm): self._names = common.WSNameSource(wsPrefix, cleanupMode) ws = self._inputWS() + processReflected = not self._directOnly() + if processReflected: + self._addBeamStatisticsToLogs(ws) sumType = self._sumType() if sumType == SumType.IN_LAMBDA: ws = self._sumForegroundInLambda(ws) self._addSumTypeToLogs(ws, SumType.IN_LAMBDA) - ws = self._rebinToDirect(ws) + if processReflected: + ws = self._rebinToDirect(ws) else: - ws = self._divideByDirect(ws) + if processReflected: + ws = self._divideByDirect(ws) ws = self._sumForegroundInQ(ws) self._addSumTypeToLogs(ws, SumType.IN_Q) - ws = self._applyWavelengthRange(ws) self._finalize(ws) @@ -99,46 +98,63 @@ class ReflectometryILLSumForeground(DataProcessorAlgorithm): threeNonnegativeInts.add(nonnegativeInts) nonnegativeFloatArray = FloatArrayBoundedValidator() nonnegativeFloatArray.setLower(0.) - - self.declareProperty(MatrixWorkspaceProperty(Prop.INPUT_WS, - defaultValue='', - direction=Direction.Input, - validator=WorkspaceUnitValidator('Wavelength')), - doc='An input workspace (units wavelength) to be integrated.') - self.declareProperty(MatrixWorkspaceProperty(Prop.OUTPUT_WS, - defaultValue='', - direction=Direction.Output), - doc='The integrated foreground divided by the summed direct beam.') - self.declareProperty(Prop.SUBALG_LOGGING, - defaultValue=SubalgLogging.OFF, - validator=StringListValidator([SubalgLogging.OFF, SubalgLogging.ON]), - doc='Enable or disable child algorithm logging.') - self.declareProperty(Prop.CLEANUP, - defaultValue=common.WSCleanup.ON, - validator=StringListValidator([common.WSCleanup.ON, common.WSCleanup.OFF]), - doc='Enable or disable intermediate workspace cleanup.') - self.declareProperty(Prop.SUM_TYPE, - defaultValue=SumType.IN_LAMBDA, - validator=StringListValidator([SumType.IN_LAMBDA, SumType.IN_Q]), - doc='Type of summation to perform.') - self.declareProperty(Prop.FLAT_SAMPLE, - defaultValue=Sample.FLAT, - validator=StringListValidator([Sample.FLAT, Sample.BENT]), - doc='For SumInQ option, determines if the summation should be done for a flat or bent sample.') - self.declareProperty(MatrixWorkspaceProperty(Prop.DIRECT_FOREGROUND_WS, - defaultValue='', - direction=Direction.Input, - optional=PropertyMode.Optional, - validator=WorkspaceUnitValidator('Wavelength')), - doc='Summed direct beam workspace if output in reflectivity is required.') - self.declareProperty(IntArrayProperty(Prop.FOREGROUND_INDICES, - values=[Property.EMPTY_INT, Property.EMPTY_INT, Property.EMPTY_INT], - validator=threeNonnegativeInts), - doc='A three element array of foreground start, centre and end workspace indices.') - self.declareProperty(FloatArrayProperty(Prop.WAVELENGTH_RANGE, - values=[0.], - validator=nonnegativeFloatArray), - doc='The wavelength bounds when summing in Q.') + inWavelength = WorkspaceUnitValidator('Wavelength') + self.declareProperty( + MatrixWorkspaceProperty( + Prop.INPUT_WS, + defaultValue='', + direction=Direction.Input, + validator=inWavelength), + doc='A reflected beam workspace (units wavelength).') + self.declareProperty( + MatrixWorkspaceProperty( + Prop.OUTPUT_WS, + defaultValue='', + direction=Direction.Output), + doc='The summed foreground workspace.') + self.declareProperty( + Prop.SUBALG_LOGGING, + defaultValue=SubalgLogging.OFF, + validator=StringListValidator([SubalgLogging.OFF, SubalgLogging.ON]), + doc='Enable or disable child algorithm logging.') + self.declareProperty( + Prop.CLEANUP, + defaultValue=common.WSCleanup.ON, + validator=StringListValidator([common.WSCleanup.ON, common.WSCleanup.OFF]), + doc='Enable or disable intermediate workspace cleanup.') + self.declareProperty( + Prop.SUM_TYPE, + defaultValue=SumType.IN_LAMBDA, + validator=StringListValidator([SumType.IN_LAMBDA, SumType.IN_Q]), + doc='Type of summation to perform.') + self.declareProperty( + MatrixWorkspaceProperty( + Prop.DIRECT_FOREGROUND_WS, + defaultValue='', + direction=Direction.Input, + optional=PropertyMode.Optional, + validator=inWavelength), + doc='Summed direct beam workspace.') + self.declareProperty( + IntArrayProperty( + Prop.FOREGROUND_INDICES, + values=[Property.EMPTY_INT, Property.EMPTY_INT, Property.EMPTY_INT], + validator=threeNonnegativeInts), + doc='A three element array of foreground start, centre and end workspace indices.') + self.declareProperty( + MatrixWorkspaceProperty( + Prop.DIRECT_WS, + defaultValue='', + direction=Direction.Input, + optional=PropertyMode.Optional, + validator=inWavelength), + doc='The (not summed) direct beam workspace in wavelength.') + self.declareProperty( + FloatArrayProperty( + Prop.WAVELENGTH_RANGE, + values=[0.], + validator=nonnegativeFloatArray), + doc='The wavelength bounds when summing in Q.') def validateInputs(self): """Validate the algorithm's input properties.""" @@ -150,6 +166,8 @@ class ReflectometryILLSumForeground(DataProcessorAlgorithm): directWS = self.getProperty(Prop.DIRECT_FOREGROUND_WS).value if directWS.getNumberHistograms() != 1: issues[Prop.DIRECT_FOREGROUND_WS] = 'The workspace should have only a single histogram. Was foreground summation forgotten?' + if self.getProperty(Prop.DIRECT_WS).isDefault: + issues[Prop.DIRECT_WS] = 'The direct beam workspace is needed for processing the reflected workspace.' wRange = self.getProperty(Prop.WAVELENGTH_RANGE).value if len(wRange) == 2 and wRange[0] >= wRange[1]: issues[Prop.WAVELENGTH_RANGE] = 'Upper limit is smaller than the lower limit.' @@ -157,6 +175,29 @@ class ReflectometryILLSumForeground(DataProcessorAlgorithm): issues[Prop.WAVELENGTH_RANGE] = 'The range should be in the form [min] or [min, max].' return issues + def _addBeamStatisticsToLogs(self, ws): + """Calculate beam statistics and add the results to the sample logs.""" + reflectedForeground = self._foregroundIndices(ws) + directWS = self.getProperty(Prop.DIRECT_WS).value + directForeground = self._foregroundIndices(directWS) + instrumentName = common.instrumentName(ws) + pixelSize = common.pixelSize(instrumentName) + detResolution = common.detectorResolution() + firstSlitSizeLog = common.slitSizeLogEntry(instrumentName, 1) + secondSlitSizeLog = common.slitSizeLogEntry(instrumentName, 2) + ReflectometryBeamStatistics( + ReflectedBeamWorkspace=ws, + ReflectedForeground=reflectedForeground, + DirectBeamWorkspace=directWS, + DirectForeground=directForeground, + PixelSize=pixelSize, + DetectorResolution=detResolution, + FirstSlitName='slit2', + FirstSlitSizeSampleLog=firstSlitSizeLog, + SecondSlitName='slit3', + SecondSlitSizeSampleLog=secondSlitSizeLog, + EnableLogging=self._subalgLogging) + def _addSumTypeToLogs(self, ws, sumType): """Add a sum type entry to sample logs.""" AddSampleLog( @@ -175,30 +216,28 @@ class ReflectometryILLSumForeground(DataProcessorAlgorithm): if len(wRange) == 2: rangeProp['XMax'] = wRange[1] croppedWSName = self._names.withSuffix('cropped') - croppedWS = CropWorkspace(InputWorkspace=ws, - OutputWorkspace=croppedWSName, - EnableLogging=self._subalgLogging, - **rangeProp) + croppedWS = CropWorkspace( + InputWorkspace=ws, + OutputWorkspace=croppedWSName, + EnableLogging=self._subalgLogging, + **rangeProp) self._cleanup.cleanup(ws) return croppedWS - def _checkIfFlatSample(self): - """Returns true if sample is deemed 'flat' for SumInQ.""" - flatness = self.getProperty(Prop.FLAT_SAMPLE).value - # The 'Sample Flatness AUTO' option should calculate the answer here someday. - return flatness == Sample.FLAT + def _directOnly(self): + """Return true if only the direct beam should be processed.""" + return self.getProperty(Prop.DIRECT_FOREGROUND_WS).isDefault def _divideByDirect(self, ws): """Divide ws by the direct beam.""" - if self.getProperty(Prop.DIRECT_FOREGROUND_WS).isDefault: - return ws ws = self._rebinToDirect(ws) directWS = self.getProperty(Prop.DIRECT_FOREGROUND_WS).value reflectivityWSName = self._names.withSuffix('reflectivity') - reflectivityWS = Divide(LHSWorkspace=ws, - RHSWorkspace=directWS, - OutputWorkspace=reflectivityWSName, - EnableLogging=self._subalgLogging) + reflectivityWS = Divide( + LHSWorkspace=ws, + RHSWorkspace=directWS, + OutputWorkspace=reflectivityWSName, + EnableLogging=self._subalgLogging) self._cleanup.cleanup(ws) reflectivityWS = common.correctForChopperOpenings(reflectivityWS, directWS, self._names, self._cleanup, self._subalgLogging) reflectivityWS.setYUnit('Reflectivity') @@ -229,21 +268,20 @@ class ReflectometryILLSumForeground(DataProcessorAlgorithm): return [start, centre, end] def _inputWS(self): - """Return the input workspace.""" + """Return the input workspaces.""" ws = self.getProperty(Prop.INPUT_WS).value self._cleanup.protect(ws) return ws def _rebinToDirect(self, ws): """Rebin ws to direct foreground.""" - if self.getProperty(Prop.DIRECT_FOREGROUND_WS).isDefault: - return ws directWS = self.getProperty(Prop.DIRECT_FOREGROUND_WS).value rebinnedWSName = self._names.withSuffix('rebinned') - rebinnedWS = RebinToWorkspace(WorkspaceToRebin=ws, - WorkspaceToMatch=directWS, - OutputWorkspace=rebinnedWSName, - EnableLogging=self._subalgLogging) + rebinnedWS = RebinToWorkspace( + WorkspaceToRebin=ws, + WorkspaceToMatch=directWS, + OutputWorkspace=rebinnedWSName, + EnableLogging=self._subalgLogging) self._cleanup.cleanup(ws) return rebinnedWS @@ -253,10 +291,11 @@ class ReflectometryILLSumForeground(DataProcessorAlgorithm): sumIndices = [i for i in range(foreground[0], foreground[2] + 1)] beamPosIndex = foreground[1] foregroundWSName = self._names.withSuffix('foreground_grouped') - foregroundWS = ExtractSingleSpectrum(InputWorkspace=ws, - OutputWorkspace=foregroundWSName, - WorkspaceIndex=beamPosIndex, - EnableLogging=self._subalgLogging) + foregroundWS = ExtractSingleSpectrum( + InputWorkspace=ws, + OutputWorkspace=foregroundWSName, + WorkspaceIndex=beamPosIndex, + EnableLogging=self._subalgLogging) maxIndex = ws.getNumberHistograms() - 1 foregroundYs = foregroundWS.dataY(0) foregroundEs = foregroundWS.dataE(0) @@ -267,14 +306,16 @@ class ReflectometryILLSumForeground(DataProcessorAlgorithm): if i < 0 or i > maxIndex: self.log().warning('Foreground partially out of the workspace.') addeeWSName = self._names.withSuffix('foreground_addee') - addeeWS = ExtractSingleSpectrum(InputWorkspace=ws, - OutputWorkspace=addeeWSName, - WorkspaceIndex=i, - EnableLogging=self._subalgLogging) - addeeWS = RebinToWorkspace(WorkspaceToRebin=addeeWS, - WorkspaceToMatch=foregroundWS, - OutputWorkspace=addeeWSName, - EnableLogging=self._subalgLogging) + addeeWS = ExtractSingleSpectrum( + InputWorkspace=ws, + OutputWorkspace=addeeWSName, + WorkspaceIndex=i, + EnableLogging=self._subalgLogging) + addeeWS = RebinToWorkspace( + WorkspaceToRebin=addeeWS, + WorkspaceToMatch=foregroundWS, + OutputWorkspace=addeeWSName, + EnableLogging=self._subalgLogging) ys = addeeWS.readY(0) foregroundYs += ys es = addeeWS.readE(0) @@ -289,7 +330,7 @@ class ReflectometryILLSumForeground(DataProcessorAlgorithm): foreground = self._foregroundIndices(ws) sumIndices = [i for i in range(foreground[0], foreground[2] + 1)] beamPosIndex = foreground[1] - isFlatSample = self._checkIfFlatSample() + isFlatSample = not ws.run().getProperty('beam_stats.bent_sample').value sumWSName = self._names.withSuffix('summed_in_Q') sumWS = ReflectometrySumInQ( InputWorkspace=ws, diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ReflectometryILL_common.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ReflectometryILL_common.py index 22f2340955f42c2cfcfff8e4a5cd900925a23484..5f7baac47245b3552d26c7b5f14cfc87c1388262 100644 --- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ReflectometryILL_common.py +++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/ReflectometryILL_common.py @@ -87,9 +87,15 @@ def correctForChopperOpenings(ws, directWS, names, cleanup, logging): def detectorResolution(): + """Return the detector resolution in mm.""" return 0.0022 +def pixelSize(instrumentName): + """Return the pixel size in mm.""" + return 0.001195 if instrumentName == 'D17' else 0.0012 + + def slitSizeLogEntry(instrumentName, slitNumber): """Return the sample log entry which contains the slit size for the given slit""" if slitNumber not in [1, 2]: @@ -98,6 +104,14 @@ def slitSizeLogEntry(instrumentName, slitNumber): return entry.format(slitNumber + 1) +def instrumentName(ws): + """Return the instrument's name validating it is either D17 or FIGARO.""" + name = ws.getInstrument().getName() + if name != 'D17' and name != 'FIGARO': + raise RuntimeError('Unrecognized instrument {}. Only D17 and FIGARO are supported.'.format(name)) + return name + + class SampleLogs: FOREGROUND_CENTRE = 'foreground.centre_workspace_index' FOREGROUND_END = 'foreground.last_workspace_index' diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANSILLReduction.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANSILLReduction.py new file mode 100644 index 0000000000000000000000000000000000000000..8424d387a4d77c68c2786162e921a4e923f1d86c --- /dev/null +++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANSILLReduction.py @@ -0,0 +1,360 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +from __future__ import (absolute_import, division, print_function) + +from mantid.api import PythonAlgorithm, MatrixWorkspaceProperty, MultipleFileProperty, PropertyMode +from mantid.kernel import Direction, EnabledWhenProperty, FloatBoundedValidator, LogicOperator, PropertyCriterion, StringListValidator +from mantid.simpleapi import * +from math import fabs + + +class SANSILLReduction(PythonAlgorithm): + + def category(self): + return "ILL\\SANS" + + def summary(self): + return 'Performs SANS data reduction at the ILL.' + + def seeAlso(self): + return [] + + def name(self): + return "SANSILLReduction" + + def validateInputs(self): + issues = dict() + if self.getPropertyValue('ProcessAs') == 'Transmission' and self.getProperty('BeamInputWorkspace').isDefault: + issues['BeamInputWorkspace'] = 'Beam input workspace is mandatory for transmission calculation.' + return issues + + def PyInit(self): + + self.declareProperty(MultipleFileProperty('Run', extensions=['nxs']), + doc='File path of run(s).') + + options = ['Absorber', 'Beam', 'Transmission', 'Container', 'Reference', 'Sample'] + + self.declareProperty(name='ProcessAs', + defaultValue='Sample', + validator=StringListValidator(options), + doc='Choose the process type.') + + self.declareProperty(MatrixWorkspaceProperty('OutputWorkspace', '', + direction=Direction.Output), + doc='The output workspace based on the value of ProcessAs.') + + not_absorber = EnabledWhenProperty('ProcessAs', PropertyCriterion.IsNotEqualTo, 'Absorber') + + sample = EnabledWhenProperty('ProcessAs', PropertyCriterion.IsEqualTo, 'Sample') + + beam = EnabledWhenProperty('ProcessAs', PropertyCriterion.IsEqualTo, 'Beam') + + transmission = EnabledWhenProperty('ProcessAs', PropertyCriterion.IsEqualTo, 'Transmission') + + not_beam = EnabledWhenProperty('ProcessAs', PropertyCriterion.IsNotEqualTo, 'Beam') + + reference = EnabledWhenProperty('ProcessAs', PropertyCriterion.IsEqualTo, 'Reference') + + container = EnabledWhenProperty('ProcessAs', PropertyCriterion.IsEqualTo, 'Container') + + self.declareProperty(name='NormaliseBy', + defaultValue='Timer', + validator=StringListValidator(['None', 'Timer', 'Monitor']), + doc='Choose the normalisation type.') + + self.declareProperty('BeamRadius', 0.05, validator=FloatBoundedValidator(lower=0.), + doc='Beam raduis [m]; used for beam center finding and transmission calculations.') + + self.setPropertySettings('BeamRadius', + EnabledWhenProperty(beam, transmission, LogicOperator.Or)) + + self.declareProperty('BeamFinderMethod', 'DirectBeam', StringListValidator(['DirectBeam', 'ScatteredBeam']), + doc='Choose between direct beam or scattered beam method for beam center finding.') + + self.setPropertySettings('BeamFinderMethod', beam) + + self.declareProperty('SampleThickness', 0.1, validator=FloatBoundedValidator(lower=0.), doc='Sample thickness [cm]') + + self.setPropertySettings('SampleThickness', EnabledWhenProperty(sample, reference, LogicOperator.Or)) + + self.declareProperty(MatrixWorkspaceProperty('AbsorberInputWorkspace', '', + direction=Direction.Input, + optional=PropertyMode.Optional), + doc='The name of the absorber workspace.') + + self.setPropertySettings('AbsorberInputWorkspace', not_absorber) + + self.declareProperty(MatrixWorkspaceProperty('BeamInputWorkspace', '', + direction=Direction.Input, + optional=PropertyMode.Optional), + doc='The name of the empty beam input workspace.') + + self.setPropertySettings('BeamInputWorkspace', + EnabledWhenProperty(not_absorber, not_beam, LogicOperator.And)) + + self.declareProperty(MatrixWorkspaceProperty('TransmissionInputWorkspace', '', + direction=Direction.Input, + optional=PropertyMode.Optional), + doc='The name of the transmission input workspace.') + + self.setPropertySettings('TransmissionInputWorkspace', + EnabledWhenProperty(container, + EnabledWhenProperty(reference, sample, LogicOperator.Or), + LogicOperator.Or)) + + self.declareProperty(MatrixWorkspaceProperty('ContainerInputWorkspace', '', + direction=Direction.Input, + optional=PropertyMode.Optional), + doc='The name of the container workspace.') + + self.setPropertySettings('ContainerInputWorkspace', + EnabledWhenProperty(sample, reference, LogicOperator.Or)) + + self.declareProperty(MatrixWorkspaceProperty('ReferenceInputWorkspace', '', + direction=Direction.Input, + optional=PropertyMode.Optional), + doc='The name of the reference workspace.') + + self.setPropertySettings('ReferenceInputWorkspace', sample) + + self.declareProperty(MatrixWorkspaceProperty('SensitivityInputWorkspace', '', + direction=Direction.Input, + optional=PropertyMode.Optional), + doc='The name of the input sensitivity workspace.') + + self.setPropertySettings('SensitivityInputWorkspace', + EnabledWhenProperty(sample, + EnabledWhenProperty('ReferenceInputWorkspace', + PropertyCriterion.IsEqualTo, ''), + LogicOperator.And)) + + self.declareProperty(MatrixWorkspaceProperty('SensitivityOutputWorkspace', '', + direction=Direction.Output, + optional=PropertyMode.Optional), + doc='The name of the output sensitivity workspace.') + + self.setPropertySettings('SensitivityOutputWorkspace', reference) + + self.declareProperty(MatrixWorkspaceProperty('MaskedInputWorkspace', '', + direction=Direction.Input, + optional=PropertyMode.Optional), + doc='Workspace to copy the mask from; for example, the beam stop') + + self.setPropertySettings('MaskedInputWorkspace', EnabledWhenProperty(sample, reference, LogicOperator.Or)) + + def _cylinder(self, radius): + """ + Returns XML for an infinite cylinder with axis of z (beam) and given radius [m] + @param radius : the radius of the cylinder [m] + @return : XML string for the geometry shape + """ + + return '<infinite-cylinder id="flux"><centre x="0.0" y="0.0" z="0.0"/><axis x="0.0" y="0.0" z="1.0"/>' \ + '<radius val="{0}"/></infinite-cylinder>'.format(radius) + + def _integrate_in_radius(self, ws, radius): + """ + Sums the detector counts within the given radius around the beam + @param ws : the input workspace + @param radius : the radius [m] + @returns : the workspace with the summed counts + """ + + shapeXML = self._cylinder(radius) + det_list = FindDetectorsInShape(Workspace=ws, ShapeXML=shapeXML) + grouped = ws + '_group' + GroupDetectors(InputWorkspace=ws, OutputWorkspace=grouped, DetectorList=det_list) + return grouped + + def _normalise(self, ws): + """ + Normalizes the workspace by time (SampleLog Timer) or Monitor (ID=100000) + @param ws : the input workspace + """ + + normalise_by = self.getPropertyValue('NormaliseBy') + if normalise_by == 'Monitor': + mon = ws + '_mon' + monID = 100000 + if mtd[ws].getInstrument().getName() == 'D33': + monID = 500000 + ExtractSpectra(InputWorkspace=ws, DetectorList=monID, OutputWorkspace=mon) + if mtd[mon].readY(0)[0] == 0: + raise RuntimeError('Normalise to monitor requested, but monitor has 0 counts.') + else: + Divide(LHSWorkspace=ws, RHSWorkspace=mon, OutputWorkspace=ws) + DeleteWorkspace(mon) + elif normalise_by == 'Timer': + if mtd[ws].getRun().hasProperty('timer'): + duration = mtd[ws].getRun().getLogData('timer').value + if duration != 0.: + Scale(InputWorkspace=ws, Factor=1./duration, OutputWorkspace=ws) + else: + raise RuntimeError('Unable to normalise to time; duration found is 0 seconds.') + else: + raise RuntimeError('Normalise to timer requested, but timer information is not available.') + + def _process_beam(self, ws): + """ + Calculates the beam center's x,y coordinates, and the beam flux + @param ws : the input [empty beam] workspace + """ + + centers = ws + '_centers' + method = self.getPropertyValue('BeamFinderMethod') + radius = self.getProperty('BeamRadius').value + FindCenterOfMassPosition(InputWorkspace=ws, DirectBeam=(method == 'DirectBeam'), BeamRadius=radius, Output=centers) + beam_x = mtd[centers].cell(0,1) + beam_y = mtd[centers].cell(1,1) + AddSampleLog(Workspace=ws, LogName='BeamCenterX', LogText=str(beam_x), LogType='Number') + AddSampleLog(Workspace=ws, LogName='BeamCenterY', LogText=str(beam_y), LogType='Number') + DeleteWorkspace(centers) + MoveInstrumentComponent(Workspace=ws, X=-beam_x, Y=-beam_y, ComponentName='detector') + integral = self._integrate_in_radius(ws, radius) + run = mtd[ws].getRun() + if run.hasProperty('attenuator.attenuation_coefficient'): + att_coeff = run.getLogData('attenuator.attenuation_coefficient').value + elif run.hasProperty('attenuator.attenuation_value'): + att_coeff = run.getLogData('attenuator.attenuation_value').value + else: + raise RuntimeError('Unable to process as beam: could not find attenuation coefficient nor value.') + self.log().information('Found attenuator coefficient/value: {0}'.format(att_coeff)) + Scale(InputWorkspace=integral, Factor=att_coeff, OutputWorkspace=integral) + AddSampleLog(Workspace=ws, LogName='BeamFluxValue', LogText=str(mtd[integral].readY(0)[0]), LogType='Number') + AddSampleLog(Workspace=ws, LogName='BeamFluxError', LogText=str(mtd[integral].readE(0)[0]), LogType='Number') + DeleteWorkspace(integral) + + @staticmethod + def _check_distances_match(ws1, ws2): + """ + Checks if the detector distance between two workspaces are close enough + @param ws1 : workspace 1 + @param ws2 : workspace 2 + @return true if the detector distance difference is less than 1 cm + """ + tolerance = 0.01 #m + l2_1 = ws1.getRun().getLogData('L2').value + l2_2 = ws2.getRun().getLogData('L2').value + return fabs(l2_1 - l2_2) < tolerance + + @staticmethod + def _check_processed_flag(ws, value): + return ws.getRun().getLogData('ProcessedAs').value == value + + def PyExec(self): # noqa: C901 + + process = self.getPropertyValue('ProcessAs') + ws = '__' + self.getPropertyValue('OutputWorkspace') + LoadAndMerge(Filename=self.getPropertyValue('Run').replace(',','+'), LoaderName='LoadILLSANS', OutputWorkspace=ws) + self._normalise(ws) + ExtractMonitors(InputWorkspace=ws, DetectorWorkspace=ws) + if process in ['Beam', 'Transmission', 'Container', 'Reference', 'Sample']: + absorber_ws = self.getProperty('AbsorberInputWorkspace').value + if absorber_ws: + if not self._check_processed_flag(absorber_ws, 'Absorber'): + self.log().warning('Absorber input workspace is not processed as absorber.') + Minus(LHSWorkspace=ws, RHSWorkspace=absorber_ws, OutputWorkspace=ws) + if process == 'Beam': + self._process_beam(ws) + else: + beam_ws = self.getProperty('BeamInputWorkspace').value + if beam_ws: + if not self._check_processed_flag(beam_ws, 'Beam'): + self.log().warning('Beam input workspace is not processed as beam.') + beam_x = beam_ws.getRun().getLogData('BeamCenterX').value + beam_y = beam_ws.getRun().getLogData('BeamCenterY').value + MoveInstrumentComponent(Workspace=ws, X=-beam_x, Y=-beam_y, ComponentName='detector') + if not self._check_distances_match(mtd[ws], beam_ws): + self.log().warning('Different detector distances found for empty beam and sample runs!') + if process == 'Transmission': + if not self._check_distances_match(mtd[ws], beam_ws): + self.log().warning('Different detector distances found for empty beam and transmission runs!') + RebinToWorkspace(WorkspaceToRebin=ws, WorkspaceToMatch=beam_ws, OutputWorkspace=ws) + radius = self.getProperty('BeamRadius').value + shapeXML = self._cylinder(radius) + det_list = FindDetectorsInShape(Workspace=ws, ShapeXML=shapeXML) + CalculateTransmission(SampleRunWorkspace=ws, DirectRunWorkspace=beam_ws, + TransmissionROI=det_list, OutputWorkspace=ws) + else: + transmission_ws = self.getProperty('TransmissionInputWorkspace').value + if transmission_ws: + if not self._check_processed_flag(transmission_ws, 'Transmission'): + self.log().warning('Transmission input workspace is not processed as transmission.') + transmission = transmission_ws.readY(0)[0] + transmission_err = transmission_ws.readE(0)[0] + ApplyTransmissionCorrection(InputWorkspace=ws, TransmissionValue=transmission, + TransmissionError=transmission_err, OutputWorkspace=ws) + solid_angle = ws + '_sa' + SolidAngle(InputWorkspace=ws, OutputWorkspace=solid_angle) + Divide(LHSWorkspace=ws, RHSWorkspace=solid_angle, OutputWorkspace=ws) + DeleteWorkspace(solid_angle) + if process in ['Reference', 'Sample']: + container_ws = self.getProperty('ContainerInputWorkspace').value + if container_ws: + if not self._check_processed_flag(container_ws, 'Container'): + self.log().warning('Container input workspace is not processed as container.') + if not self._check_distances_match(mtd[ws], container_ws): + self.log().warning( + 'Different detector distances found for container and sample runs!') + Minus(LHSWorkspace=ws, RHSWorkspace=container_ws, OutputWorkspace=ws) + mask_ws = self.getProperty('MaskedInputWorkspace').value + if mask_ws: + masked_ws = ws + '_mask' + CloneWorkspace(InputWorkspace=mask_ws, OutputWorkspace=masked_ws) + ExtractMonitors(InputWorkspace=masked_ws, DetectorWorkspace=masked_ws) + MaskDetectors(Workspace=ws, MaskedWorkspace=masked_ws) + DeleteWorkspace(masked_ws) + thickness = self.getProperty('SampleThickness').value + NormaliseByThickness(InputWorkspace=ws, OutputWorkspace=ws, SampleThickness=thickness) + if process == 'Reference': + sensitivity_out = self.getPropertyValue('SensitivityOutputWorkspace') + if sensitivity_out: + CalculateEfficiency(InputWorkspace=ws, OutputWorkspace=sensitivity_out) + mtd[sensitivity_out].getRun().addProperty('ProcessedAs', 'Sensitivity', True) + self.setProperty('SensitivityOutputWorkspace', mtd[sensitivity_out]) + elif process == 'Sample': + reference_ws = self.getProperty('ReferenceInputWorkspace').value + coll_ws = None + if reference_ws: + if not self._check_processed_flag(reference_ws, 'Reference'): + self.log().warning('Reference input workspace is not processed as reference.') + Divide(LHSWorkspace=ws, RHSWorkspace=reference_ws, OutputWorkspace=ws) + coll_ws = reference_ws + else: + sensitivity_in = self.getProperty('SensitivityInputWorkspace').value + if sensitivity_in: + if not self._check_processed_flag(sensitivity_in, 'Sensitivity'): + self.log().warning('Sensitivity input workspace is not processed as sensitivity.') + Divide(LHSWorkspace=ws, RHSWorkspace=sensitivity_in, OutputWorkspace=ws) + if beam_ws: + coll_ws = beam_ws + flux = beam_ws.getRun().getLogData('BeamFluxValue').value + ferr = beam_ws.getRun().getLogData('BeamFluxError').value + flux_ws = ws + '_flux' + CreateSingleValuedWorkspace(DataValue=flux, ErrorValue=ferr, OutputWorkspace=flux_ws) + Divide(LHSWorkspace=ws, RHSWorkspace=flux_ws, OutputWorkspace=ws) + DeleteWorkspace(flux_ws) + if coll_ws: + if not self._check_distances_match(mtd[ws], coll_ws): + self.log().warning( + 'Different detector distances found for the reference/flux and sample runs!') + sample_coll = mtd[ws].getRun().getLogData('collimation.actual_position').value + ref_coll = coll_ws.getRun().getLogData('collimation.actual_position').value + flux_factor = (sample_coll ** 2) / (ref_coll ** 2) + self.log().notice('Flux factor is: ' + str(flux_factor)) + Scale(InputWorkspace=ws, Factor=flux_factor, OutputWorkspace=ws) + ReplaceSpecialValues(InputWorkspace=ws, OutputWorkspace=ws, + NaNValue=0., NaNError=0., InfinityValue=0., InfinityError=0.) + + CalculateDynamicRange(Workspace=ws) + mtd[ws].getRun().addProperty('ProcessedAs', process, True) + RenameWorkspace(InputWorkspace=ws, OutputWorkspace=ws[2:]) + self.setProperty('OutputWorkspace', mtd[ws[2:]]) + +# Register algorithm with Mantid +AlgorithmFactory.subscribe(SANSILLReduction) diff --git a/Framework/PythonInterface/plugins/functions/BivariateGaussian.py b/Framework/PythonInterface/plugins/functions/BivariateGaussian.py index 329bf112a5a40deef21ca332deb6f04575f0aa24..716ca076c904d7bcbb3b15b2a15fbfb4f36ed97d 100644 --- a/Framework/PythonInterface/plugins/functions/BivariateGaussian.py +++ b/Framework/PythonInterface/plugins/functions/BivariateGaussian.py @@ -116,7 +116,7 @@ class BivariateGaussian(IFunction1D): def getMuSigma(self): return self.getMu(), self.getSigma() - def setConstraints(self, boundsDict): + def setConstraints(self, boundsDict, penalty=None): """ setConstraints sets fitting constraints for the mbvg function. Intput: @@ -130,9 +130,10 @@ class BivariateGaussian(IFunction1D): constraintString = "{:4.4e} < {:s} < {:4.4e}".format(boundsDict[param][0], param, boundsDict[param][1]) self.addConstraints(constraintString) else: - self.addConstraints("{:4.4e} < A < {:4.4e}".format(boundsDict[param][1], boundsDict[param][0])) + self.addConstraints("{:4.4e} < {:s} < {:4.4e}".format(boundsDict[param][1], param, boundsDict[param][0])) + if penalty is not None: + self.setConstraintPenaltyFactor(param, penalty) except ValueError: - raise raise UserWarning("Cannot set parameter {:s} for mbvg. Valid choices are " + "('A', 'MuX', 'MuY', 'SigX', 'SigY', 'SigP', 'Bg')".format(param)) diff --git a/Framework/PythonInterface/plugins/functions/ICConvoluted.py b/Framework/PythonInterface/plugins/functions/ICConvoluted.py index eb4df5f6086b519260442c8398a363436275079c..eeaffbfe321b8668adfc081cf5f7a89634c07029 100644 --- a/Framework/PythonInterface/plugins/functions/ICConvoluted.py +++ b/Framework/PythonInterface/plugins/functions/ICConvoluted.py @@ -26,8 +26,8 @@ class IkedaCarpenterConvoluted(IFunction1D): self.declareParameter("HatWidth") #width of square wave self.declareParameter("KConv") #KConv for Gaussian - #n.b. pass penalty=None to not use default mantid penalty - useful for development - def setPenalizedConstraints(self, A0=None, B0=None, R0=None, T00=None, Scale0=None, HatWidth0=None, KConv0=None, penalty=1.0e20): + #use penalty=None to not use default mantid penalty + def setPenalizedConstraints(self, A0=None, B0=None, R0=None, T00=None, Scale0=None, HatWidth0=None, KConv0=None, penalty=None): if A0 is not None: self.addConstraints("{:4.4e} < A < {:4.4e}".format(A0[0], A0[1])) if penalty is not None: diff --git a/Framework/PythonInterface/plugins/functions/MsdGauss.py b/Framework/PythonInterface/plugins/functions/MsdGauss.py index 0f7f8e9d0ed1cdcdf829e1ded284740b3f61db9a..1f796e1367d63d2ffeb8958a373ed7ee264b97e7 100644 --- a/Framework/PythonInterface/plugins/functions/MsdGauss.py +++ b/Framework/PythonInterface/plugins/functions/MsdGauss.py @@ -27,11 +27,11 @@ class MsdGauss(IFunction1D): def init(self): # Active fitting parameters self.declareParameter("Height", 1.0, 'Height') - self.declareParameter("MSD", 0.05, 'Mean square displacement') + self.declareParameter("Msd", 0.05, 'Mean square displacement') def function1D(self, xvals): height = self.getParameterValue("Height") - msd = self.getParameterValue("MSD") + msd = self.getParameterValue("Msd") xvals = np.array(xvals) intensity = height * np.exp(-msd * xvals**2) @@ -40,7 +40,7 @@ class MsdGauss(IFunction1D): def functionDeriv1D(self, xvals, jacobian): height = self.getParameterValue("Height") - msd = self.getParameterValue("MSD") + msd = self.getParameterValue("Msd") for i, x in enumerate(xvals): e = math.exp(-msd * x**2) diff --git a/Framework/PythonInterface/plugins/functions/MsdPeters.py b/Framework/PythonInterface/plugins/functions/MsdPeters.py index b3278bc1eaf8dff62ed1e409c7eda2c0751201f3..9974d21ec72df219503536a556219a5e35656c07 100644 --- a/Framework/PythonInterface/plugins/functions/MsdPeters.py +++ b/Framework/PythonInterface/plugins/functions/MsdPeters.py @@ -28,12 +28,12 @@ class MsdPeters(IFunction1D): def init(self): # Active fitting parameters self.declareParameter("Height", 1.0, 'Height') - self.declareParameter("MSD", 0.05, 'Mean square displacement') + self.declareParameter("Msd", 0.05, 'Mean square displacement') self.declareParameter("Beta", 1.0, 'beta') def function1D(self, xvals): height = self.getParameterValue("Height") - msd = self.getParameterValue("MSD") + msd = self.getParameterValue("Msd") beta = self.getParameterValue("Beta") xvals = np.array(xvals) @@ -44,7 +44,7 @@ class MsdPeters(IFunction1D): def functionDeriv1D(self, xvals, jacobian): height = self.getParameterValue("Height") - msd = self.getParameterValue("MSD") + msd = self.getParameterValue("Msd") beta = self.getParameterValue("Beta") for i, x in enumerate(xvals): diff --git a/Framework/PythonInterface/plugins/functions/MsdYi.py b/Framework/PythonInterface/plugins/functions/MsdYi.py index 50c25f4fd24c6e63a488317727d3f2d15eef8813..3e92154c30074bd59135b1da19897c175ad7f517 100644 --- a/Framework/PythonInterface/plugins/functions/MsdYi.py +++ b/Framework/PythonInterface/plugins/functions/MsdYi.py @@ -1,43 +1,46 @@ -# Mantid Repository : https://github.com/mantidproject/mantid -# -# Copyright © 2007 ISIS Rutherford Appleton Laboratory UKRI, -# NScD Oak Ridge National Laboratory, European Spallation Source -# & Institut Laue - Langevin -# SPDX - License - Identifier: GPL - 3.0 + -# pylint: disable=no-init,invalid-name ''' @author Spencer Howells, ISIS @date December 05, 2013 + +Copyright © 2007-8 ISIS Rutherford Appleton Laboratory, +NScD Oak Ridge National Laboratory & European Spallation Source + +pylint: disable=no-init,invalid-name + +This file is part of Mantid. +Mantid Repository : https://github.com/mantidproject/mantid +File change history is stored at: <https://github.com/mantidproject/mantid> +Code Documentation is available at: <http://doxygen.mantidproject.org> ''' from __future__ import (absolute_import, division, print_function) - import math import numpy as np - from mantid.api import IFunction1D, FunctionFactory - # The model of Yi et al(J Phys Chem B 1316 5029 2012) takes into account motional heterogeneity. -# The elastic intensity is propotional to exp(-1/6*Q^2*msd)*(1+Q^4*sigma/72) +# The elastic intensity is proportional to exp(-(1/6)*Q^2*msd)*(1+Q^4*sigma/72) # where the mean square displacement msd = <r^2> and sigma^2 is the variance of the msd. +# In the limit sigma = 0 the model becomes Gaussian. + class MsdYi(IFunction1D): + def category(self): return "QuasiElastic" def init(self): # Active fitting parameters self.declareParameter("Height", 1.0, 'Height') - self.declareParameter("MSD", 0.05, 'Mean square displacement') + self.declareParameter("Msd", 0.05, 'Mean square displacement') self.declareParameter("Sigma", 1.0, 'Sigma') def function1D(self, xvals): height = self.getParameterValue("Height") - msd = self.getParameterValue("MSD") + msd = self.getParameterValue("Msd") sigma = self.getParameterValue("Sigma") xvals = np.array(xvals) - i1 = np.exp(-1.0 / (6.0 * xvals**2 * msd)) + i1 = np.exp((-1.0 / 6.0) * xvals * xvals * msd) i2 = 1.0 + (np.power(xvals, 4) * sigma / 72.0) intensity = height * i1 * i2 @@ -45,13 +48,12 @@ class MsdYi(IFunction1D): def functionDeriv1D(self, xvals, jacobian): height = self.getParameterValue("Height") - msd = self.getParameterValue("MSD") + msd = self.getParameterValue("Msd") sigma = self.getParameterValue("Sigma") for i, x in enumerate(xvals): - q = msd * x**2 - f1 = math.exp(-1.0 / (6.0 * q)) - df1 = f1 / (6.0 * x * q) + f1 = math.exp((-1.0 / 6.0) * x * x * msd) + df1 = -f1 * ((2.0 / 6.0) * x * msd) x4 = math.pow(x, 4) f2 = 1.0 + (x4 * sigma / 72.0) df2 = x4 / 72.0 diff --git a/Framework/PythonInterface/test/python/mantid/api/MatrixWorkspaceTest.py b/Framework/PythonInterface/test/python/mantid/api/MatrixWorkspaceTest.py index f45ea9b60de95cca6752978e33a722057591e3cd..9b6a5c7db5810330efd7e604b7bf579040e9c18c 100644 --- a/Framework/PythonInterface/test/python/mantid/api/MatrixWorkspaceTest.py +++ b/Framework/PythonInterface/test/python/mantid/api/MatrixWorkspaceTest.py @@ -417,6 +417,9 @@ class MatrixWorkspaceTest(unittest.TestCase): self.assertEquals(specInfo.isMasked(0), False) self.assertEquals(specInfo.isMasked(1), False) + def test_isCommonBins(self): + self.assertTrue(self._test_ws.isCommonBins()) + if __name__ == '__main__': unittest.main() #Testing particular test from Mantid diff --git a/Framework/PythonInterface/test/python/mantid/api/SpectrumInfoTest.py b/Framework/PythonInterface/test/python/mantid/api/SpectrumInfoTest.py index 3ef053c88a4b335ae3328fa18a41634e20750632..44e349b9d4f1d6973a012b69fd8445c196a05d7d 100644 --- a/Framework/PythonInterface/test/python/mantid/api/SpectrumInfoTest.py +++ b/Framework/PythonInterface/test/python/mantid/api/SpectrumInfoTest.py @@ -141,6 +141,16 @@ class SpectrumInfoTest(unittest.TestCase): for item in it: self.assertFalse(item.isMasked) + def test_iterator_for_setting_masked(self): + info = self._ws.spectrumInfo() + # nothing should be masked + it = iter(info) + next(it) # skip first as detectors cleared + for item in it: + # mask and check + item.setMasked(True) + self.assertTrue(item.isMasked) + def test_iteration_for_position(self): info = self._ws.spectrumInfo() lastY = None diff --git a/Framework/PythonInterface/test/python/mantid/geometry/DetectorInfoTest.py b/Framework/PythonInterface/test/python/mantid/geometry/DetectorInfoTest.py index d2cd69bc4dfed69945c5cf982587831e405a708b..a9ca56956b6369c20a6f475597f0fd513c4dd91c 100644 --- a/Framework/PythonInterface/test/python/mantid/geometry/DetectorInfoTest.py +++ b/Framework/PythonInterface/test/python/mantid/geometry/DetectorInfoTest.py @@ -87,11 +87,15 @@ class DetectorInfoTest(unittest.TestCase): workspace = CreateWorkspace(DataX=dataX, DataY=dataY) info = workspace.detectorInfo() self.assertEquals(info.size(), 0) + + def test_detectorIds(self): + info = self._ws.detectorInfo() + ids = info.detectorIDs() + # Should be read-only + self.assertFalse(ids.flags.writeable) + for a, b in zip(ids, [1,2]): + self.assertEquals(a,b) - """ - The following test cases test for returned objects to do with position - and rotation. - """ def test_position(self): """ Test that the detector's position is returned. """ @@ -129,6 +133,15 @@ class DetectorInfoTest(unittest.TestCase): # nothing should be masked for item in info: self.assertFalse(item.isMasked) + + def test_iterator_for_masked(self): + info = self._ws.detectorInfo() + it = iter(info) + item = next(it) + item.setMasked(True) + self.assertTrue(item.isMasked) + item.setMasked(False) + self.assertFalse(item.isMasked) def test_iteration_for_position(self): info = self._ws.detectorInfo() diff --git a/Framework/PythonInterface/test/python/mantid/kernel/ConfigServiceTest.py b/Framework/PythonInterface/test/python/mantid/kernel/ConfigServiceTest.py index 1fc400c885b081521fa8ebd73225fefefb1134d2..4149da46745b32f65bb5c14cff597a19baf80371 100644 --- a/Framework/PythonInterface/test/python/mantid/kernel/ConfigServiceTest.py +++ b/Framework/PythonInterface/test/python/mantid/kernel/ConfigServiceTest.py @@ -4,7 +4,7 @@ # NScD Oak Ridge National Laboratory, European Spallation Source # & Institut Laue - Langevin # SPDX - License - Identifier: GPL - 3.0 + -from __future__ import (absolute_import, division, print_function) +from __future__ import (absolute_import, division, print_function, unicode_literals) import inspect import os @@ -126,6 +126,7 @@ class ConfigServiceTest(unittest.TestCase): 'workspace.sendto.SansView.arguments', 'workspace.sendto.SansView.saveusing', # related to SASview in menu 'workspace.sendto.SansView.target', 'workspace.sendto.SansView.visible', # related to SASview in menu 'workspace.sendto.name.SansView', # related to SASview in menu + 'catalog.oncat.token.accessToken', 'catalog.oncat.token.expiresIn', 'catalog.oncat.token.refreshToken', 'catalog.oncat.token.scope', 'catalog.oncat.token.tokenType', # Shouldn't be changed by users. ########## TODO should be documented! 'filefinder.casesensitive', diff --git a/Framework/PythonInterface/test/python/mantid/kernel/LoggerTest.py b/Framework/PythonInterface/test/python/mantid/kernel/LoggerTest.py index 64a4c82c8223df7d6938b4db09a8add4c3b1b177..bf79855befc3373cb7572488286d73f0535b026e 100644 --- a/Framework/PythonInterface/test/python/mantid/kernel/LoggerTest.py +++ b/Framework/PythonInterface/test/python/mantid/kernel/LoggerTest.py @@ -4,22 +4,43 @@ # NScD Oak Ridge National Laboratory, European Spallation Source # & Institut Laue - Langevin # SPDX - License - Identifier: GPL - 3.0 + -from __future__ import (absolute_import, division, print_function) +from __future__ import (absolute_import, division, print_function, unicode_literals) import unittest from mantid.kernel import Logger + class LoggerTest(unittest.TestCase): - def test_logger_creation_does_not_raise_an_error(self): + def test_str_logger(self): + logger = Logger(str("LoggerTest")) + self.assertTrue(isinstance(logger, Logger)) + for att in ['fatal', 'error', 'warning', 'notice', 'information', 'debug']: + if not hasattr(logger, att): + self.fail("Logger object does not have the required attribute '%s'" % att) + + logger.fatal(str('This is a test')) + logger.error(str('This is a test')) + logger.warning(str('This is a test')) + logger.notice(str('This is a test')) + logger.information(str('This is a test')) + logger.debug(str('This is a test')) + + def test_unicode_logger(self): logger = Logger("LoggerTest") self.assertTrue(isinstance(logger, Logger)) - attrs = ['fatal', 'error','warning','notice', 'information', 'debug'] - for att in attrs: + for att in ['fatal', 'error', 'warning', 'notice', 'information', 'debug']: if not hasattr(logger, att): self.fail("Logger object does not have the required attribute '%s'" % att) + logger.fatal('This is a test') + logger.error('This is a test') + logger.warning('This is a test') + logger.notice('This is a test') + logger.information('This is a test') + logger.debug('This is a test') + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/Framework/PythonInterface/test/python/mantid/kernel/UsageServiceTest.py b/Framework/PythonInterface/test/python/mantid/kernel/UsageServiceTest.py index 17954ae838934380508a717c0e357f1a6c225dba..78a4ca2fed938009714235ddb24501168259b69b 100644 --- a/Framework/PythonInterface/test/python/mantid/kernel/UsageServiceTest.py +++ b/Framework/PythonInterface/test/python/mantid/kernel/UsageServiceTest.py @@ -4,7 +4,7 @@ # NScD Oak Ridge National Laboratory, European Spallation Source # & Institut Laue - Langevin # SPDX - License - Identifier: GPL - 3.0 + -from __future__ import (absolute_import, division, print_function) +from __future__ import (absolute_import, division, print_function, unicode_literals) import unittest diff --git a/Framework/PythonInterface/test/python/mantid/plots/plotfunctionsTest.py b/Framework/PythonInterface/test/python/mantid/plots/plotfunctionsTest.py index 08a641d7da19b595ecdb3a9019b68cdcc253ad78..f815504db44a0af3ee3ce1026b9581a8a6e45ccc 100644 --- a/Framework/PythonInterface/test/python/mantid/plots/plotfunctionsTest.py +++ b/Framework/PythonInterface/test/python/mantid/plots/plotfunctionsTest.py @@ -121,6 +121,7 @@ class PlotFunctionsTest(unittest.TestCase): funcs.tripcolor(ax, self.ws2d_histo, vmin=0) funcs.pcolormesh(ax, self.ws_MD_2d) funcs.pcolorfast(ax, self.ws2d_point_uneven, vmin=-1) + funcs.imshow(ax, self.ws2d_histo) def test_1d_plots_with_unplottable_type_raises_attributeerror(self): table = CreateEmptyTableWorkspace() diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/LoadLogPropertyTableTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/LoadLogPropertyTableTest.py index 3edbc15aba684b5d12bba25cd3207d783a59efd0..972556065d826512b89689b47d22508eeaef3b1a 100644 --- a/Framework/PythonInterface/test/python/plugins/algorithms/LoadLogPropertyTableTest.py +++ b/Framework/PythonInterface/test/python/plugins/algorithms/LoadLogPropertyTableTest.py @@ -88,7 +88,8 @@ class LoadLogPropertyTableTest(unittest.TestCase): ExecptionThrownOnBadLogName = False try: alg_test = run_algorithm("LoadLogPropertyTable", FirstFile = "emu00006473.nxs", - LastFile = "emu00006475.nxs", LogNames="WrongTemp", OutputWorkspace = outputWorskapceName) + LastFile = "emu00006475.nxs", LogNames="WrongTemp", OutputWorkspace = outputWorskapceName, + rethrow=True) except RuntimeError: ExecptionThrownOnBadLogName = True self.assertEqual(True, ExecptionThrownOnBadLogName) @@ -97,7 +98,8 @@ class LoadLogPropertyTableTest(unittest.TestCase): ExecptionThrownOnBadFileParameter = False try: alg_test = run_algorithm("LoadLogPropertyTable", FirstFile = "emu0000000.nxs", - LastFile = "emu00006475.nxs", LogNames="Temp_Sample", OutputWorkspace = outputWorskapceName) + LastFile = "emu00006475.nxs", LogNames="Temp_Sample", OutputWorkspace = outputWorskapceName, + rethrow=True) self.assertFalse(alg_test.isExecuted()) except: ExecptionThrownOnBadFileParameter = True @@ -107,7 +109,8 @@ class LoadLogPropertyTableTest(unittest.TestCase): ExecptionThrownOnBadFileParameter = False try: alg_test = run_algorithm("LoadLogPropertyTable", FirstFile = "emu00006473.nxs", - LastFile = "emu9999999.nxs", LogNames="Temp_Sample", OutputWorkspace = outputWorskapceName) + LastFile = "emu9999999.nxs", LogNames="Temp_Sample", OutputWorkspace = outputWorskapceName, + rethrow=True) self.assertFalse(alg_test.isExecuted()) except: ExecptionThrownOnBadFileParameter = True diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/MaskAngleTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/MaskAngleTest.py index 57e07ac09e36913207a8f0687fb26117d83f2f01..ba7d62fd58caee2d107f7220dac3751fe1ddffcf 100644 --- a/Framework/PythonInterface/test/python/plugins/algorithms/MaskAngleTest.py +++ b/Framework/PythonInterface/test/python/plugins/algorithms/MaskAngleTest.py @@ -17,7 +17,7 @@ class MaskAngleTest(unittest.TestCase): def testMaskAngle(self): w=WorkspaceCreationHelper.create2DWorkspaceWithFullInstrument(30,5,False,False) AnalysisDataService.add('w',w) - masklist = MaskAngle(w,10,20) + masklist = MaskAngle(w,MinAngle=10,MaxAngle=20) detInfo = w.detectorInfo() for i in arange(w.getNumberHistograms()): if (i<9) or (i>18): diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/CMakeLists.txt b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/CMakeLists.txt index f2be3b341491d482dd28a42dd2c9c0d5b33e579c..6d10c935b18577dca5ef71589a4808ab1a3859bc 100644 --- a/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/CMakeLists.txt +++ b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/CMakeLists.txt @@ -49,6 +49,7 @@ set ( TEST_PY_FILES ResNorm2Test.py SANSDarkRunBackgroundCorrectionTest.py SANSFitShiftScaleTest.py + SANSILLReductionTest.py SANSMaskTest.py SANSStitchTest.py SavePlot1DTest.py diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLConvertToQTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLConvertToQTest.py index 6e54ec0234a7cc75d619ae41dfce35d1353c3902..ca68746beda44f861d77e2073fb9614d2f00f90b 100644 --- a/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLConvertToQTest.py +++ b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLConvertToQTest.py @@ -33,12 +33,10 @@ class ReflectometryILLConvertToQTest(unittest.TestCase): mtd.add('reflWS', reflWS) illhelpers.refl_create_beam_position_ws('reflBeamPosWS', reflWS, 1.5, 128) reflWS = illhelpers.refl_preprocess('reflWS', reflWS, 'reflBeamPosWS') - fgdWS = illhelpers.refl_sum_foreground('fgdWS', 'SumInLambda', reflWS, dirFgdWS) + fgdWS = illhelpers.refl_sum_foreground('fgdWS', 'SumInLambda', reflWS, dirFgdWS, dirWS) args = { 'InputWorkspace': fgdWS, 'OutputWorkspace': 'inQ', - 'ReflectedBeamWorkspace': reflWS, - 'DirectBeamWorkspace': dirWS, 'DirectForegroundWorkspace': dirFgdWS, 'GroupingQFraction': 0.2, 'rethrow': True, @@ -65,12 +63,11 @@ class ReflectometryILLConvertToQTest(unittest.TestCase): mtd.add('reflWS', reflWS) illhelpers.refl_create_beam_position_ws('reflBeamPosWS', reflWS, 1.5, 128) reflWS = illhelpers.refl_preprocess('reflWS', reflWS, 'reflBeamPosWS') - fgdWS = illhelpers.refl_sum_foreground('fgdWS', 'SumInQ', reflWS, dirFgdWS) + fgdWS = illhelpers.refl_sum_foreground('fgdWS', 'SumInQ', reflWS, dirFgdWS, dirWS) args = { 'InputWorkspace': fgdWS, 'OutputWorkspace': 'inQ', - 'ReflectedBeamWorkspace': reflWS, - 'DirectBeamWorkspace': dirWS, + 'DirectForegroundWorkspace': dirFgdWS, 'GroupingQFraction': 0.2, 'rethrow': True, 'child': True diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLSumForegroundTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLSumForegroundTest.py index 3f47548d9a839040a9b912c53f35c66b5a4530d9..98b51cd72812b465747e5cf66196d34236a33e36 100644 --- a/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLSumForegroundTest.py +++ b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/ReflectometryILLSumForegroundTest.py @@ -36,6 +36,7 @@ class ReflectometryILLSumForegroundTest(unittest.TestCase): def testReflectedBeamSumInLambdaExecutes(self): dirWS = illhelpers.create_poor_mans_d17_workspace() illhelpers.add_chopper_configuration_D17(dirWS) + illhelpers.add_slit_configuration_D17(dirWS, 0.03, 0.02) dirBeamPosWS = illhelpers.refl_create_beam_position_ws('dirBeamPosWS', dirWS, 0., 128) dirWS = illhelpers.refl_preprocess('dirWS', dirWS, dirBeamPosWS) args = { @@ -51,6 +52,7 @@ class ReflectometryILLSumForegroundTest(unittest.TestCase): reflWS = illhelpers.create_poor_mans_d17_workspace() illhelpers.refl_rotate_detector(reflWS, 1.2) illhelpers.add_chopper_configuration_D17(reflWS) + illhelpers.add_slit_configuration_D17(reflWS, 0.03, 0.02) reflBeamPosWS = illhelpers.refl_create_beam_position_ws('reflBeamPosWS', reflWS, 1.2, 128) reflWS = illhelpers.refl_preprocess('refWS', reflWS, reflBeamPosWS) args = { @@ -58,6 +60,7 @@ class ReflectometryILLSumForegroundTest(unittest.TestCase): 'OutputWorkspace': 'foreground', 'DirectForegroundWorkspace': dirForeground, 'SummationType': 'SumInLambda', + 'DirectBeamWorkspace': dirWS, 'rethrow': True, 'child': True } @@ -68,6 +71,7 @@ class ReflectometryILLSumForegroundTest(unittest.TestCase): def testReflectedBeamSumInQExecutes(self): dirWS = illhelpers.create_poor_mans_d17_workspace() illhelpers.add_chopper_configuration_D17(dirWS) + illhelpers.add_slit_configuration_D17(dirWS, 0.02, 0.03) dirBeamPosWS = illhelpers.refl_create_beam_position_ws('dirBeamPosWS', dirWS, 0., 128) dirWS = illhelpers.refl_preprocess('dirWS', dirWS, dirBeamPosWS) args = { @@ -83,6 +87,7 @@ class ReflectometryILLSumForegroundTest(unittest.TestCase): reflWS = illhelpers.create_poor_mans_d17_workspace() illhelpers.refl_rotate_detector(reflWS, 1.2) illhelpers.add_chopper_configuration_D17(reflWS) + illhelpers.add_slit_configuration_D17(reflWS, 0.02, 0.03) reflBeamPosWS = illhelpers.refl_create_beam_position_ws('reflBeamPosWS', reflWS, 1.2, 128) reflWS = illhelpers.refl_preprocess('refWS', reflWS, reflBeamPosWS) args = { @@ -90,6 +95,7 @@ class ReflectometryILLSumForegroundTest(unittest.TestCase): 'OutputWorkspace': 'foreground', 'DirectForegroundWorkspace': dirForeground, 'SummationType': 'SumInQ', + 'DirectBeamWorkspace': dirWS, 'rethrow': True, 'child': True } @@ -126,7 +132,8 @@ class ReflectometryILLSumForegroundTest(unittest.TestCase): args = { 'InputWorkspace': 'reflected', 'OutputWorkspace': 'reflected-fgd', - 'DirectForegroundWorkspace': 'direct-fgd' + 'DirectForegroundWorkspace': 'direct-fgd', + 'DirectBeamWorkspace': 'direct' } alg = create_algorithm('ReflectometryILLSumForeground', **args) assertRaisesNothing(self, alg.execute) diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/SANSILLReductionTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/SANSILLReductionTest.py new file mode 100644 index 0000000000000000000000000000000000000000..736230a1e1787c90e830e5a7fee89dd7b0a79d0d --- /dev/null +++ b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/SANSILLReductionTest.py @@ -0,0 +1,89 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +from __future__ import (absolute_import, division, print_function) + +import unittest +from mantid.api import MatrixWorkspace +from mantid.simpleapi import SANSILLReduction, config, mtd + + +class SANSILLReductionTest(unittest.TestCase): + + _facility = None + + def setUp(self): + self._facility = config['default.facility'] + config.appendDataSearchSubDir('ILL/D11/') + config['default.facility'] = 'ILL' + + def tearDown(self): + config['default.facility'] = self._facility + mtd.clear() + + def test_absorber(self): + SANSILLReduction(Run='010462', ProcessAs='Absorber', OutputWorkspace='Cd') + self._check_output(mtd['Cd']) + self._check_process_flag(mtd['Cd'], 'Absorber') + + def test_beam(self): + SANSILLReduction(Run='010414', ProcessAs='Beam', OutputWorkspace='Db') + self._check_output(mtd['Db']) + self._check_process_flag(mtd['Db'], 'Beam') + run = mtd['Db'].getRun() + self.assertAlmostEqual(run.getLogData('BeamCenterX').value, -0.0048, delta=1e-4) + self.assertAlmostEqual(run.getLogData('BeamCenterY').value, -0.0027, delta=1e-4) + self.assertAlmostEqual(run.getLogData('BeamFluxValue').value, 6618939, delta=1) + self.assertAlmostEqual(run.getLogData('BeamFluxError').value, 8554, delta=1) + + def test_transmission(self): + SANSILLReduction(Run='010414', ProcessAs='Beam', OutputWorkspace='Db') + SANSILLReduction(Run='010585', ProcessAs='Transmission', BeamInputWorkspace='Db', OutputWorkspace='Tr') + self.assertAlmostEqual(mtd['Tr'].readY(0)[0], 0.640, delta=1e-3) + self.assertAlmostEqual(mtd['Tr'].readE(0)[0], 0.0019, delta=1e-4) + self._check_process_flag(mtd['Tr'], 'Transmission') + + def test_container(self): + SANSILLReduction(Run='010460', ProcessAs='Container', OutputWorkspace='can') + self._check_output(mtd['can']) + self._check_process_flag(mtd['can'], 'Container') + + def test_reference(self): + SANSILLReduction(Run='010453', ProcessAs='Reference', SensitivityOutputWorkspace='sens', OutputWorkspace='water') + self._check_output(mtd['water']) + self._check_output(mtd['sens'], logs=False) + self._check_process_flag(mtd['water'], 'Reference') + self._check_process_flag(mtd['sens'], 'Sensitivity') + + def test_sample(self): + SANSILLReduction(Run='010569', ProcessAs='Sample', OutputWorkspace='sample') + self._check_output(mtd['sample']) + self._check_process_flag(mtd['sample'], 'Sample') + + def _check_process_flag(self, ws, value): + self.assertTrue(ws.getRun().getLogData('ProcessedAs').value, value) + + def _check_output(self, ws, logs=True): + self.assertTrue(ws) + self.assertTrue(isinstance(ws, MatrixWorkspace)) + self.assertTrue(ws.isHistogramData()) + self.assertEqual(ws.getAxis(0).getUnit().unitID(), "Wavelength") + self.assertEqual(ws.blocksize(), 1) + self.assertEqual(ws.getNumberHistograms(), 128 * 128) + self.assertTrue(ws.getInstrument()) + self.assertTrue(ws.getRun()) + self.assertTrue(ws.getSampleDetails()) + self.assertTrue(ws.getHistory()) + if logs: + self.assertTrue(ws.getRun().hasProperty('qmin')) + self.assertTrue(ws.getRun().hasProperty('qmax')) + self.assertTrue(ws.getRun().hasProperty('l2')) + self.assertTrue(ws.getRun().hasProperty('pixel_height')) + self.assertTrue(ws.getRun().hasProperty('pixel_width')) + self.assertTrue(ws.getRun().hasProperty('collimation.actual_position')) + +if __name__ == '__main__': + unittest.main() diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/WANDPowderReductionTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/WANDPowderReductionTest.py index c2e890f55927f96e49a16e70a9a0f9b8da59b8ae..dbf44069da87890e1105c02ce9477daddde41970 100644 --- a/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/WANDPowderReductionTest.py +++ b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/WANDPowderReductionTest.py @@ -130,11 +130,11 @@ class WANDPowderReductionTest(unittest.TestCase): x = pd_out4.extractX() y = pd_out4.extractY() - self.assertAlmostEqual(x.min(), 0.53479223) - self.assertAlmostEqual(x.max(), 3.21684994) - self.assertAlmostEqual(y.min(), 0) - self.assertAlmostEqual(y.max(), 19.9948756) - self.assertAlmostEqual(x[0,y.argmax()], 2.9122841) + self.assertAlmostEqual(x.min(), 0.53479223, places=4) + self.assertAlmostEqual(x.max(), 3.21684994, places=4) + self.assertAlmostEqual(y.min(), 0, places=4) + self.assertAlmostEqual(y.max(), 19.9948756, places=4) + self.assertAlmostEqual(x[0,y.argmax()], 2.9122841, places=4) # data, cal and background, scale background pd_out4=WANDPowderReduction(InputWorkspace=data, diff --git a/Framework/PythonInterface/test/python/plugins/functions/MsdGaussTest.py b/Framework/PythonInterface/test/python/plugins/functions/MsdGaussTest.py index 60f0cba3d67a5d7c06789a26dc548e2eff9047b5..2126d5f4edc9f23ae6b2f98bf2dceced5a42265a 100644 --- a/Framework/PythonInterface/test/python/plugins/functions/MsdGaussTest.py +++ b/Framework/PythonInterface/test/python/plugins/functions/MsdGaussTest.py @@ -23,15 +23,15 @@ class MsdGaussTest(unittest.TestCase): input = np.array([[0, 1], [2, 3]]) expected = np.array([[1., 0.95122942], [0.81873075, 0.63762815]]) tolerance = 0.000001 - status, output = check_output("MsdGauss", input, expected, tolerance, Height=1.0, MSD=0.05) + status, output = check_output("MsdGauss", input, expected, tolerance, Height=1.0, Msd=0.05) if not status: self.fail("Computed output " + str(output) + " from input " + str(input) + " is not equal to the expected output: " + str(expected)) def test_use_in_fit(self): - workspace = create_test_workspace(create_model("MsdGauss", Height=1.0, MSD=0.05), 1000) - function_string = create_function_string("MsdGauss", Height=1.0, MSD=0.05) + workspace = create_test_workspace(create_model("MsdGauss", Height=1.0, Msd=0.05), 1000) + function_string = create_function_string("MsdGauss", Height=1.0, Msd=0.05) Fit(Function=function_string, InputWorkspace=workspace, StartX=1.2, EndX=1200) diff --git a/Framework/PythonInterface/test/python/plugins/functions/MsdPetersTest.py b/Framework/PythonInterface/test/python/plugins/functions/MsdPetersTest.py index 62797361d291a8747625b2e835738f7f18b532bc..86ff0730ccdaf2d7c59367c7aa96855ba4c57474 100644 --- a/Framework/PythonInterface/test/python/plugins/functions/MsdPetersTest.py +++ b/Framework/PythonInterface/test/python/plugins/functions/MsdPetersTest.py @@ -23,15 +23,15 @@ class MsdPetersTest(unittest.TestCase): input = np.array([[0, 1], [2, 3]]) expected = np.array([[1., 0.99173554], [0.96774194, 0.93023256]]) tolerance = 0.000001 - status, output = check_output("MsdPeters", input, expected, tolerance, Height=1.0, MSD=0.05, Beta=1.0) + status, output = check_output("MsdPeters", input, expected, tolerance, Height=1.0, Msd=0.05, Beta=1.0) if not status: self.fail("Computed output " + str(output) + " from input " + str(input) + " is not equal to the expected output: " + str(expected)) def test_use_in_fit(self): - workspace = create_test_workspace(create_model("MsdPeters", Height=1.0, MSD=0.05, Beta=1.0), 1000) - function_string = create_function_string("MsdPeters", Height=1.0, MSD=0.05, Beta=1.0) + workspace = create_test_workspace(create_model("MsdPeters", Height=1.0, Msd=0.05, Beta=1.0), 1000) + function_string = create_function_string("MsdPeters", Height=1.0, Msd=0.05, Beta=1.0) Fit(Function=function_string, InputWorkspace=workspace, StartX=1.2, EndX=1200) diff --git a/Framework/PythonInterface/test/python/plugins/functions/MsdYiTest.py b/Framework/PythonInterface/test/python/plugins/functions/MsdYiTest.py index 2558863e09090090db257620551250cf09e50232..4a1e5431d573283d2c73a486801b50ac2adc5d42 100644 --- a/Framework/PythonInterface/test/python/plugins/functions/MsdYiTest.py +++ b/Framework/PythonInterface/test/python/plugins/functions/MsdYiTest.py @@ -21,18 +21,30 @@ class MsdYiTest(unittest.TestCase): def test_function_output(self): input = np.array([[1, 2], [3, 4]]) - expected = np.array([[0.03616947, 0.53117559], [1.46726692, 3.69882113]]) + expected = np.array( + [[1.00547492, 1.18215301], [1.97145491, 3.98690068]]) tolerance = 0.000001 - status, output = check_output("MsdYi", input, expected, tolerance, Height=1.0, MSD=0.05, Sigma=1.0) + status, output = check_output( + "MsdYi", input, expected, tolerance, Height=1.0, Msd=0.05, Sigma=1.0) if not status: self.fail("Computed output " + str(output) + " from input " + str(input) + " is not equal to the expected output: " + str(expected)) def test_use_in_fit(self): - workspace = create_test_workspace(create_model("MsdYi", Height=1.0, MSD=0.05, Sigma=1.0), 1000) - function_string = create_function_string("MsdYi", Height=1.0, MSD=0.05, Sigma=1.0) - Fit(Function=function_string, InputWorkspace=workspace, StartX=1.2, EndX=1200) + workspace = create_test_workspace( + create_model( + "MsdYi", + Height=1.0, + Msd=0.05, + Sigma=1.0), + 1000) + function_string = create_function_string( + "MsdYi", Height=1.0, Msd=0.05, Sigma=1.0) + Fit(Function=function_string, + InputWorkspace=workspace, + StartX=1.2, + EndX=1200) if __name__ == '__main__': diff --git a/Framework/PythonInterface/test/testhelpers/illhelpers.py b/Framework/PythonInterface/test/testhelpers/illhelpers.py index 6ca627e81d062b8603e18c8ddac34a595a8d3808..34c6b39d6436ffa4b615e2025de7e665f9e32d2e 100644 --- a/Framework/PythonInterface/test/testhelpers/illhelpers.py +++ b/Framework/PythonInterface/test/testhelpers/illhelpers.py @@ -383,12 +383,13 @@ def refl_rotate_detector(ws, angle): run_algorithm('RotateInstrumentComponent', **args) -def refl_sum_foreground(outputWSName, sumType, ws, dirFgdWS=None): +def refl_sum_foreground(outputWSName, sumType, ws, dirFgdWS=None, dirWS=None): args = { 'InputWorkspace': ws, 'OutputWorkspace': outputWSName, 'SummationType': sumType, 'DirectForegroundWorkspace': dirFgdWS, + 'DirectBeamWorkspace': dirWS, 'WavelengthRange': [0.1] } alg = create_algorithm('ReflectometryILLSumForeground', **args) diff --git a/Framework/TestHelpers/inc/MantidTestHelpers/IndirectFitDataCreationHelper.h b/Framework/TestHelpers/inc/MantidTestHelpers/IndirectFitDataCreationHelper.h new file mode 100644 index 0000000000000000000000000000000000000000..ddce2d0ed52e0a33a216d7d83559289e143d6262 --- /dev/null +++ b/Framework/TestHelpers/inc/MantidTestHelpers/IndirectFitDataCreationHelper.h @@ -0,0 +1,81 @@ +#ifndef MANTID_INDIRECTFITDATACREATIONHELPER_H_ +#define MANTID_INDIRECTFITDATACREATIONHELPER_H_ + +#include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/MatrixWorkspace_fwd.h" +#include "MantidHistogramData/BinEdges.h" + +#include <string> + +#include <boost/variant.hpp> +#include <boost/variant/apply_visitor.hpp> +#include <boost/variant/static_visitor.hpp> + +namespace Mantid { +namespace IndirectFitDataCreationHelper { + +/// Functions used in the creation of workspaces +Mantid::API::MatrixWorkspace_sptr createWorkspace(int const &numberOfSpectra); +Mantid::API::MatrixWorkspace_sptr createInstrumentWorkspace(int const &xLength, + int const &yLength); +Mantid::API::MatrixWorkspace_sptr +setWorkspaceEFixed(Mantid::API::MatrixWorkspace_sptr workspace, + int const &xLength); +Mantid::API::MatrixWorkspace_sptr +setWorkspaceBinEdges(Mantid::API::MatrixWorkspace_sptr workspace, + int const &yLength, + Mantid::HistogramData::BinEdges const &binEdges); +Mantid::API::MatrixWorkspace_sptr +setWorkspaceBinEdges(Mantid::API::MatrixWorkspace_sptr workspace, + int const &xLength, int const &yLength); +Mantid::API::MatrixWorkspace_sptr +setWorkspaceProperties(Mantid::API::MatrixWorkspace_sptr workspace, + int const &xLength, int const &yLength); +Mantid::API::MatrixWorkspace_sptr +createWorkspaceWithInstrument(int const &xLength, int const &yLength); + +/// Simple struct used to access features of the ADS +/// No destructor so ensure you tearDown the ADS +struct SetUpADSWithWorkspace { + + template <typename T> + SetUpADSWithWorkspace(std::string const &inputWSName, T const &workspace) { + Mantid::API::AnalysisDataService::Instance().addOrReplace(inputWSName, + workspace); + } + + template <typename T> + void addOrReplace(std::string const &workspaceName, T const &workspace) { + Mantid::API::AnalysisDataService::Instance().addOrReplace(workspaceName, + workspace); + } + + bool doesExist(std::string const &workspaceName) { + return Mantid::API::AnalysisDataService::Instance().doesExist( + workspaceName); + } + + Mantid::API::MatrixWorkspace_sptr + retrieveWorkspace(std::string const &workspaceName) { + return boost::dynamic_pointer_cast<Mantid::API::MatrixWorkspace>( + Mantid::API::AnalysisDataService::Instance().retrieve(workspaceName)); + } +}; + +/// This is used to compare Spectra which is implemented as a boost::variant +struct AreSpectraEqual : public boost::static_visitor<bool> { + + template <typename T, typename U> + bool operator()(const T &, const U &) const { + return false; // cannot compare different types + } + + template <typename T> bool operator()(const T &lhs, const T &rhs) const { + return lhs == rhs; + } +}; + +} // namespace IndirectFitDataCreationHelper +} // namespace Mantid + +#endif // MANTID_INDIRECTFITDATACREATIONHELPER_H diff --git a/Framework/TestHelpers/inc/MantidTestHelpers/MuonWorkspaceCreationHelper.h b/Framework/TestHelpers/inc/MantidTestHelpers/MuonWorkspaceCreationHelper.h index 90475c964ef3d3400d616c13ba310e11c4ed0bd4..d6a6010f4c56ee05a28694baad62e23d1e9162f0 100644 --- a/Framework/TestHelpers/inc/MantidTestHelpers/MuonWorkspaceCreationHelper.h +++ b/Framework/TestHelpers/inc/MantidTestHelpers/MuonWorkspaceCreationHelper.h @@ -92,18 +92,27 @@ Mantid::API::MatrixWorkspace_sptr createCountsWorkspace(size_t nspec, size_t maxt, double seed, size_t detectorIDseed = 1); +Mantid::API::MatrixWorkspace_sptr +createCountsWorkspace(size_t nspec, size_t maxt, double seed, + size_t detectorIDseed, bool hist, double xStart, + double xEnd); + /** - * Create a WorkspaceGroup and add to the ADS, populate with MatrixWorkspaces - * simulating periods as used in muon analysis. Workspace for period i has a - * name ending _i. + * Create a WorkspaceGroup and add to the ADS, populate with + * MatrixWorkspaces simulating periods as used in muon analysis. Workspace + * for period i has a name ending _i. */ Mantid::API::WorkspaceGroup_sptr createMultiPeriodWorkspaceGroup(const int &nPeriods, size_t nspec, size_t maxt, const std::string &wsGroupName); +Mantid::API::WorkspaceGroup_sptr +createMultiPeriodAsymmetryData(const int &nPeriods, size_t nspec, size_t maxt, + const std::string &wsGroupName); + /** - * Create a simple dead time TableWorkspace with two columns (spectrum number - * and dead time). + * Create a simple dead time TableWorkspace with two columns (spectrum + * number and dead time). */ Mantid::API::ITableWorkspace_sptr createDeadTimeTable(const size_t &nspec, std::vector<double> &deadTimes); diff --git a/Framework/TestHelpers/src/IndirectFitDataCreationHelper.cpp b/Framework/TestHelpers/src/IndirectFitDataCreationHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..83082b0c41ef340802cfdb8591cfd883832370ba --- /dev/null +++ b/Framework/TestHelpers/src/IndirectFitDataCreationHelper.cpp @@ -0,0 +1,60 @@ +#include "MantidTestHelpers/IndirectFitDataCreationHelper.h" +#include "MantidTestHelpers/WorkspaceCreationHelper.h" + +namespace Mantid { +namespace IndirectFitDataCreationHelper { +using namespace Mantid::API; + +MatrixWorkspace_sptr createWorkspace(int const &numberOfSpectra) { + return WorkspaceCreationHelper::create2DWorkspace(numberOfSpectra, 10); +} + +MatrixWorkspace_sptr createInstrumentWorkspace(int const &xLength, + int const &yLength) { + return WorkspaceCreationHelper::create2DWorkspaceWithFullInstrument( + xLength, yLength - 1, false, false, true, "testInst"); +} + +MatrixWorkspace_sptr setWorkspaceEFixed(MatrixWorkspace_sptr workspace, + int const &xLength) { + for (int i = 0; i < xLength; ++i) + workspace->setEFixed((i + 1), 0.50); + return workspace; +} + +MatrixWorkspace_sptr +setWorkspaceBinEdges(MatrixWorkspace_sptr workspace, int const &yLength, + Mantid::HistogramData::BinEdges const &binEdges) { + for (int i = 0; i < yLength; ++i) + workspace->setBinEdges(i, binEdges); + return workspace; +} + +MatrixWorkspace_sptr setWorkspaceBinEdges(MatrixWorkspace_sptr workspace, + int const &xLength, + int const &yLength) { + Mantid::HistogramData::BinEdges binEdges(xLength - 1, 0.0); + int j = 0; + std::generate(begin(binEdges), end(binEdges), + [&j] { return 0.5 + 0.75 * ++j; }); + setWorkspaceBinEdges(workspace, yLength, binEdges); + return workspace; +} + +MatrixWorkspace_sptr setWorkspaceProperties(MatrixWorkspace_sptr workspace, + int const &xLength, + int const &yLength) { + setWorkspaceBinEdges(workspace, xLength, yLength); + setWorkspaceEFixed(workspace, xLength); + return workspace; +} + +MatrixWorkspace_sptr createWorkspaceWithInstrument(int const &xLength, + int const &yLength) { + auto workspace = createInstrumentWorkspace(xLength, yLength); + workspace->initialize(yLength, xLength, xLength - 1); + return setWorkspaceProperties(workspace, xLength, yLength); +} + +} // namespace IndirectFitDataCreationHelper +} // namespace Mantid diff --git a/Framework/TestHelpers/src/MuonWorkspaceCreationHelper.cpp b/Framework/TestHelpers/src/MuonWorkspaceCreationHelper.cpp index 7241c7013076347d7222b998037665794ad02534..285ccd2d22aa93c74cb54a254ceaadbff337aa07 100644 --- a/Framework/TestHelpers/src/MuonWorkspaceCreationHelper.cpp +++ b/Framework/TestHelpers/src/MuonWorkspaceCreationHelper.cpp @@ -65,15 +65,20 @@ double eData::operator()(const double, size_t) { return 0.005; } * Number of bins = maxt - 1 . * @param seed :: Number added to all y-values. * @param detectorIDseed :: detector IDs starting from this number. + * @param isHist :: Whether to output histogram data or not + * @param xStart :: The start value of the x-axis. + * @param xEnd :: The end value of the x-axis. * @return Pointer to the workspace. */ MatrixWorkspace_sptr createCountsWorkspace(size_t nspec, size_t maxt, - double seed, size_t detectorIDseed) { + double seed, size_t detectorIDseed, + bool isHist, double xStart, + double xEnd) { MatrixWorkspace_sptr ws = WorkspaceCreationHelper::create2DWorkspaceFromFunction( - yDataCounts(), static_cast<int>(nspec), 0.0, 1.0, - (1.0 / static_cast<double>(maxt)), true, eData()); + yDataCounts(), static_cast<int>(nspec), xStart, xEnd, + (1.0 / static_cast<double>(maxt)), isHist, eData()); ws->setInstrument(ComponentCreationHelper::createTestInstrumentCylindrical( static_cast<int>(nspec))); @@ -97,6 +102,12 @@ MatrixWorkspace_sptr createCountsWorkspace(size_t nspec, size_t maxt, return ws; } +MatrixWorkspace_sptr createCountsWorkspace(size_t nspec, size_t maxt, + double seed, size_t detectorIDseed) { + return createCountsWorkspace(nspec, maxt, seed, detectorIDseed, true, 0.0, + 1.0); +} + /** * Create a WorkspaceGroup and add to the ADS, populate with MatrixWorkspaces * simulating periods as used in muon analysis. Workspace for period i has a @@ -136,6 +147,33 @@ createMultiPeriodWorkspaceGroup(const int &nPeriods, size_t nspec, size_t maxt, return wsGroup; } +Mantid::API::WorkspaceGroup_sptr +createMultiPeriodAsymmetryData(const int &nPeriods, size_t nspec, size_t maxt, + const std::string &wsGroupName) { + Mantid::API::WorkspaceGroup_sptr wsGroup = + boost::make_shared<Mantid::API::WorkspaceGroup>(); + Mantid::API::AnalysisDataService::Instance().addOrReplace(wsGroupName, + wsGroup); + + std::string wsNameStem = "MuonDataPeriod_"; + std::string wsName; + + boost::shared_ptr<Mantid::Geometry::Instrument> inst1 = + boost::make_shared<Mantid::Geometry::Instrument>(); + inst1->setName("EMU"); + + for (int period = 1; period < nPeriods + 1; period++) { + Mantid::API::MatrixWorkspace_sptr ws = createAsymmetryWorkspace( + nspec, maxt, yDataAsymmetry(10.0 * period, 0.1 * period)); + + wsGroup->addWorkspace(ws); + wsName = wsNameStem + std::to_string(period); + Mantid::API::AnalysisDataService::Instance().addOrReplace(wsName, ws); + } + + return wsGroup; +} + /** * Create a simple dead time TableWorkspace with two columns (spectrum number * and dead time). diff --git a/Framework/Types/src/Core/DateAndTimeHelpers.cpp b/Framework/Types/src/Core/DateAndTimeHelpers.cpp index 753bf82a84a4d89fb033cdaf32b0dfad9eea347d..902698318804c407a3be8b26399921a1563f7a87 100644 --- a/Framework/Types/src/Core/DateAndTimeHelpers.cpp +++ b/Framework/Types/src/Core/DateAndTimeHelpers.cpp @@ -20,11 +20,10 @@ namespace DateAndTimeHelpers { bool stringIsISO8601(const std::string &date) { // Expecting most of Mantid's time stamp strings to be in the // extended format --- check it first. - // On Ubuntu 14.04, std::regex seems to be broken, thus boost. - const boost::regex extendedFormat( + static const boost::regex extendedFormat( R"(^\d{4}-[01]\d-[0-3]\d([T\s][0-2]\d:[0-5]\d(:\d{2})?(.\d+)?(Z|[+-]\d{2}(:?\d{2})?)?)?$)"); if (!boost::regex_match(date, extendedFormat)) { - const boost::regex basicFormat( + static const boost::regex basicFormat( R"(^\d{4}[01]\d[0-3]\d([T\s][0-2]\d[0-5]\d(\d{2})?(.\d+)?(Z|[+-]\d{2}(:?\d{2})?)?)?$)"); return boost::regex_match(date, basicFormat); } @@ -38,7 +37,7 @@ bool stringIsISO8601(const std::string &date) { */ bool stringIsPosix(const std::string &date) { // Formatting taken from boost::to_simple_string. - const boost::regex format( + static const boost::regex format( R"(^\d{4}-[A-Z][a-z]{2}-[0-3]\d\s[0-2]\d:[0-5]\d:\d{2}(.\d+)?$)"); return boost::regex_match(date, format); } diff --git a/Framework/Types/test/DateAndTimeTest.h b/Framework/Types/test/DateAndTimeTest.h index 9b2e79db4eff5755cd7b3e5b399f6a20bdb081bc..ea8e3c075acb9b59af8ed2474fcd40c2750c684b 100644 --- a/Framework/Types/test/DateAndTimeTest.h +++ b/Framework/Types/test/DateAndTimeTest.h @@ -461,4 +461,14 @@ public: } }; +class DateAndTimeTestPerformance : public CxxTest::TestSuite { +public: + void test_construction_from_iso8601_string() { + for (size_t i = 0; i < 500000; ++i) { + DateAndTime d("2010-03-24T14:12:51.562Z"); + d.totalNanoseconds(); + } + } +}; + #endif /* DATEANDTIMETEST_H_ */ diff --git a/MantidPlot/CMakeLists.txt b/MantidPlot/CMakeLists.txt index 9006e50ef3175fbdb82269c12129baf4258ef749..0d6e1c9ebeb076ec8695026785b7cf3b38014beb 100644 --- a/MantidPlot/CMakeLists.txt +++ b/MantidPlot/CMakeLists.txt @@ -153,6 +153,11 @@ set ( QTIPLOT_SRCS src/ApplicationWindow.cpp src/lib/src/SymbolDialog.cpp src/lib/src/TextFormatButtons.cpp src/lib/3rdparty/qtcolorpicker/src/qtcolorpicker.cpp + src/ProjectRecoveryGUIs/ProjectRecoveryModel.cpp + src/ProjectRecoveryGUIs/ProjectRecoveryPresenter.cpp + src/ProjectRecoveryGUIs/ProjectRecoveryView.cpp + src/ProjectRecoveryGUIs/RecoveryFailureView.cpp + src/ProjectRecoveryGUIs/RecoveryThread.cpp ) set ( QTIPLOT_C_SRC src/zlib123/minigzip.c ) @@ -355,6 +360,11 @@ set ( QTIPLOT_HDRS src/ApplicationWindow.h src/lib/include/SymbolDialog.h src/lib/include/TextFormatButtons.h src/lib/3rdparty/qtcolorpicker/src/qtcolorpicker.h + src/ProjectRecoveryGUIs/ProjectRecoveryModel.h + src/ProjectRecoveryGUIs/ProjectRecoveryPresenter.h + src/ProjectRecoveryGUIs/ProjectRecoveryView.h + src/ProjectRecoveryGUIs/RecoveryFailureView.h + src/ProjectRecoveryGUIs/RecoveryThread.h ) set ( MANTID_HDRS src/Mantid/AlgorithmMonitor.h @@ -609,6 +619,9 @@ set ( MANTID_MOC_FILES src/Mantid/AlgorithmDockWidget.h src/Mantid/SampleLogDialogBase.h src/Mantid/UserFitFunctionDialog.h src/Mantid/InstrumentWidget/InstrumentWindow.h + src/ProjectRecoveryGUIs/ProjectRecoveryView.h + src/ProjectRecoveryGUIs/RecoveryFailureView.h + src/ProjectRecoveryGUIs/RecoveryThread.h ) set ( UI_FILES src/SendToProgramDialog.ui @@ -621,6 +634,8 @@ set ( UI_FILES src/SendToProgramDialog.ui src/Mantid/ManageInterfaceCategories.ui src/Mantid/MantidMDCurveDialog.ui src/Mantid/MantidSampleMaterialDialog.ui + src/ProjectRecoveryGUIs/ProjectRecoveryWidget.ui + src/ProjectRecoveryGUIs/RecoveryFailure.ui ) qt4_wrap_cpp ( MOCCED_FILES ${QTIPLOT_MOC_FILES} ${MANTID_MOC_FILES} ) @@ -652,7 +667,6 @@ include_directories ( ${CMAKE_CURRENT_BINARY_DIR} ) ########################################################################### qt4_add_resources ( RES_FILES ${PROJECT_SOURCE_DIR}/images/images.qrc ) -qt4_add_resources ( RES_FILES ${PROJECT_SOURCE_DIR}/images/MantidWidgets.qrc ) qt4_add_resources ( RES_FILES ${PROJECT_SOURCE_DIR}/images//fonts/fonts.qrc ) qt4_add_resources ( RES_FILES ${CMAKE_CURRENT_SOURCE_DIR}/icons/icons.qrc ) qt4_add_resources ( RES_FILES ${PROJECT_SOURCE_DIR}/scripts/ErrorReporter/errorreporter.qrc ) diff --git a/MantidPlot/make_package.rb.in b/MantidPlot/make_package.rb.in index 257b05efa752aca660dad65710f5a6f2a8312d29..2b7ffea2dc202a6f9363aaae9275fa890c8df5c3 100755 --- a/MantidPlot/make_package.rb.in +++ b/MantidPlot/make_package.rb.in @@ -335,22 +335,30 @@ end #Copy over python libraries not included with OSX. #currently missing epics -path = "/Library/Python/2.7/site-packages" +system_python_extras = "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python" +pip_site_packages = "/Library/Python/2.7/site-packages" directories = ["sphinx","sphinx_bootstrap_theme","IPython","zmq","pygments","backports", "qtawesome", "qtpy", - "certifi","tornado","markupsafe","jinja2","jsonschema","functools32","ptyprocess","CifFile","yaml"] + "certifi","tornado","markupsafe","matplotlib","mpl_toolkits", "jinja2","jsonschema","functools32", + "ptyprocess","CifFile","yaml","requests"] directories.each do |directory| - addPythonLibrary("#{path}/#{directory}","Contents/MacOS/") + module_dir = "#{pip_site_packages}/#{directory}" + if !File.exist?(module_dir) + module_dir = "#{system_python_extras}/#{directory}" + if !File.exist?(module_dir) + p "Cannot find python module #{directory} to copy into bundle" + exit 1 + end + end + addPythonLibrary(module_dir,"Contents/MacOS/") end -# System mpl_toolkits in macOS 10.12 and above do not have __init__.py which causes a problem importing -# So we pack the mpl_toolkits and touch an empty file to temporarily work around it -#TODO: consider installing mpl_toolkits (thus matplotlib) with pip -system_python_extras = "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python" -modules = ["mpl_toolkits"] -modules.each do |directory| - addPythonLibrary("#{system_python_extras}/#{directory}","Contents/MacOS/") +# Some versions of mpltool_kits are missing their __init__ file +mpltoolkit_init = "Contents/MacOS/mpl_toolkits/__init__.py" +if !File.exist?(mpltoolkit_init) + p "Creating missing #{mpltoolkit_init}" + `touch #{mpltoolkit_init}` end -`touch Contents/MacOS/mpl_toolkits/__init__.py` + if( "@MAKE_VATES@" == "ON" ) addPythonLibrariesInDirectory("#{ParaView_dir}/lib/site-packages","Contents/Python/") @@ -389,13 +397,13 @@ h5py_patterns.each do |pattern| end end -files = ["gnureadline.so","readline.py","pyparsing.py","mistune.py"] +files = ["gnureadline.so","cycler.py","readline.py","pyparsing.py","mistune.py"] files.each do |file| - copyFile("#{path}/#{file}") + copyFile("#{pip_site_packages}/#{file}") end -#mistune.so isn't present in v0.7 -copyOptionalFile("#{path}/mistune.so") +# mistune.so isn't present in v0.7 +copyOptionalFile("#{pip_site_packages}/mistune.so") `mkdir Contents/MacOS/bin` `cp /usr/local/bin/ipython@PYTHON_VERSION_MAJOR@ Contents/MacOS/bin/` diff --git a/MantidPlot/mantidplot.py b/MantidPlot/mantidplot.py index c04391e2dc771f7d61e6660b4d25c3a7a203c641..995893ed0c97b72cd236090463d452198ca812d4 100644 --- a/MantidPlot/mantidplot.py +++ b/MantidPlot/mantidplot.py @@ -13,8 +13,24 @@ from __future__ import (absolute_import, division, print_function) +import os.path as osp import pymantidplot from pymantidplot import * # and the old qtiplot stuff import pymantidplot.qtiplot + +def load_ui(caller_filename, ui_relfilename, baseinstance=None): + '''This is copied from mantidqt.utils.qt and should be deprecated as + soon as possible.''' + from qtpy.uic import loadUi, loadUiType # noqa + + filepath = osp.join(osp.dirname(caller_filename), ui_relfilename) + if not osp.exists(filepath): + raise ImportError('File "{}" does not exist'.format(filepath)) + if not osp.isfile(filepath): + raise ImportError('File "{}" is not a file'.format(filepath)) + if baseinstance is not None: + return loadUi(filepath, baseinstance=baseinstance) + else: + return loadUiType(filepath) diff --git a/MantidPlot/src/ApplicationWindow.cpp b/MantidPlot/src/ApplicationWindow.cpp index d17cb9f527b1906fd62128aacd16947dfc95d2c0..2bac464a07dfc705a59aa93975d7c6662e881fd6 100644 --- a/MantidPlot/src/ApplicationWindow.cpp +++ b/MantidPlot/src/ApplicationWindow.cpp @@ -181,7 +181,6 @@ #include <gsl/gsl_sort.h> #include <boost/regex.hpp> -#include <boost/scoped_ptr.hpp> #include <Poco/Path.h> @@ -16668,8 +16667,13 @@ void ApplicationWindow::onAboutToStart() { resultsLog->scrollToTop(); // Kick off project recovery - g_log.debug("Starting project autosaving."); - checkForProjectRecovery(); + if (Mantid::Kernel::ConfigService::Instance().getString( + "projectRecovery.enabled") == "true") { + g_log.debug("Starting project autosaving."); + checkForProjectRecovery(); + } else { + g_log.debug("Project Recovery is disabled."); + } } /** @@ -16779,11 +16783,26 @@ bool ApplicationWindow::isOfType(const QObject *obj, * @param sourceFile The full path to the .project file * @return True is loading was successful, false otherwise */ -bool ApplicationWindow::loadProjectRecovery(std::string sourceFile) { +bool ApplicationWindow::loadProjectRecovery(std::string sourceFile, + std::string recoveryFolder) { + // Wait on this thread until scriptWindow is finished (Should be a seperate + // thread) + do { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } while (scriptingWindow->isExecuting()); const bool isRecovery = true; ProjectSerialiser projectWriter(this, isRecovery); // File version is not applicable to project recovery - so set to 0 - return projectWriter.load(sourceFile, 0); + const auto loadSuccess = projectWriter.load(sourceFile, 0); + + // Handle the removal of old checkpoints and start project saving again + Poco::Path deletePath(recoveryFolder); + deletePath.setFileName(""); + deletePath.popDirectory(); + m_projectRecovery.clearAllCheckpoints(deletePath); + m_projectRecovery.startProjectSaving(); + + return loadSuccess; } /** @@ -16809,6 +16828,9 @@ void ApplicationWindow::checkForProjectRecovery() { m_projectRecovery.removeOlderCheckpoints(); + // Mantid crashed during writing to this checkpoint so remove it + m_projectRecovery.removeLockedCheckpoints(); + if (!m_projectRecovery.checkForRecovery()) { m_projectRecovery.startProjectSaving(); return; @@ -16834,4 +16856,4 @@ void ApplicationWindow::checkForProjectRecovery() { void ApplicationWindow::saveRecoveryCheckpoint() { m_projectRecovery.saveAll(false); -} \ No newline at end of file +} diff --git a/MantidPlot/src/ApplicationWindow.h b/MantidPlot/src/ApplicationWindow.h index 60b7fbb789d84942c762e87351e915aab8f37adf..db0236aa8bd6cb84681b729c9dd6c6dd4eba30fc 100644 --- a/MantidPlot/src/ApplicationWindow.h +++ b/MantidPlot/src/ApplicationWindow.h @@ -1124,7 +1124,7 @@ public slots: bool isOfType(const QObject *obj, const char *toCompare) const; - bool loadProjectRecovery(std::string sourceFile); + bool loadProjectRecovery(std::string sourceFile, std::string recoveryFolder); // The string must be copied from the other thread in saveProjectRecovery /// Saves the current project as part of recovery auto saving diff --git a/MantidPlot/src/Graph.cpp b/MantidPlot/src/Graph.cpp index 5cc523b7e1871613f9abad675412cd8a9f31b19d..e969a22b4632c3d20efc890826e53e79405f1e7e 100644 --- a/MantidPlot/src/Graph.cpp +++ b/MantidPlot/src/Graph.cpp @@ -1355,7 +1355,7 @@ void Graph::setAxisScale(int axis, double start, double end, int scaleType, start = sp->getMinPositiveValue(); } sp->mutableColorMap().changeScaleType( - (GraphOptions::ScaleType)type); + (MantidColorMap::ScaleType)type); sp->mutableColorMap().setNthPower(sc_engine->nthPower()); rightAxis->setColorMap(QwtDoubleInterval(start, end), sp->getColorMap()); @@ -4732,12 +4732,14 @@ Spectrogram *Graph::plotSpectrogram(Spectrogram *d_spectrogram, d_spectrogram->setDisplayMode(QwtPlotSpectrogram::ImageMode, false); d_spectrogram->setDisplayMode(QwtPlotSpectrogram::ContourMode, true); } else if (type == GraphOptions::ColorMap) { - d_spectrogram->mutableColorMap().changeScaleType(GraphOptions::Linear); + d_spectrogram->mutableColorMap().changeScaleType( + MantidColorMap::ScaleType::Linear); d_spectrogram->setDefaultColorMap(); d_spectrogram->setDisplayMode(QwtPlotSpectrogram::ImageMode, true); d_spectrogram->setDisplayMode(QwtPlotSpectrogram::ContourMode, false); } else if (type == GraphOptions::ColorMapContour) { - d_spectrogram->mutableColorMap().changeScaleType(GraphOptions::Linear); + d_spectrogram->mutableColorMap().changeScaleType( + MantidColorMap::ScaleType::Linear); d_spectrogram->setDefaultColorMap(); d_spectrogram->setDisplayMode(QwtPlotSpectrogram::ImageMode, true); d_spectrogram->setDisplayMode(QwtPlotSpectrogram::ContourMode, true); diff --git a/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindow.cpp b/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindow.cpp index b1a8f911e167c44e9b7b67b3d19bec2441d25e46..3d178e274f4e244fbb23c6d30c18068a11201806 100644 --- a/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindow.cpp +++ b/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindow.cpp @@ -172,7 +172,8 @@ void InstrumentWindow::selectComponent(const QString &name) { } void InstrumentWindow::setScaleType(GraphOptions::ScaleType type) { - return m_instrumentWidget->setScaleType(type); + return m_instrumentWidget->setScaleType( + static_cast<MantidColorMap::ScaleType>(type)); } void InstrumentWindow::setViewType(const QString &type) { diff --git a/MantidPlot/src/MultiTabScriptInterpreter.h b/MantidPlot/src/MultiTabScriptInterpreter.h index 00b51ad78da2b45a3a76b7e53298f26af27ed7cd..c57a57742e348c140601b85e8cea02c45b8a753d 100644 --- a/MantidPlot/src/MultiTabScriptInterpreter.h +++ b/MantidPlot/src/MultiTabScriptInterpreter.h @@ -50,7 +50,7 @@ public: ~MultiTabScriptInterpreter() override; /// Current interpreter - ScriptFileInterpreter *currentInterpreter(); + ScriptFileInterpreter *currentInterpreter() { return m_current; }; /// Interpreter at given index ScriptFileInterpreter *interpreterAt(int index); diff --git a/MantidPlot/src/ProjectRecovery.cpp b/MantidPlot/src/ProjectRecovery.cpp index 186248c9e7aa4945416aa54079fa4bde95323065..84ed23f4a5bd056b3a2ca1a4f3421e71bb641786 100644 --- a/MantidPlot/src/ProjectRecovery.cpp +++ b/MantidPlot/src/ProjectRecovery.cpp @@ -9,12 +9,17 @@ #include "ApplicationWindow.h" #include "Folder.h" #include "Process.h" +#include "ProjectRecoveryGUIs/ProjectRecoveryPresenter.h" +#include "ProjectRecoveryGUIs/ProjectRecoveryView.h" +#include "ProjectRecoveryGUIs/RecoveryFailureView.h" #include "ProjectSerialiser.h" #include "ScriptingWindow.h" #include "MantidAPI/AlgorithmManager.h" +#include "MantidAPI/AnalysisDataService.h" #include "MantidAPI/FileProperty.h" #include "MantidAPI/Workspace.h" +#include "MantidAPI/WorkspaceGroup.h" #include "MantidAPI/WorkspaceHistory.h" #include "MantidKernel/ConfigService.h" #include "MantidKernel/Logger.h" @@ -57,10 +62,6 @@ boost::optional<T> getConfigValue(const std::string &key) { return Mantid::Kernel::ConfigService::Instance().getValue<T>(key); } -boost::optional<bool> getConfigBool(const std::string &key) { - return Mantid::Kernel::ConfigService::Instance().getValue<bool>(key); -} - /// Returns a string to the folder it should output to std::string getRecoveryFolderOutput() { static std::string appData = @@ -222,16 +223,52 @@ getRecoveryFolderCheckpoints(const std::string &recoveryFolderPath) { return folderPaths; } +void removeEmptyFolders(std::vector<Poco::Path> &checkpointPaths) { + for (auto i = 0u; i < checkpointPaths.size(); ++i) { + const auto listOfFolders = + getListOfFoldersInDirectory(checkpointPaths[i].toString()); + if (listOfFolders.size() == 0) { + // Remove actual folder to stop this happening again in further checks + Poco::File(checkpointPaths[i]).remove(true); + // Erase from checkpointPaths vector + checkpointPaths.erase(checkpointPaths.begin() + i); + } + } +} + +const std::string LOCK_FILE_NAME = "projectrecovery.lock"; + +Poco::File addLockFile(const Poco::Path &lockFilePath) { + Poco::File lockFile(Poco::Path(lockFilePath).append(LOCK_FILE_NAME)); + + // If file is already there ignore as it shouldn't be a problem. + lockFile.createFile(); + return lockFile; +} + +/** + * Checks the passed parameter and if it is an empty group then it returns true. + * + * @param ws :: check this workspace to see if it's an empty group + * @return true :: bool when it is an empty group + * @return false :: bool when it is not an empty group + */ +bool checkIfEmptyGroup(const Mantid::API::Workspace_sptr &ws) { + if (auto groupWS = + boost::dynamic_pointer_cast<Mantid::API::WorkspaceGroup>(ws)) { + if (groupWS->isEmpty()) { + g_log.debug("Empty group was present when recovery ran so was removed"); + return true; + } + } + return false; +} + const std::string OUTPUT_PROJ_NAME = "recovery.mantid"; -// Config keys -const std::string SAVING_ENABLED_CONFIG_KEY = "projectRecovery.enabled"; const std::string SAVING_TIME_KEY = "projectRecovery.secondsBetween"; const std::string NO_OF_CHECKPOINTS_KEY = "projectRecovery.numberOfCheckpoints"; -// Config values -bool SAVING_ENABLED = - getConfigBool(SAVING_ENABLED_CONFIG_KEY).get_value_or(false); const int SAVING_TIME = getConfigValue<int>(SAVING_TIME_KEY).get_value_or(60); // Seconds const int NO_OF_CHECKPOINTS = @@ -253,58 +290,36 @@ namespace MantidQt { */ ProjectRecovery::ProjectRecovery(ApplicationWindow *windowHandle) : m_backgroundSavingThread(), m_stopBackgroundThread(true), - m_configKeyObserver(*this, &ProjectRecovery::configKeyChanged), - m_windowPtr(windowHandle) {} + m_windowPtr(windowHandle), m_recoveryGui(nullptr) {} /// Destructor which also stops any background threads currently in progress -ProjectRecovery::~ProjectRecovery() { stopProjectSaving(); } +ProjectRecovery::~ProjectRecovery() { + stopProjectSaving(); + delete m_recoveryGui; +} void ProjectRecovery::attemptRecovery() { - QString recoveryMsg = QObject::tr( - "Mantid did not close correctly and a recovery" - " checkpoint has been found. Would you like to attempt recovery?"); - - int userChoice = QMessageBox::information( - m_windowPtr, QObject::tr("Project Recovery"), recoveryMsg, - QObject::tr("Yes"), QObject::tr("No"), - QObject::tr("Only open script in editor"), 0, 1); - - if (userChoice == 1) { - // User selected no - clearAllUnusedCheckpoints(); - this->startProjectSaving(); - return; - } + Mantid::Kernel::UsageService::Instance().registerFeatureUsage( + "Feature", "ProjectRecovery->AttemptRecovery", true); - auto beforeRecoveryFolder = getRecoveryFolderLoad(); - auto checkpointPaths = getRecoveryFolderCheckpoints(beforeRecoveryFolder); - auto mostRecentCheckpoint = checkpointPaths.back(); - - auto destFilename = - Poco::Path(Mantid::Kernel::ConfigService::Instance().getAppDataDir()); - destFilename.append("ordered_recovery.py"); - - if (userChoice == 0) { - // We have to spin up a new thread so the GUI can continue painting whilst - // we exec - openInEditor(mostRecentCheckpoint, destFilename); - std::thread recoveryThread( - [=] { loadRecoveryCheckpoint(mostRecentCheckpoint); }); - recoveryThread.detach(); - } else if (userChoice == 2) { - openInEditor(mostRecentCheckpoint, destFilename); - // Restart project recovery as we stay synchronous - clearAllCheckpoints(beforeRecoveryFolder); - startProjectSaving(); - } else { - throw std::runtime_error("Unknown choice in ProjectRecovery"); + m_recoveryGui = new ProjectRecoveryPresenter(this, m_windowPtr); + bool failed = m_recoveryGui->startRecoveryView(); + + if (failed) { + while (failed) { + failed = m_recoveryGui->startRecoveryFailure(); + } } } bool ProjectRecovery::checkForRecovery() const noexcept { try { - const auto checkpointPaths = + auto checkpointPaths = getRecoveryFolderCheckpoints(getRecoveryFolderCheck()); + // Since adding removal of checkpoints before this check it is possible that + // a PID is there with no checkpoint this loop fixes that issue removing + // them. + removeEmptyFolders(checkpointPaths); return checkpointPaths.size() != 0 && (checkpointPaths.size() > Process::numberOfMantids()); } catch (...) { @@ -314,17 +329,6 @@ bool ProjectRecovery::checkForRecovery() const noexcept { } } -bool ProjectRecovery::clearAllCheckpoints() const noexcept { - try { - deleteExistingCheckpoints(0); - return true; - } catch (...) { - g_log.warning("Project Recovery: Caught exception whilst attempting to " - "clear existing checkpoints."); - return false; - } -} - bool ProjectRecovery::clearAllCheckpoints(Poco::Path path) const noexcept { try { Poco::File(path).remove(true); @@ -354,22 +358,6 @@ std::thread ProjectRecovery::createBackgroundThread() { return std::thread([this] { projectSavingThreadWrapper(); }); } -/// Callback for POCO when a config change had fired for the enabled key -void ProjectRecovery::configKeyChanged( - Mantid::Kernel::ConfigValChangeNotification_ptr notif) { - if (notif->key() != (SAVING_ENABLED_CONFIG_KEY)) { - return; - } - - if (notif->curValue() == "True") { - SAVING_ENABLED = true; - startProjectSaving(); - } else { - SAVING_ENABLED = false; - stopProjectSaving(); - } -} - void ProjectRecovery::compileRecoveryScript(const Poco::Path &inputFolder, const Poco::Path &outputFile) { const std::string algName = "OrderWorkspaceHistory"; @@ -449,10 +437,6 @@ void ProjectRecovery::startProjectSaving() { // Close the existing thread first stopProjectSaving(); - if (!SAVING_ENABLED) { - return; - } - // Spin up a new thread { std::lock_guard<std::mutex> lock(m_notifierMutex); @@ -465,6 +449,7 @@ void ProjectRecovery::startProjectSaving() { /// Stops any existing background threads which are running void ProjectRecovery::stopProjectSaving() { { + std::lock_guard<std::mutex> lock(m_notifierMutex); m_stopBackgroundThread = true; m_threadNotifier.notify_all(); @@ -484,12 +469,14 @@ void ProjectRecovery::stopProjectSaving() { * * @param recoveryFolder : The checkpoint folder */ -void ProjectRecovery::loadRecoveryCheckpoint(const Poco::Path &recoveryFolder) { +bool ProjectRecovery::loadRecoveryCheckpoint(const Poco::Path &recoveryFolder) { ScriptingWindow *scriptWindow = m_windowPtr->getScriptWindowHandle(); if (!scriptWindow) { throw std::runtime_error("Could not get handle to scripting window"); } + m_recoveryGui->connectProgressBarToRecoveryView(); + // Ensure the window repaints so it doesn't appear frozen before exec scriptWindow->executeCurrentTab(Script::ExecutionMode::Serialised); if (scriptWindow->getSynchronousErrorFlag()) { @@ -499,34 +486,24 @@ void ProjectRecovery::loadRecoveryCheckpoint(const Poco::Path &recoveryFolder) { // exception g_log.error("Project recovery script did not finish. Your work has been " "partially recovered."); - this->startProjectSaving(); - return; + // This has failed so terminate the thread + return false; } g_log.notice("Re-opening GUIs"); auto projectFile = Poco::Path(recoveryFolder).append(OUTPUT_PROJ_NAME); - bool loadCompleted = false; if (!QMetaObject::invokeMethod( - m_windowPtr, "loadProjectRecovery", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(bool, loadCompleted), - Q_ARG(const std::string, projectFile.toString()))) { - this->startProjectSaving(); + m_windowPtr, "loadProjectRecovery", Qt::QueuedConnection, + Q_ARG(const std::string, projectFile.toString()), + Q_ARG(const std::string, recoveryFolder.toString()))) { throw std::runtime_error("Project Recovery: Failed to load project " "windows - Qt binding failed"); } + g_log.notice("Project Recovery workspace loading finished"); - if (!loadCompleted) { - g_log.warning("Loading failed to recovery everything completely"); - this->startProjectSaving(); - return; - } - g_log.notice("Project Recovery finished"); - - // Restart project recovery when the async part finishes - clearAllCheckpoints(Poco::Path(recoveryFolder).popDirectory()); - startProjectSaving(); -} // namespace MantidQt + return true; +} /** * Compiles the project recovery script from a given checkpoint @@ -540,6 +517,16 @@ void ProjectRecovery::openInEditor(const Poco::Path &inputFolder, const Poco::Path &historyDest) { compileRecoveryScript(inputFolder, historyDest); + // Get length of recovery script + std::ifstream fileCount(historyDest.toString()); + const int lineLength = + static_cast<int>(std::count(std::istreambuf_iterator<char>(fileCount), + std::istreambuf_iterator<char>(), '\n')); + fileCount.close(); + + // Update Progress bar + m_recoveryGui->setUpProgressBar(lineLength); + // Force application window to create the script window first const bool forceVisible = true; m_windowPtr->showScriptWindow(forceVisible); @@ -629,8 +616,7 @@ void ProjectRecovery::saveWsHistories(const Poco::Path &historyDestFolder) { const auto &ads = Mantid::API::AnalysisDataService::Instance(); // Hold a copy to the shared pointers so they do not get deleted under us - std::vector<boost::shared_ptr<Mantid::API::Workspace>> wsHandles = - ads.getObjects(Mantid::Kernel::DataServiceHidden::Include); + auto wsHandles = ads.getObjects(Mantid::Kernel::DataServiceHidden::Include); if (wsHandles.empty()) { return; @@ -646,6 +632,11 @@ void ProjectRecovery::saveWsHistories(const Poco::Path &historyDestFolder) { alg->setLogging(false); for (auto i = 0u; i < wsHandles.size(); ++i) { + // Check if workspace is an empty worksapce group and remove it if it is as + // well as skip + if (checkIfEmptyGroup(wsHandles[i])) + continue; + std::string filename = std::to_string(i) + ".py"; Poco::Path destFilename = historyDestFolder; @@ -690,6 +681,34 @@ void ProjectRecovery::removeOlderCheckpoints() { } } +void ProjectRecovery::removeLockedCheckpoints() { + std::string recoverFolder = getRecoveryFolderCheck(); + // Get the PIDS + std::vector<Poco::Path> possiblePidsPaths = + getListOfFoldersInDirectory(recoverFolder); + // Order pids based on date last modified descending + std::vector<int> possiblePids = orderProcessIDs(possiblePidsPaths); + // check if pid exists + std::vector<Poco::Path> files; + for (auto i = 0u; i < possiblePids.size(); ++i) { + if (!isPIDused(possiblePids[i])) { + std::string folder = recoverFolder; + folder.append(std::to_string(possiblePids[i]) + "/"); + auto checkpointsInsidePIDs = getListOfFoldersInDirectory(folder); + for (auto c : checkpointsInsidePIDs) { + if (Poco::File(c.setFileName(LOCK_FILE_NAME)).exists()) { + files.emplace_back(c.setFileName("")); + } + } + } + } + + bool recurse = true; + for (auto c : files) { + Poco::File(c).remove(recurse); + } +} + bool ProjectRecovery::olderThanAGivenTime(const Poco::Path &path, int64_t elapsedTime) { return Poco::File(path).getLastModified().isElapsed(elapsedTime); @@ -713,6 +732,8 @@ void ProjectRecovery::saveAll(bool autoSave) { const auto basePath = getOutputPath(); Poco::File(basePath).createDirectories(); + auto lockFile = addLockFile(basePath); + saveWsHistories(basePath); auto projectFile = Poco::Path(basePath).append(OUTPUT_PROJ_NAME); saveOpenWindows(projectFile.toString(), autoSave); @@ -720,9 +741,30 @@ void ProjectRecovery::saveAll(bool autoSave) { // Purge any excessive folders deleteExistingCheckpoints(NO_OF_CHECKPOINTS); g_log.debug("Project Recovery: Saving finished"); + + // Remove lock file + lockFile.remove(true); } std::string ProjectRecovery::getRecoveryFolderOutputPR() { return getRecoveryFolderOutput(); } +std::vector<Poco::Path> ProjectRecovery::getListOfFoldersInDirectoryPR( + const std::string &recoveryFolderPath) { + return getListOfFoldersInDirectory(recoveryFolderPath); +} + +std::string ProjectRecovery::getRecoveryFolderCheckPR() { + return getRecoveryFolderCheck(); +} + +std::string ProjectRecovery::getRecoveryFolderLoadPR() { + return getRecoveryFolderLoad(); +} + +std::vector<Poco::Path> ProjectRecovery::getRecoveryFolderCheckpointsPR( + const std::string &recoveryFolderPath) { + return getRecoveryFolderCheckpoints(recoveryFolderPath); +} + } // namespace MantidQt diff --git a/MantidPlot/src/ProjectRecovery.h b/MantidPlot/src/ProjectRecovery.h index 46a5e962610600b3bdd1eacb34605abdc4f4649d..f4f155a193103df7010c682133370c1fc00cc57c 100644 --- a/MantidPlot/src/ProjectRecovery.h +++ b/MantidPlot/src/ProjectRecovery.h @@ -7,7 +7,9 @@ #ifndef PROJECT_RECOVERY_H_ #define PROJECT_RECOVERY_H_ +#include "MantidAPI/Workspace.h" #include "MantidKernel/ConfigService.h" +#include "ProjectRecoveryGUIs/ProjectRecoveryPresenter.h" #include <Poco/NObserver.h> @@ -21,7 +23,6 @@ // Forward declarations class ApplicationWindow; class Folder; - namespace Poco { class Path; } @@ -37,6 +38,7 @@ class ProjectRecovery { public: /// Constructor explicit ProjectRecovery(ApplicationWindow *windowHandle); + /// Destructor the ensures background thread stops ~ProjectRecovery(); @@ -45,9 +47,6 @@ public: /// Checks if recovery is required bool checkForRecovery() const noexcept; - /// Clears all checkpoints in the existing folder - bool clearAllCheckpoints() const noexcept; - /// Clears all checkpoints in the existing folder at the given path bool clearAllCheckpoints(Poco::Path path) const noexcept; @@ -56,6 +55,7 @@ public: /// Starts the background thread void startProjectSaving(); + /// Stops the background thread void stopProjectSaving(); @@ -68,13 +68,34 @@ public: /// get Recovery Folder location std::string getRecoveryFolderOutputPR(); + /// Get a list of poco paths based on recoveryFolderPaths' directory + std::vector<Poco::Path> + getListOfFoldersInDirectoryPR(const std::string &recoveryFolderPath); + + /// get Recovery Folder to loads location + std::string getRecoveryFolderLoadPR(); + + /// Exposing the getRecoveryFolderCheckpoints function + std::vector<Poco::Path> + getRecoveryFolderCheckpointsPR(const std::string &recoveryFolderPath); + + /// Expose the getRecoveryFolderCheck function + std::string getRecoveryFolderCheckPR(); + + /// Loads a recovery checkpoint in the given folder + bool loadRecoveryCheckpoint(const Poco::Path &path); + + /// Open a recovery checkpoint in the scripting window + void openInEditor(const Poco::Path &inputFolder, + const Poco::Path &historyDest); + /// Remove checkpoints if it has lock file + void removeLockedCheckpoints(); + private: + friend class RecoveryThread; /// Captures the current object in the background thread std::thread createBackgroundThread(); - /// Triggers when the config key is updated to a new value - void configKeyChanged(Mantid::Kernel::ConfigValChangeNotification_ptr notif); - /// Creates a recovery script based on all .py scripts in a folder void compileRecoveryScript(const Poco::Path &inputFolder, const Poco::Path &outputFile); @@ -89,13 +110,6 @@ private: /// Deletes oldest "unused" checkpoints beyond the maximum number to keep void deleteExistingUnusedCheckpoints(size_t checkpointsToKeep) const; - /// Loads a recovery checkpoint in the given folder - void loadRecoveryCheckpoint(const Poco::Path &path); - - /// Open a recovery checkpoint in the scripting window - void openInEditor(const Poco::Path &inputFolder, - const Poco::Path &historyDest); - /// Wraps the thread in a try catch to log any failures void projectSavingThreadWrapper(); @@ -117,18 +131,19 @@ private: /// Mutex for conditional variable and background thread flag std::mutex m_notifierMutex; + /// Flag to indicate to the thread to exit std::atomic<bool> m_stopBackgroundThread; + /// Atomic to detect when the thread should fire or exit std::condition_variable m_threadNotifier; - /// Config observer to monitor the key - Poco::NObserver<ProjectRecovery, Mantid::Kernel::ConfigValChangeNotification> - m_configKeyObserver; - /// Pointer to main GUI window ApplicationWindow *m_windowPtr; + // The presenter of the recovery guis + ProjectRecoveryPresenter *m_recoveryGui; + std::vector<std::string> m_algsToIgnore = { "EnggSaveGSASIIFitResultsToHDF5", "EnggSaveSinglePeakFitResultsToHDF5", diff --git a/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryModel.cpp b/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryModel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8442c7853055420f192689a521716d7581033ace --- /dev/null +++ b/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryModel.cpp @@ -0,0 +1,232 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "ProjectRecoveryModel.h" +#include "MantidAPI/AlgorithmManager.h" +#include "MantidAPI/AnalysisDataService.h" +#include "MantidKernel/ConfigService.h" +#include "ProjectRecovery.h" +#include "ProjectRecoveryPresenter.h" +#include "RecoveryThread.h" + +#include <Poco/File.h> +#include <Poco/Path.h> +#include <QApplication> +#include <QThread> +#include <fstream> +#include <memory> + +namespace { +std::string findNumberOfWorkspacesInDirectory(const Poco::Path &path) { + std::vector<std::string> files; + Poco::File(path).list(files); + // Number of workspaces is equal to the number of files in the path directory + // -1 of that value. + return std::to_string(files.size() - 1); +} +void replaceSpaceWithT(std::string &string) { + const auto stringSpacePos = string.find(" "); + if (stringSpacePos != std::string::npos) + string.replace(stringSpacePos, 1, "T"); +} +void sortByLastModified(std::vector<Poco::Path> &paths) { + std::sort(paths.begin(), paths.end(), [](const auto &a, const auto &b) { + Poco::File a1(a); + Poco::File b1(b); // Last modified is first! + return a1.getLastModified() > b1.getLastModified(); + }); +} +} // namespace + +ProjectRecoveryModel::ProjectRecoveryModel( + MantidQt::ProjectRecovery *projectRecovery, + ProjectRecoveryPresenter *presenter) + : m_projRec(projectRecovery), m_presenter(presenter), m_failedRun(true), + m_recoveryRunning(false) { + fillFirstRow(); +} + +const std::vector<std::string> &ProjectRecoveryModel::getRow(const int i) { + return m_rows.at(i); +} + +std::vector<std::string> +ProjectRecoveryModel::getRow(std::string checkpointName) { + replaceSpaceWithT(checkpointName); + for (auto c : m_rows) { + if (c[0] == checkpointName) { + return c; + } + } + return std::vector<std::string>({"", "", "0"}); +} + +void ProjectRecoveryModel::startMantidNormally() { + m_projRec->clearAllUnusedCheckpoints(); + m_projRec->startProjectSaving(); + m_failedRun = false; + + // If project recovery is running on script window we need to abort + if (m_recoveryRunning) { + m_presenter->emitAbortScript(); + } + + // Close view + m_presenter->closeView(); +} +void ProjectRecoveryModel::recoverSelectedCheckpoint(std::string &selected) { + m_recoveryRunning = true; + m_presenter->changeStartMantidToCancelLabel(); + // Clear the ADS + Mantid::API::AnalysisDataService::Instance().clear(); + + // Recovery given the checkpoint selected here + replaceSpaceWithT(selected); + Poco::Path checkpoint(m_projRec->getRecoveryFolderLoadPR()); + checkpoint.append(selected); + Poco::Path output(Mantid::Kernel::ConfigService::Instance().getAppDataDir()); + output.append("ordered_recovery.py"); + + m_projRec->openInEditor(checkpoint, output); + createThreadAndManage(checkpoint); + + selected.replace(selected.find("T"), 1, " "); + if (m_failedRun) { + updateCheckpointTried(selected); + } + m_recoveryRunning = false; + // Close View + m_presenter->closeView(); +} +void ProjectRecoveryModel::openSelectedInEditor(std::string &selected) { + m_recoveryRunning = true; + // Clear the ADS + Mantid::API::AnalysisDataService::Instance().clear(); + + // Open editor for this checkpoint + replaceSpaceWithT(selected); + auto beforeCheckpoint = m_projRec->getRecoveryFolderLoadPR(); + Poco::Path checkpoint(beforeCheckpoint); + checkpoint.append(selected); + Poco::Path output(Mantid::Kernel::ConfigService::Instance().getAppDataDir()); + output.append("ordered_recovery.py"); + + m_projRec->openInEditor(checkpoint, output); + // Restart project recovery as we stay synchronous + m_projRec->clearAllCheckpoints(beforeCheckpoint); + m_projRec->startProjectSaving(); + + selected.replace(selected.find("T"), 1, " "); + if (m_failedRun) { + updateCheckpointTried(selected); + } + m_recoveryRunning = false; + m_failedRun = false; + // Close View + m_presenter->closeView(); +} + +void ProjectRecoveryModel::fillRow(const Poco::Path &path, + const std::string &checkpointName) { + std::string lengthOfFile = findNumberOfWorkspacesInDirectory(path); + std::string checked = "No"; + std::vector<std::string> nextVector = { + std::move(checkpointName), std::move(lengthOfFile), std::move(checked)}; + m_rows.emplace_back(std::move(nextVector)); +} + +void ProjectRecoveryModel::fillFirstRow() { + auto paths = m_projRec->getListOfFoldersInDirectoryPR( + m_projRec->getRecoveryFolderLoadPR()); + sortByLastModified(paths); + + // Grab the first path as that is the one that should be loaded + const auto path = paths.front(); + std::string checkpointName = path.directory(path.depth() - 1); + checkpointName.replace(checkpointName.find("T"), 1, " "); + fillRow(path, checkpointName); +} + +void ProjectRecoveryModel::fillRows() { + auto paths = m_projRec->getListOfFoldersInDirectoryPR( + m_projRec->getRecoveryFolderLoadPR()); + sortByLastModified(paths); + + // Sort the rows first string of the vector lists + for (const auto &c : paths) { + std::string checkpointName = c.directory(c.depth() - 1); + checkpointName.replace(checkpointName.find("T"), 1, " "); + // Check if there is a first row already and skip first one + if (m_rows[0][0] == checkpointName) + continue; + + fillRow(c, checkpointName); + } + + // Get the number of checkpoints from ConfigService + int numberOfCheckpoints = getNumberOfCheckpoints(); + for (auto i = paths.size(); + i < static_cast<unsigned int>(numberOfCheckpoints); ++i) { + std::vector<std::string> newVector = {"", "", ""}; + m_rows.emplace_back(std::move(newVector)); + } + + // order the vector based on save date and time Most recent first + std::sort(m_rows.begin(), m_rows.end(), + [](const auto &a, const auto &b) -> bool { + return a.front() > b.front(); + }); +} + +void ProjectRecoveryModel::updateCheckpointTried( + const std::string &checkpointName) { + for (auto &c : m_rows) { + if (c[0] == checkpointName) { + c[2] = "Yes"; + return; + } + } + throw std::runtime_error("Passed checkpoint name for update was incorrect: " + + checkpointName); +} + +bool ProjectRecoveryModel::getFailedRun() const { return m_failedRun; } + +void ProjectRecoveryModel::createThreadAndManage(const Poco::Path &checkpoint) { + RecoveryThread recoverThread; + recoverThread.setProjRecPtr(m_projRec); + recoverThread.setCheckpoint(checkpoint); + recoverThread.start(QThread::LowPriority); + + while (!recoverThread.isFinished()) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + QApplication::processEvents(); + } + + // Set failed run member to the value from the thread + m_failedRun = recoverThread.getFailedRun(); +} + +std::string ProjectRecoveryModel::decideLastCheckpoint() { + auto mostRecentCheckpoints = m_projRec->getRecoveryFolderCheckpointsPR( + m_projRec->getRecoveryFolderLoadPR()); + auto mostRecentCheckpointPath = mostRecentCheckpoints.back(); + return mostRecentCheckpointPath.directory(mostRecentCheckpointPath.depth() - + 1); +} + +int ProjectRecoveryModel::getNumberOfCheckpoints() { + int numberOfCheckpoints; + try { + numberOfCheckpoints = + std::stoi(Mantid::Kernel::ConfigService::Instance().getString( + "projectRecovery.numberOfCheckpoints")); + } catch (...) { + // Fail silently and set to 5 + numberOfCheckpoints = 5; + } + return numberOfCheckpoints; +} \ No newline at end of file diff --git a/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryModel.h b/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryModel.h new file mode 100644 index 0000000000000000000000000000000000000000..c0ad4c414f3fc42c1add79e4d4cf11ccbe162cf9 --- /dev/null +++ b/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryModel.h @@ -0,0 +1,47 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef PROJECTRECOVERYMODEL_H +#define PROJECTRECOVERYMODEL_H + +#include <Poco/Path.h> +#include <memory> +#include <string> +#include <vector> +namespace MantidQt { +class ProjectRecovery; +} +class ProjectRecoveryPresenter; +class ProjectRecoveryModel { + +public: + ProjectRecoveryModel(MantidQt::ProjectRecovery *projectRecovery, + ProjectRecoveryPresenter *presenter); + const std::vector<std::string> &getRow(const int i); + std::vector<std::string> getRow(std::string checkpointName); + void startMantidNormally(); + void recoverSelectedCheckpoint(std::string &selected); + void openSelectedInEditor(std::string &selected); + bool getFailedRun() const; + bool hasRecoveryStarted() const { return m_recoveryRunning; } + std::string decideLastCheckpoint(); + void fillRows(); + static int getNumberOfCheckpoints(); + +private: + void fillFirstRow(); + void fillRow(const Poco::Path &path, const std::string &checkpointName); + void updateCheckpointTried(const std::string &checkpointName); + bool checkRecoverWasASuccess(const std::string &projectFile); + void createThreadAndManage(const Poco::Path &checkpoint); + std::vector<std::vector<std::string>> m_rows; + MantidQt::ProjectRecovery *m_projRec; + ProjectRecoveryPresenter *m_presenter; + bool m_failedRun; + bool m_recoveryRunning; +}; + +#endif // PROJECTRECOVERYMODEL_H diff --git a/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryPresenter.cpp b/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryPresenter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..73d1b64b01217bae0b35abda2c673b7d8cc755f1 --- /dev/null +++ b/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryPresenter.cpp @@ -0,0 +1,159 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "ProjectRecoveryPresenter.h" +#include "ApplicationWindow.h" +#include "ProjectRecovery.h" +#include "ProjectRecoveryModel.h" +#include "ProjectRecoveryView.h" +#include "RecoveryFailureView.h" + +#include <QDialog> +#include <memory> + +ProjectRecoveryPresenter::ProjectRecoveryPresenter( + MantidQt::ProjectRecovery *projectRecovery, ApplicationWindow *parentWindow) + : m_mainWindow(parentWindow), m_recView(nullptr), m_failureView(nullptr), + m_model(std::make_unique<ProjectRecoveryModel>(projectRecovery, this)), + m_openView(RecoveryView), m_startMantidNormallyCalled(false) {} + +bool ProjectRecoveryPresenter::startRecoveryView() { + try { + m_recView = std::make_unique<ProjectRecoveryView>(m_mainWindow, this); + m_openView = RecoveryView; + m_recView->exec(); + } catch (...) { + return true; + } + + // If start mantid normally was called we want to cancel + if (m_startMantidNormallyCalled) { + return false; + } + + // If run has failed and recovery is not running + if (m_model->getFailedRun()) { + return true; + } + return false; +} + +bool ProjectRecoveryPresenter::startRecoveryFailure() { + try { + m_failureView = std::make_unique<RecoveryFailureView>(m_mainWindow, this); + m_openView = FailureView; + m_failureView->exec(); + } catch (...) { + return true; + } + + // If start mantid normally was called we want to cancel + if (m_startMantidNormallyCalled) { + return false; + } + + // If run has failed and recovery is not running + if (m_model->getFailedRun()) { + return true; + } + return false; +} + +QStringList ProjectRecoveryPresenter::getRow(int i) { + const auto &vec = m_model->getRow(i); + QStringList returnVal; + for (auto i = 0u; i < vec.size(); ++i) { + QString newString = QString::fromStdString(vec[i]); + returnVal << newString; + } + return returnVal; +} + +void ProjectRecoveryPresenter::recoverLast() { + if (m_model->hasRecoveryStarted()) + return; + auto checkpointToRecover = m_model->decideLastCheckpoint(); + m_model->recoverSelectedCheckpoint(checkpointToRecover); +} + +void ProjectRecoveryPresenter::openLastInEditor() { + if (m_model->hasRecoveryStarted()) + return; + auto checkpointToRecover = m_model->decideLastCheckpoint(); + m_model->openSelectedInEditor(checkpointToRecover); +} + +void ProjectRecoveryPresenter::startMantidNormally() { + m_startMantidNormallyCalled = true; + m_model->startMantidNormally(); +} + +void ProjectRecoveryPresenter::recoverSelectedCheckpoint( + const QString &selected) { + if (m_model->hasRecoveryStarted()) + return; + auto checkpointToRecover = selected.toStdString(); + m_model->recoverSelectedCheckpoint(checkpointToRecover); +} + +void ProjectRecoveryPresenter::openSelectedInEditor(const QString &selected) { + if (m_model->hasRecoveryStarted()) + return; + auto checkpointToRecover = selected.toStdString(); + m_model->openSelectedInEditor(checkpointToRecover); +} + +void ProjectRecoveryPresenter::closeView() { + if (m_recView != nullptr) { + m_recView->setVisible(false); + } + if (m_failureView != nullptr) { + m_failureView->setVisible(false); + } +} + +void ProjectRecoveryPresenter::setUpProgressBar(const int barMax) { + if (m_openView == RecoveryView && m_recView) { + m_recView->setProgressBarMaximum(barMax); + } else if (m_failureView) { + m_failureView->setProgressBarMaximum(barMax); + } +} + +void ProjectRecoveryPresenter::connectProgressBarToRecoveryView() { + if (m_openView == RecoveryView) { + m_recView->connectProgressBar(); + } else { + m_failureView->connectProgressBar(); + } +} + +void ProjectRecoveryPresenter::emitAbortScript() { + if (m_openView == RecoveryView) { + m_recView->emitAbortScript(); + } else { + m_failureView->emitAbortScript(); + } +} + +void ProjectRecoveryPresenter::changeStartMantidToCancelLabel() { + if (m_openView == RecoveryView) { + m_recView->changeStartMantidButton("Cancel Recovery"); + } else { + m_failureView->changeStartMantidButton("Cancel Recovery"); + } +} + +void ProjectRecoveryPresenter::fillAllRows() { + // Only allow this to run once, first run will have value RecoveryView + if (m_openView == RecoveryView) { + m_model->fillRows(); + } +} + +int ProjectRecoveryPresenter::getNumberOfCheckpoints() { + return ProjectRecoveryModel::getNumberOfCheckpoints(); +} \ No newline at end of file diff --git a/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryPresenter.h b/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryPresenter.h new file mode 100644 index 0000000000000000000000000000000000000000..08150456f8cb50070776c70b21cbbbe79823434f --- /dev/null +++ b/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryPresenter.h @@ -0,0 +1,57 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef PROJECTRECOVERYPRESENTER_H +#define PROJECTRECOVERYPRESENTER_H + +#include "ProjectRecoveryModel.h" +#include <QDialog> +#include <QStringList> +#include <boost/shared_ptr.hpp> +#include <memory> + +namespace MantidQt { +class ProjectRecovery; +} +class ApplicationWindow; +class ProjectRecoveryView; +class RecoveryFailureView; +class ProjectRecoveryPresenter { +public: + enum OpenView { RecoveryView, FailureView }; + ProjectRecoveryPresenter(MantidQt::ProjectRecovery *projectRecovery, + ApplicationWindow *parentWindow); + ProjectRecoveryPresenter(const ProjectRecoveryPresenter &obj); + ~ProjectRecoveryPresenter() = default; + bool startRecoveryView(); + bool startRecoveryFailure(); + QStringList getRow(int i); + void recoverLast(); + void openLastInEditor(); + void startMantidNormally(); + void recoverSelectedCheckpoint(const QString &selected); + void openSelectedInEditor(const QString &selected); + void closeView(); + void connectProgressBarToRecoveryView(); + ProjectRecoveryPresenter &operator=(const ProjectRecoveryPresenter &obj); + void emitAbortScript(); + void changeStartMantidToCancelLabel(); + void fillAllRows(); + void setUpProgressBar(const int barMax); + static int getNumberOfCheckpoints(); + +private: + friend class ProjectRecoveryView; + friend class RecoveryFailureView; + ApplicationWindow *m_mainWindow; + std::unique_ptr<ProjectRecoveryView> m_recView; + std::unique_ptr<RecoveryFailureView> m_failureView; + std::unique_ptr<ProjectRecoveryModel> m_model; + OpenView m_openView; + bool m_startMantidNormallyCalled; +}; + +#endif // PROJECTRECOVERYPRESENTER_H diff --git a/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryView.cpp b/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryView.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4e44af806e4539d7572914d56536775f56e43957 --- /dev/null +++ b/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryView.cpp @@ -0,0 +1,87 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "ProjectRecoveryView.h" +#include "ApplicationWindow.h" +#include "MantidKernel/UsageService.h" +#include "Script.h" +#include "ScriptingWindow.h" + +ProjectRecoveryView::ProjectRecoveryView(QWidget *parent, + ProjectRecoveryPresenter *presenter) + : QDialog(parent), m_ui(std::make_unique<Ui::ProjectRecoveryWidget>()), + m_presenter(presenter) { + m_ui->setupUi(this); + m_ui->tableWidget->horizontalHeader()->setResizeMode(QHeaderView::Stretch); + m_ui->tableWidget->verticalHeader()->setResizeMode(QHeaderView::Stretch); + m_ui->progressBar->setMinimum(0); + // Set the table information + addDataToTable(); + Mantid::Kernel::UsageService::Instance().registerFeatureUsage( + "Interface", "ProjectRecoveryWindow", true); +} + +void ProjectRecoveryView::addDataToTable() { + const QStringList row = m_presenter->getRow(0); + m_ui->tableWidget->setItem(0, 0, new QTableWidgetItem(row[0])); + m_ui->tableWidget->setItem(0, 1, new QTableWidgetItem(row[1])); +} + +void ProjectRecoveryView::onClickLastCheckpoint() { + // Recover last checkpoint + m_presenter->recoverLast(); + Mantid::Kernel::UsageService::Instance().registerFeatureUsage( + "Feature", "ProjectRecoveryWindow->RecoverLastCheckpoint", false); +} + +void ProjectRecoveryView::onClickOpenLastInScriptWindow() { + // Open checkpoint in script window + m_presenter->openLastInEditor(); + Mantid::Kernel::UsageService::Instance().registerFeatureUsage( + "Feature", "ProjectRecoveryWindow->OpenInScriptWindow", false); +} + +void ProjectRecoveryView::onClickStartMantidNormally() { + // Start save and close this, clear checkpoint that was offered for load + m_presenter->startMantidNormally(); + Mantid::Kernel::UsageService::Instance().registerFeatureUsage( + "Feature", "ProjectRecoveryWindow->StartMantidNormally", false); +} + +void ProjectRecoveryView::reject() { + // Do the same as startMantidNormally + m_presenter->startMantidNormally(); + Mantid::Kernel::UsageService::Instance().registerFeatureUsage( + "Feature", "ProjectRecoveryWindow->StartMantidNormally", false); +} + +void ProjectRecoveryView::updateProgressBar(int newValue, bool err) { + if (!err) { + m_ui->progressBar->setValue(newValue); + } +} + +void ProjectRecoveryView::setProgressBarMaximum(int newValue) { + m_ui->progressBar->setMaximum(newValue); +} + +void ProjectRecoveryView::connectProgressBar() { + connect(&m_presenter->m_mainWindow->getScriptWindowHandle() + ->getCurrentScriptRunner(), + SIGNAL(currentLineChanged(int, bool)), this, + SLOT(updateProgressBar(int, bool))); +} + +void ProjectRecoveryView::emitAbortScript() { + connect(this, SIGNAL(abortProjectRecoveryScript()), + m_presenter->m_mainWindow->getScriptWindowHandle(), + SLOT(abortCurrent())); + emit(abortProjectRecoveryScript()); +} + +void ProjectRecoveryView::changeStartMantidButton(const QString &string) { + m_ui->startmantidButton->setText(string); +} \ No newline at end of file diff --git a/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryView.h b/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryView.h new file mode 100644 index 0000000000000000000000000000000000000000..3bf88f28a7086713e72ec9dffb3ea858b36bf7fe --- /dev/null +++ b/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryView.h @@ -0,0 +1,46 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef PROJECTRECOVERYVIEW_H +#define PROJECTRECOVERYVIEW_H + +#include "ProjectRecoveryPresenter.h" +#include "ui_ProjectRecoveryWidget.h" +#include <QDialog> +#include <QWidget> +#include <memory> + +class ProjectRecoveryView : public QDialog { + Q_OBJECT + +public: + explicit ProjectRecoveryView(QWidget *parent = 0, + ProjectRecoveryPresenter *presenter = nullptr); + void reject() override; + void setProgressBarMaximum(int newValue); + void connectProgressBar(); + void emitAbortScript(); + void changeStartMantidButton(const QString &string); + +signals: + void abortProjectRecoveryScript(); + +public slots: + void updateProgressBar(int newValue, bool err); + +private slots: + void onClickLastCheckpoint(); + void onClickOpenLastInScriptWindow(); + void onClickStartMantidNormally(); + +private: + void addDataToTable(); + + std::unique_ptr<Ui::ProjectRecoveryWidget> m_ui; + ProjectRecoveryPresenter *m_presenter; +}; + +#endif // PROJECTRECOVERYVIEW_H diff --git a/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryWidget.ui b/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryWidget.ui new file mode 100644 index 0000000000000000000000000000000000000000..2c63f7c067055729748cd0c61c88ddff66341476 --- /dev/null +++ b/MantidPlot/src/ProjectRecoveryGUIs/ProjectRecoveryWidget.ui @@ -0,0 +1,200 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ProjectRecoveryWidget</class> + <widget class="QWidget" name="ProjectRecoveryWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>484</width> + <height>194</height> + </rect> + </property> + <property name="windowTitle"> + <string>Project Recovery</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>It looks like Mantid has crashed recently. There is a recovery checkpoint available would you like to try it?</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item alignment="Qt::AlignVCenter"> + <widget class="QTableWidget" name="tableWidget"> + <property name="selectionMode"> + <enum>QAbstractItemView::SingleSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <row> + <property name="text"> + <string/> + </property> + </row> + <column> + <property name="text"> + <string>Checkpoint Time and Date</string> + </property> + </column> + <column> + <property name="text"> + <string>Workspaces to recover</string> + </property> + </column> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="yesButton"> + <property name="text"> + <string>Yes</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">buttonGroup</string> + </attribute> + </widget> + </item> + <item> + <widget class="QPushButton" name="scriptWindowButton"> + <property name="text"> + <string>Just open in script window</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">buttonGroup</string> + </attribute> + </widget> + </item> + <item> + <widget class="QPushButton" name="startmantidButton"> + <property name="text"> + <string>Start mantid normally</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">buttonGroup</string> + </attribute> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QProgressBar" name="progressBar"> + <property name="value"> + <number>0</number> + </property> + </widget> + </item> + </layout> + </widget> + <layoutdefault spacing="6" margin="11"/> + <resources/> + <connections> + <connection> + <sender>yesButton</sender> + <signal>clicked()</signal> + <receiver>ProjectRecoveryWidget</receiver> + <slot>onClickLastCheckpoint()</slot> + <hints> + <hint type="sourcelabel"> + <x>51</x> + <y>185</y> + </hint> + <hint type="destinationlabel"> + <x>11</x> + <y>161</y> + </hint> + </hints> + </connection> + <connection> + <sender>scriptWindowButton</sender> + <signal>clicked()</signal> + <receiver>ProjectRecoveryWidget</receiver> + <slot>onClickOpenLastInScriptWindow()</slot> + <hints> + <hint type="sourcelabel"> + <x>204</x> + <y>185</y> + </hint> + <hint type="destinationlabel"> + <x>165</x> + <y>195</y> + </hint> + </hints> + </connection> + <connection> + <sender>startmantidButton</sender> + <signal>clicked()</signal> + <receiver>ProjectRecoveryWidget</receiver> + <slot>onClickStartMantidNormally()</slot> + <hints> + <hint type="sourcelabel"> + <x>335</x> + <y>185</y> + </hint> + <hint type="destinationlabel"> + <x>291</x> + <y>195</y> + </hint> + </hints> + </connection> + </connections> + <slots> + <slot>onClickLastCheckpoint()</slot> + <slot>onClickOpenLastInScriptWindow()</slot> + <slot>onClickStartMantidNormally()</slot> + </slots> + <buttongroups> + <buttongroup name="buttonGroup"/> + </buttongroups> +</ui> diff --git a/MantidPlot/src/ProjectRecoveryGUIs/RecoveryFailure.ui b/MantidPlot/src/ProjectRecoveryGUIs/RecoveryFailure.ui new file mode 100644 index 0000000000000000000000000000000000000000..c8db7cd22198ea5eac7d236cfd6695858f4570da --- /dev/null +++ b/MantidPlot/src/ProjectRecoveryGUIs/RecoveryFailure.ui @@ -0,0 +1,202 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>RecoveryFailure</class> + <widget class="QWidget" name="RecoveryFailure"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>633</width> + <height>339</height> + </rect> + </property> + <property name="windowTitle"> + <string>Recovery Failed</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Project recovery has failed unexpectedly, you can choose to recover from one of the checkpoints below, open the selected checkpoint in the script window, or you can start Mantid normally.</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QTableWidget" name="tableWidget"> + <property name="selectionMode"> + <enum>QAbstractItemView::SingleSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <row> + <property name="text"> + <string/> + </property> + </row> + <row> + <property name="text"> + <string/> + </property> + </row> + <row> + <property name="text"> + <string/> + </property> + </row> + <row> + <property name="text"> + <string/> + </property> + </row> + <row> + <property name="text"> + <string/> + </property> + </row> + <column> + <property name="text"> + <string>Checkpoint Time and Date</string> + </property> + </column> + <column> + <property name="text"> + <string>Workspaces to recover</string> + </property> + </column> + <column> + <property name="text"> + <string>Checkpoint Tried</string> + </property> + </column> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="pushButton_4"> + <property name="text"> + <string>Try last +checkpoint again</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pushButton"> + <property name="text"> + <string>Try selected +checkpoint</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pushButton_2"> + <property name="text"> + <string>Open selected +in script window</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pushButton_3"> + <property name="text"> + <string>Start Mantid + normally</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="QProgressBar" name="progressBar"> + <property name="value"> + <number>0</number> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>pushButton_4</sender> + <signal>clicked()</signal> + <receiver>RecoveryFailure</receiver> + <slot>onClickLastCheckpoint()</slot> + <hints> + <hint type="sourcelabel"> + <x>143</x> + <y>274</y> + </hint> + <hint type="destinationlabel"> + <x>160</x> + <y>299</y> + </hint> + </hints> + </connection> + <connection> + <sender>pushButton</sender> + <signal>clicked()</signal> + <receiver>RecoveryFailure</receiver> + <slot>onClickSelectedCheckpoint()</slot> + <hints> + <hint type="sourcelabel"> + <x>245</x> + <y>263</y> + </hint> + <hint type="destinationlabel"> + <x>346</x> + <y>298</y> + </hint> + </hints> + </connection> + <connection> + <sender>pushButton_2</sender> + <signal>clicked()</signal> + <receiver>RecoveryFailure</receiver> + <slot>onClickOpenSelectedInScriptWindow()</slot> + <hints> + <hint type="sourcelabel"> + <x>382</x> + <y>263</y> + </hint> + <hint type="destinationlabel"> + <x>568</x> + <y>297</y> + </hint> + </hints> + </connection> + <connection> + <sender>pushButton_3</sender> + <signal>clicked()</signal> + <receiver>RecoveryFailure</receiver> + <slot>onClickStartMantidNormally()</slot> + <hints> + <hint type="sourcelabel"> + <x>494</x> + <y>272</y> + </hint> + <hint type="destinationlabel"> + <x>631</x> + <y>260</y> + </hint> + </hints> + </connection> + </connections> + <slots> + <slot>onClickLastCheckpoint()</slot> + <slot>onClickSelectedCheckpoint()</slot> + <slot>onClickOpenSelectedInScriptWindow()</slot> + <slot>onClickStartMantidNormally()</slot> + </slots> +</ui> diff --git a/MantidPlot/src/ProjectRecoveryGUIs/RecoveryFailureView.cpp b/MantidPlot/src/ProjectRecoveryGUIs/RecoveryFailureView.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a8fd18f5995ebd5297582039c6b6be773fc1627b --- /dev/null +++ b/MantidPlot/src/ProjectRecoveryGUIs/RecoveryFailureView.cpp @@ -0,0 +1,118 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "RecoveryFailureView.h" +#include "ApplicationWindow.h" +#include "MantidKernel/UsageService.h" +#include "Script.h" +#include "ScriptingWindow.h" + +RecoveryFailureView::RecoveryFailureView(QWidget *parent, + ProjectRecoveryPresenter *presenter) + : QDialog(parent), m_ui(std::make_unique<Ui::RecoveryFailure>()), + m_presenter(presenter) { + m_ui->setupUi(this); + m_ui->tableWidget->horizontalHeader()->setResizeMode(QHeaderView::Stretch); + m_ui->tableWidget->verticalHeader()->setResizeMode(QHeaderView::Stretch); + // Make sure the ui has all the data it needs to display + m_presenter->fillAllRows(); + // Set the table information + addDataToTable(); + Mantid::Kernel::UsageService::Instance().registerFeatureUsage( + "Interface", "ProjectRecoveryFailureWindow", true); +} + +void RecoveryFailureView::addDataToTable() { + // This table's size was generated for 5 which is the default but will take + // more or less than 5, but won't look as neat + const auto numberOfRows = m_presenter->getNumberOfCheckpoints(); + for (auto i = 0; i < numberOfRows; ++i) { + const auto row = m_presenter->getRow(i); + for (auto j = 0; j < row.size(); ++j) { + m_ui->tableWidget->setItem(i, j, new QTableWidgetItem(row[j])); + } + } +} + +void RecoveryFailureView::onClickLastCheckpoint() { + // Recover last checkpoint + m_presenter->recoverLast(); + Mantid::Kernel::UsageService::Instance().registerFeatureUsage( + "Feature", "ProjectRecoveryFailureWindow->RecoverLastCheckpoint", false); +} + +void RecoveryFailureView::onClickSelectedCheckpoint() { + // Recover Selected + QList<QTableWidgetItem *> selectedRows = m_ui->tableWidget->selectedItems(); + if (selectedRows.size() > 0) { + const QString text = selectedRows[0]->text(); + if (text.toStdString().empty()) { + return; + } + m_presenter->recoverSelectedCheckpoint(text); + } + Mantid::Kernel::UsageService::Instance().registerFeatureUsage( + "Feature", "ProjectRecoveryFailureWindow->RecoverSelectedCheckpoint", + false); +} + +void RecoveryFailureView::onClickOpenSelectedInScriptWindow() { + // Open checkpoint in script window + QList<QTableWidgetItem *> selectedRows = m_ui->tableWidget->selectedItems(); + if (selectedRows.size() > 0) { + const QString text = selectedRows[0]->text(); + if (text.toStdString().empty()) { + return; + } + m_presenter->openSelectedInEditor(text); + } + Mantid::Kernel::UsageService::Instance().registerFeatureUsage( + "Feature", "ProjectRecoveryFailureWindow->OpenSelectedInScriptWindow", + false); +} + +void RecoveryFailureView::onClickStartMantidNormally() { + // Start save and close this, clear checkpoint that was offered for load + m_presenter->startMantidNormally(); + Mantid::Kernel::UsageService::Instance().registerFeatureUsage( + "Feature", "ProjectRecoveryFailureWindow->StartMantidNormally", false); +} + +void RecoveryFailureView::reject() { + // Do nothing just absorb request + m_presenter->startMantidNormally(); + Mantid::Kernel::UsageService::Instance().registerFeatureUsage( + "Feature", "ProjectRecoveryFailureWindow->StartMantidNormally", false); +} + +void RecoveryFailureView::updateProgressBar(const int newValue, + const bool err) { + if (!err) { + m_ui->progressBar->setValue(newValue); + } +} + +void RecoveryFailureView::setProgressBarMaximum(const int newValue) { + m_ui->progressBar->setMaximum(newValue); +} + +void RecoveryFailureView::connectProgressBar() { + connect(&m_presenter->m_mainWindow->getScriptWindowHandle() + ->getCurrentScriptRunner(), + SIGNAL(currentLineChanged(int, bool)), this, + SLOT(updateProgressBar(int, bool))); +} + +void RecoveryFailureView::emitAbortScript() { + connect(this, SIGNAL(abortProjectRecoveryScript()), + m_presenter->m_mainWindow->getScriptWindowHandle(), + SLOT(abortCurrent())); + emit(abortProjectRecoveryScript()); +} + +void RecoveryFailureView::changeStartMantidButton(const QString &string) { + m_ui->pushButton_3->setText(string); +} \ No newline at end of file diff --git a/MantidPlot/src/ProjectRecoveryGUIs/RecoveryFailureView.h b/MantidPlot/src/ProjectRecoveryGUIs/RecoveryFailureView.h new file mode 100644 index 0000000000000000000000000000000000000000..6a6c63a7bf6bdd9fac2266ca6e2acc10e54edbc3 --- /dev/null +++ b/MantidPlot/src/ProjectRecoveryGUIs/RecoveryFailureView.h @@ -0,0 +1,48 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef RECOVERYFAILUREVIEW_H +#define RECOVERYFAILUREVIEW_H + +#include "ProjectRecoveryPresenter.h" +#include "ui_RecoveryFailure.h" +#include <QDialog> +#include <QWidget> +#include <memory> + +class RecoveryFailureView : public QDialog { + Q_OBJECT + +public: + explicit RecoveryFailureView(QWidget *parent = 0, + ProjectRecoveryPresenter *presenter = nullptr); + void reject() override; + + void setProgressBarMaximum(const int newValue); + void connectProgressBar(); + void emitAbortScript(); + void changeStartMantidButton(const QString &string); + +signals: + void abortProjectRecoveryScript(); + +public slots: + void updateProgressBar(const int newValue, const bool err); + +private slots: + void onClickLastCheckpoint(); + void onClickSelectedCheckpoint(); + void onClickOpenSelectedInScriptWindow(); + void onClickStartMantidNormally(); + +private: + void addDataToTable(); + + std::unique_ptr<Ui::RecoveryFailure> m_ui; + ProjectRecoveryPresenter *m_presenter; +}; + +#endif // RECOVERYFAILUREVIEW_H diff --git a/MantidPlot/src/ProjectRecoveryGUIs/RecoveryThread.cpp b/MantidPlot/src/ProjectRecoveryGUIs/RecoveryThread.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a1e6ad6fb759ef4649097b88641afd7e43b5eb9e --- /dev/null +++ b/MantidPlot/src/ProjectRecoveryGUIs/RecoveryThread.cpp @@ -0,0 +1,22 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "RecoveryThread.h" +#include "ProjectRecovery.h" + +bool RecoveryThread::getFailedRun() { return m_failedRunInThread; } + +void RecoveryThread::setCheckpoint(const Poco::Path &checkpoint) { + m_checkpoint = checkpoint; +} + +void RecoveryThread::setProjRecPtr(MantidQt::ProjectRecovery *projectRec) { + m_projRec = projectRec; +} + +void RecoveryThread::run() { + m_failedRunInThread = !m_projRec->loadRecoveryCheckpoint(m_checkpoint); +} \ No newline at end of file diff --git a/MantidPlot/src/ProjectRecoveryGUIs/RecoveryThread.h b/MantidPlot/src/ProjectRecoveryGUIs/RecoveryThread.h new file mode 100644 index 0000000000000000000000000000000000000000..377f30adb79d4188c0b46703b38b701be302b91d --- /dev/null +++ b/MantidPlot/src/ProjectRecoveryGUIs/RecoveryThread.h @@ -0,0 +1,35 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef RECOVERYTHREAD_H_ +#define RECOVERYTHREAD_H_ + +#include <Poco/Path.h> +#include <QThread> + +namespace MantidQt { +class ProjectRecovery; +} + +class RecoveryThread : public QThread { + Q_OBJECT + +public: + RecoveryThread() { m_projRec = nullptr; } + bool getFailedRun(); + void setCheckpoint(const Poco::Path &checkpoint); + void setProjRecPtr(MantidQt::ProjectRecovery *projectRec); + +protected: + void run() override; + +private: + bool m_failedRunInThread = true; + Poco::Path m_checkpoint; + MantidQt::ProjectRecovery *m_projRec; +}; + +#endif /*RECOVERYTHREAD_H_*/ \ No newline at end of file diff --git a/MantidPlot/src/Script.cpp b/MantidPlot/src/Script.cpp index ea45f9c6c64d2ee8d840e0a32292437ad19cf741..1542564b7bbf631a23f5935421d71cb9fe5fabf1 100644 --- a/MantidPlot/src/Script.cpp +++ b/MantidPlot/src/Script.cpp @@ -29,7 +29,6 @@ #include "Script.h" #include "ScriptingEnv.h" -#include <QRegExp> #include <stdexcept> //-------------------------------------------------------------------------------------------------- diff --git a/MantidPlot/src/ScriptFileInterpreter.h b/MantidPlot/src/ScriptFileInterpreter.h index 8a6a5b394f9285ab1c150c51b6a2d3351d39a6fb..419c18a53d2623c4ae954adb916b205045a7199b 100644 --- a/MantidPlot/src/ScriptFileInterpreter.h +++ b/MantidPlot/src/ScriptFileInterpreter.h @@ -57,6 +57,8 @@ public: /// Is the script running virtual bool isExecuting() const; + const Script &getRunner() const { return *m_runner.data(); } + public slots: /// Save to the currently stored name virtual void saveToCurrentFile(); diff --git a/MantidPlot/src/ScriptingWindow.cpp b/MantidPlot/src/ScriptingWindow.cpp index 3c5b9c7d898686cb2b5efc547e94e6d4cf48b881..7ffc308255a08e6ec689e8870f6665f921973bab 100644 --- a/MantidPlot/src/ScriptingWindow.cpp +++ b/MantidPlot/src/ScriptingWindow.cpp @@ -905,3 +905,7 @@ Script::ExecutionMode ScriptingWindow::getExecutionMode() const { else return Script::Serialised; } + +const Script &ScriptingWindow::getCurrentScriptRunner() { + return m_manager->currentInterpreter()->getRunner(); +} \ No newline at end of file diff --git a/MantidPlot/src/ScriptingWindow.h b/MantidPlot/src/ScriptingWindow.h index 8c5b048ccbb640cf92b83011ed371a763e93ebd1..d363c04ca0834068df496a14f1cabbf7b6bfadad 100644 --- a/MantidPlot/src/ScriptingWindow.h +++ b/MantidPlot/src/ScriptingWindow.h @@ -19,6 +19,7 @@ //---------------------------------------------------------- // Forward declarations //--------------------------------------------------------- +class ScriptFileInterpreter; class MultiTabScriptInterpreter; class ScriptingEnv; class QTextEdit; @@ -76,6 +77,9 @@ public: // We set a flag on failure to avoid problems with Async not returning success bool getSynchronousErrorFlag() { return m_failureFlag; } + /// Get a reference to the runner of the current script on the current tab + const Script &getCurrentScriptRunner(); + signals: /// Show the scripting language dialog void chooseScriptingLanguage(); diff --git a/MantidPlot/src/Spectrogram.cpp b/MantidPlot/src/Spectrogram.cpp index e63b40512147256faa56d97ebfedfe9c934d5420..c97cf47309ff577ce26b7bb5799b848ca2c681ad 100644 --- a/MantidPlot/src/Spectrogram.cpp +++ b/MantidPlot/src/Spectrogram.cpp @@ -472,7 +472,8 @@ MantidColorMap Spectrogram::getDefaultColorMap() { settings.endGroup(); // if the file is not valid you will get the default - MantidColorMap retColorMap(lastColormapFile, GraphOptions::Linear); + MantidColorMap retColorMap(lastColormapFile, + MantidColorMap::ScaleType::Linear); return retColorMap; } @@ -666,7 +667,8 @@ void Spectrogram::saveSettings() { // settings.setValue("BackgroundColor", // mInstrumentDisplay->currentBackgroundColor()); settings.setValue("ColormapFile", mCurrentColorMap); - settings.setValue("ScaleType", getColorMap().getScaleType()); + settings.setValue("ScaleType", + static_cast<int>(getColorMap().getScaleType())); settings.endGroup(); } /** @@ -682,9 +684,8 @@ void Spectrogram::loadSettings() { // Set values from settings mutableColorMap().loadMap(mCurrentColorMap); - GraphOptions::ScaleType type = - (GraphOptions::ScaleType)settings.value("ScaleType", GraphOptions::Log10) - .toUInt(); + auto type = static_cast<MantidColorMap::ScaleType>( + settings.value("ScaleType", GraphOptions::Log10).toUInt()); mutableColorMap().changeScaleType(type); @@ -1077,7 +1078,7 @@ void Spectrogram::loadFromProject(const std::string &lines) { // color map will revert to the default color map if // the file path is invalid MantidColorMap colorMap(QString::fromStdString(filename), - GraphOptions::Linear); + MantidColorMap::ScaleType::Linear); mCurrentColorMap = colorMap.getFilePath(); mColorMap = colorMap; setCustomColorMap(colorMap); diff --git a/Testing/Data/DocTest/ILL/D11/010413.nxs.md5 b/Testing/Data/DocTest/ILL/D11/010413.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..ed7fc7e5e5f83e04651f1dc5cb2e6c82fee96a38 --- /dev/null +++ b/Testing/Data/DocTest/ILL/D11/010413.nxs.md5 @@ -0,0 +1 @@ +c91facb99568c19cdd4fe973f8144e80 diff --git a/Testing/Data/DocTest/ILL/D11/010414.nxs.md5 b/Testing/Data/DocTest/ILL/D11/010414.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..f1e3c010ac752453b487667d6f83d16001a066a8 --- /dev/null +++ b/Testing/Data/DocTest/ILL/D11/010414.nxs.md5 @@ -0,0 +1 @@ +854f0e59b0f1c503b74021f10ae3f916 diff --git a/Testing/Data/DocTest/ILL/D11/010444.nxs.md5 b/Testing/Data/DocTest/ILL/D11/010444.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..eeeab86ed183cd8d5fb9313fd829b78d3378282f --- /dev/null +++ b/Testing/Data/DocTest/ILL/D11/010444.nxs.md5 @@ -0,0 +1 @@ +0760c37b511cef85217aa5e4dcdf54df diff --git a/Testing/Data/DocTest/ILL/D11/010445.nxs.md5 b/Testing/Data/DocTest/ILL/D11/010445.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..1644e0dbc218abfd8cf1d41fe6b4f4d3ee989e6f --- /dev/null +++ b/Testing/Data/DocTest/ILL/D11/010445.nxs.md5 @@ -0,0 +1 @@ +295f065ac59fc25673d334b33a10c186 diff --git a/Testing/Data/DocTest/ILL/D11/010446.nxs.md5 b/Testing/Data/DocTest/ILL/D11/010446.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..267c66f070edcddce8ded657035b1153ed65a8b9 --- /dev/null +++ b/Testing/Data/DocTest/ILL/D11/010446.nxs.md5 @@ -0,0 +1 @@ +423db249bee5a4ac19f9ef20b75e30a4 diff --git a/Testing/Data/DocTest/ILL/D11/010453.nxs.md5 b/Testing/Data/DocTest/ILL/D11/010453.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..f6023f1e03476800844a18b14112a3c1f3464286 --- /dev/null +++ b/Testing/Data/DocTest/ILL/D11/010453.nxs.md5 @@ -0,0 +1 @@ +c2402b81aabcefaa11c1d9e533d29306 diff --git a/Testing/Data/DocTest/ILL/D11/010454.nxs.md5 b/Testing/Data/DocTest/ILL/D11/010454.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..940bc095a1764d2be8f478739eb10dcf3045e92f --- /dev/null +++ b/Testing/Data/DocTest/ILL/D11/010454.nxs.md5 @@ -0,0 +1 @@ +16225764939f981194a1bf2608200ac1 diff --git a/Testing/Data/DocTest/ILL/D11/010455.nxs.md5 b/Testing/Data/DocTest/ILL/D11/010455.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..fe8c148e706517ec1f8eaf791f37baffc2f9f654 --- /dev/null +++ b/Testing/Data/DocTest/ILL/D11/010455.nxs.md5 @@ -0,0 +1 @@ +77d7ab78b415664a3c81f34a706860cf diff --git a/Testing/Data/DocTest/ILL/D11/010460.nxs.md5 b/Testing/Data/DocTest/ILL/D11/010460.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..ffe7b639017c149fc519ebfa6893926709ff2c6b --- /dev/null +++ b/Testing/Data/DocTest/ILL/D11/010460.nxs.md5 @@ -0,0 +1 @@ +0c2875160206652aa2f9bcb9ec6ded56 diff --git a/Testing/Data/DocTest/ILL/D11/010462.nxs.md5 b/Testing/Data/DocTest/ILL/D11/010462.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..cfbb14aa5ad0fa8fb92e3d1eadabf8c608a63909 --- /dev/null +++ b/Testing/Data/DocTest/ILL/D11/010462.nxs.md5 @@ -0,0 +1 @@ +26001887d0a23e1aa84892c502cae964 diff --git a/Testing/Data/DocTest/ILL/D11/010560.nxs.md5 b/Testing/Data/DocTest/ILL/D11/010560.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..c8b868aca3792e887b3309e5f0de145b8b76e957 --- /dev/null +++ b/Testing/Data/DocTest/ILL/D11/010560.nxs.md5 @@ -0,0 +1 @@ +f141378c5178c8ad272f8b0c95041d2d diff --git a/Testing/Data/DocTest/ILL/D11/010569.nxs.md5 b/Testing/Data/DocTest/ILL/D11/010569.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..2dc3a73e7eed0c45adf265475daae8fd877d3da9 --- /dev/null +++ b/Testing/Data/DocTest/ILL/D11/010569.nxs.md5 @@ -0,0 +1 @@ +e548e1a00dd240110efa826d449100ba diff --git a/Testing/Data/DocTest/ILL/D11/010585.nxs.md5 b/Testing/Data/DocTest/ILL/D11/010585.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..3af83667b762c2fc5c535f2ca48b4ac78bf85fbd --- /dev/null +++ b/Testing/Data/DocTest/ILL/D11/010585.nxs.md5 @@ -0,0 +1 @@ +5fb62f31f4445e26faedf031b487e7ff diff --git a/Testing/Data/UnitTest/EMU0006330.tar.md5 b/Testing/Data/UnitTest/EMU0006330.tar.md5 new file mode 100644 index 0000000000000000000000000000000000000000..cc108f4750cb7c56a8d825082869e139aa6f2f6c --- /dev/null +++ b/Testing/Data/UnitTest/EMU0006330.tar.md5 @@ -0,0 +1 @@ +7811054fb83c39a772a430079e4811eb diff --git a/Testing/Data/UnitTest/ILL/D11/010413.nxs.md5 b/Testing/Data/UnitTest/ILL/D11/010413.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..ed7fc7e5e5f83e04651f1dc5cb2e6c82fee96a38 --- /dev/null +++ b/Testing/Data/UnitTest/ILL/D11/010413.nxs.md5 @@ -0,0 +1 @@ +c91facb99568c19cdd4fe973f8144e80 diff --git a/Testing/Data/UnitTest/ILL/D11/010414.nxs.md5 b/Testing/Data/UnitTest/ILL/D11/010414.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..f1e3c010ac752453b487667d6f83d16001a066a8 --- /dev/null +++ b/Testing/Data/UnitTest/ILL/D11/010414.nxs.md5 @@ -0,0 +1 @@ +854f0e59b0f1c503b74021f10ae3f916 diff --git a/Testing/Data/UnitTest/ILL/D11/010444.nxs.md5 b/Testing/Data/UnitTest/ILL/D11/010444.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..eeeab86ed183cd8d5fb9313fd829b78d3378282f --- /dev/null +++ b/Testing/Data/UnitTest/ILL/D11/010444.nxs.md5 @@ -0,0 +1 @@ +0760c37b511cef85217aa5e4dcdf54df diff --git a/Testing/Data/UnitTest/ILL/D11/010445.nxs.md5 b/Testing/Data/UnitTest/ILL/D11/010445.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..1644e0dbc218abfd8cf1d41fe6b4f4d3ee989e6f --- /dev/null +++ b/Testing/Data/UnitTest/ILL/D11/010445.nxs.md5 @@ -0,0 +1 @@ +295f065ac59fc25673d334b33a10c186 diff --git a/Testing/Data/UnitTest/ILL/D11/010446.nxs.md5 b/Testing/Data/UnitTest/ILL/D11/010446.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..267c66f070edcddce8ded657035b1153ed65a8b9 --- /dev/null +++ b/Testing/Data/UnitTest/ILL/D11/010446.nxs.md5 @@ -0,0 +1 @@ +423db249bee5a4ac19f9ef20b75e30a4 diff --git a/Testing/Data/UnitTest/ILL/D11/010453.nxs.md5 b/Testing/Data/UnitTest/ILL/D11/010453.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..f6023f1e03476800844a18b14112a3c1f3464286 --- /dev/null +++ b/Testing/Data/UnitTest/ILL/D11/010453.nxs.md5 @@ -0,0 +1 @@ +c2402b81aabcefaa11c1d9e533d29306 diff --git a/Testing/Data/UnitTest/ILL/D11/010454.nxs.md5 b/Testing/Data/UnitTest/ILL/D11/010454.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..940bc095a1764d2be8f478739eb10dcf3045e92f --- /dev/null +++ b/Testing/Data/UnitTest/ILL/D11/010454.nxs.md5 @@ -0,0 +1 @@ +16225764939f981194a1bf2608200ac1 diff --git a/Testing/Data/UnitTest/ILL/D11/010455.nxs.md5 b/Testing/Data/UnitTest/ILL/D11/010455.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..fe8c148e706517ec1f8eaf791f37baffc2f9f654 --- /dev/null +++ b/Testing/Data/UnitTest/ILL/D11/010455.nxs.md5 @@ -0,0 +1 @@ +77d7ab78b415664a3c81f34a706860cf diff --git a/Testing/Data/UnitTest/ILL/D11/010460.nxs.md5 b/Testing/Data/UnitTest/ILL/D11/010460.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..ffe7b639017c149fc519ebfa6893926709ff2c6b --- /dev/null +++ b/Testing/Data/UnitTest/ILL/D11/010460.nxs.md5 @@ -0,0 +1 @@ +0c2875160206652aa2f9bcb9ec6ded56 diff --git a/Testing/Data/UnitTest/ILL/D11/010462.nxs.md5 b/Testing/Data/UnitTest/ILL/D11/010462.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..cfbb14aa5ad0fa8fb92e3d1eadabf8c608a63909 --- /dev/null +++ b/Testing/Data/UnitTest/ILL/D11/010462.nxs.md5 @@ -0,0 +1 @@ +26001887d0a23e1aa84892c502cae964 diff --git a/Testing/Data/UnitTest/ILL/D11/010569.nxs.md5 b/Testing/Data/UnitTest/ILL/D11/010569.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..2dc3a73e7eed0c45adf265475daae8fd877d3da9 --- /dev/null +++ b/Testing/Data/UnitTest/ILL/D11/010569.nxs.md5 @@ -0,0 +1 @@ +e548e1a00dd240110efa826d449100ba diff --git a/Testing/Data/UnitTest/ILL/D11/010585.nxs.md5 b/Testing/Data/UnitTest/ILL/D11/010585.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..3af83667b762c2fc5c535f2ca48b4ac78bf85fbd --- /dev/null +++ b/Testing/Data/UnitTest/ILL/D11/010585.nxs.md5 @@ -0,0 +1 @@ +5fb62f31f4445e26faedf031b487e7ff diff --git a/Testing/SystemTests/tests/analysis/BASISTest.py b/Testing/SystemTests/tests/analysis/BASISTest.py index 064f65d64874423b64819417aea6a52d391bcfaf..afa665f4f04994a5db7adc3f3ea4b09c0b4ae39d 100644 --- a/Testing/SystemTests/tests/analysis/BASISTest.py +++ b/Testing/SystemTests/tests/analysis/BASISTest.py @@ -174,8 +174,8 @@ class PowderSampleTest(stresstesting.MantidStressTest, PreppingMixin): def requiredFiles(self): return ['BASIS_Mask_default_diff.xml', - 'BSS_74799_event.nxs', - 'BASISPowderSample.nxs'] + 'BSS_74799_event.nxs', 'BSS_75527_event.nxs', + 'BSS_64642_event.nxs', 'BASISPowderSample.nxs'] def runTest(self): r""" diff --git a/Testing/SystemTests/tests/analysis/SANSILLReductionTest.py b/Testing/SystemTests/tests/analysis/SANSILLReductionTest.py new file mode 100644 index 0000000000000000000000000000000000000000..f62a2f9f6fb7ebe9cfcbd78f2cdb6e9a4306c2b7 --- /dev/null +++ b/Testing/SystemTests/tests/analysis/SANSILLReductionTest.py @@ -0,0 +1,85 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +from __future__ import (absolute_import, division, print_function) + +import stresstesting +from mantid.simpleapi import SANSILLReduction, Q1DWeighted, config, mtd + + +class ILL_D11_Test(stresstesting.MantidStressTest): + + def __init__(self): + super(ILL_D11_Test, self).__init__() + self.setUp() + + def setUp(self): + config['default.facility'] = 'ILL' + config['default.instrument'] = 'D11' + config.appendDataSearchSubDir('ILL/D11/') + + def requiredFiles(self): + return ['010455.nxs', '010414.nxs', '010446.nxs', '010454.nxs', '010445.nxs', '010453.nxs', + '010462.nxs', '010413.nxs', '010444.nxs', '010460.nxs', '010585.nxs', '010569.nxs', + 'ILL_SANS_D11_IQ.nxs'] + + def tearDown(self): + mtd.clear() + + def runTest(self): + + # Process the dark current Cd/B4C for water + SANSILLReduction(Run='010455.nxs', ProcessAs='Absorber', OutputWorkspace='Cdw') + + # Process the empty beam for water + SANSILLReduction(Run='010414.nxs', ProcessAs='Beam', AbsorberInputWorkspace='Cdw', OutputWorkspace='Dbw') + + # Water container transmission + SANSILLReduction(Run='010446.nxs', ProcessAs='Transmission', AbsorberInputWorkspace='Cdw', + BeamInputWorkspace='Dbw', OutputWorkspace='wc_tr') + + # Water container + SANSILLReduction(Run='010454.nxs', ProcessAs='Container', AbsorberInputWorkspace='Cdw', + BeamInputWorkspace='Dbw', TransmissionInputWorkspace='wc_tr', OutputWorkspace='wc') + + # Water transmission + SANSILLReduction(Run='010445.nxs', ProcessAs='Transmission', AbsorberInputWorkspace='Cdw', + BeamInputWorkspace='Dbw', OutputWorkspace='w_tr') + + # Water + SANSILLReduction(Run='010453.nxs', ProcessAs='Reference', AbsorberInputWorkspace='Cdw', + ContainerInputWorkspace='wc', BeamInputWorkspace='Dbw', TransmissionInputWorkspace='wc_tr', + SensitivityOutputWorkspace='sens', OutputWorkspace='water') + + # Process the dark current Cd/B4C for sample + SANSILLReduction(Run='010462.nxs', ProcessAs='Absorber', OutputWorkspace='Cd') + + # Process the empty beam for sample + SANSILLReduction(Run='010413.nxs', ProcessAs='Beam', AbsorberInputWorkspace='Cd', OutputWorkspace='Db') + + # Sample container transmission + SANSILLReduction(Run='010444.nxs', ProcessAs='Transmission', AbsorberInputWorkspace='Cd', + BeamInputWorkspace='Dbw', OutputWorkspace='sc_tr') + + # Sample container + SANSILLReduction(Run='010460.nxs', ProcessAs='Container', AbsorberInputWorkspace='Cd', BeamInputWorkspace='Db', + TransmissionInputWorkspace='sc_tr', OutputWorkspace='sc') + + # Sample transmission + SANSILLReduction(Run='010585.nxs', ProcessAs='Transmission', AbsorberInputWorkspace='Cd', BeamInputWorkspace='Dbw', + OutputWorkspace='s_tr') + + # Sample + SANSILLReduction(Run='010569.nxs', ProcessAs='Sample', AbsorberInputWorkspace='Cd', ContainerInputWorkspace='sc', + BeamInputWorkspace='Db', SensitivityInputWorkspace='sens', + TransmissionInputWorkspace='s_tr', OutputWorkspace='sample_flux') + + # Convert to I(Q) + Q1DWeighted(InputWorkspace='sample_flux', NumberOfWedges=0, OutputBinning='0.0027,0.0004,0.033', OutputWorkspace='iq') + + def validate(self): + self.tolerance = 1e-5 + return ['iq', 'ILL_SANS_D11_IQ.nxs'] diff --git a/Testing/SystemTests/tests/analysis/reference/BASISPowderSample.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/BASISPowderSample.nxs.md5 index 7ae881e8af20857d3cf58be27c0dc914b7248a07..a96db1bdc34ecd092158c0dc1e252914c4deec45 100644 --- a/Testing/SystemTests/tests/analysis/reference/BASISPowderSample.nxs.md5 +++ b/Testing/SystemTests/tests/analysis/reference/BASISPowderSample.nxs.md5 @@ -1 +1 @@ -3e00c1815556f60d6369bfff50dc51b7 +3ebb51ba24c074fb3cdc5fff38eba7a9 diff --git a/Testing/SystemTests/tests/analysis/reference/ILL_SANS_D11_IQ.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/ILL_SANS_D11_IQ.nxs.md5 new file mode 100644 index 0000000000000000000000000000000000000000..adb2237eb5616c4c3380081f5d5ca07c2f6e3a27 --- /dev/null +++ b/Testing/SystemTests/tests/analysis/reference/ILL_SANS_D11_IQ.nxs.md5 @@ -0,0 +1 @@ +6b29448d26ab8cd75d051609e94c40bb diff --git a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-GEM83605_FocusSempty.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-GEM83605_FocusSempty.nxs.md5 index e1dee0343f1ec3b8b492ce2f7b73106db8606678..57c32f59bdb04dc9d68578edd6247a6383542a91 100644 --- a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-GEM83605_FocusSempty.nxs.md5 +++ b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-GEM83605_FocusSempty.nxs.md5 @@ -1 +1 @@ -1feccda871823814107a21de6eb9a260 +2e0a73c2c476682db7d404efe18a324a diff --git a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5 index ad05a563ed3258d0724c3bb7b0d35e4ceebdeb95..9292572473d5aa5d342669eb2dca5181d0fdcd94 100644 --- a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5 +++ b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5 @@ -1 +1 @@ -3a040549431730d27b7e4cc255b8e8f8 \ No newline at end of file +6242205ecf13a64067c83a490f01b9c0 diff --git a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5 index f7c5f189c329c82461e6ef7505072a022daf3c06..a2a6b3bed79ed4ae0258dff85b2cd7fa82464cca 100644 --- a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5 +++ b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5 @@ -1 +1 @@ -fa6c586545f682d0661d2c9b65281bf7 +d06b61ec5bdca9b9043142cf14d57390 diff --git a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder_PRL98472_tt70_all.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder_PRL98472_tt70_all.nxs.md5 index 2a55e6cbd8dfad9e8fab0c2654102331ed44dd5a..d7a28072dbed523bc7cb9dfa2a28118465557501 100644 --- a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder_PRL98472_tt70_all.nxs.md5 +++ b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder_PRL98472_tt70_all.nxs.md5 @@ -1 +1 @@ -e2d4ccd733bcb6248f6f09afc178b117 +0344a747f9eabd506c2222af3efe96f6 diff --git a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder_PRL98472_tt70_groups.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder_PRL98472_tt70_groups.nxs.md5 index 4f7781fad43773fa973bd1a9a0abdb25658ba31e..c2bb7194f50a9722d4c6bf19a0aa3998deed6eb7 100644 --- a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder_PRL98472_tt70_groups.nxs.md5 +++ b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder_PRL98472_tt70_groups.nxs.md5 @@ -1 +1 @@ -a8160d86df1324a3e753f27c91eb2f92 +5c2522a082080fb67ef2a03ee3cecf0e diff --git a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder_PRL98472_tt70_trans.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder_PRL98472_tt70_trans.nxs.md5 index 1929007b78590f63462e284b18022c20e99e6acf..9e808f9a6c2a8b58bbbf4d720ea32e931f235c85 100644 --- a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder_PRL98472_tt70_trans.nxs.md5 +++ b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder_PRL98472_tt70_trans.nxs.md5 @@ -1 +1 @@ -0d05ff5759b7a15a8b3623c7479399b5 +912260aa96f33e3a5a117724b3a890e2 diff --git a/buildconfig/CMake/Bootstrap.cmake b/buildconfig/CMake/Bootstrap.cmake index 0dcbf196e1238a5694bbdbded005bfb6e314e164..ffe7fc454f75e90ed741f49471bd4467da322036 100644 --- a/buildconfig/CMake/Bootstrap.cmake +++ b/buildconfig/CMake/Bootstrap.cmake @@ -10,7 +10,7 @@ if( MSVC ) include ( ExternalProject ) set( EXTERNAL_ROOT ${PROJECT_SOURCE_DIR}/external CACHE PATH "Location to clone third party dependencies to" ) set( THIRD_PARTY_GIT_URL "https://github.com/mantidproject/thirdparty-msvc2015.git" ) - set ( THIRD_PARTY_GIT_SHA1 47bb41d8e67f929b513f6f67eae34fdb4c15dfb8 ) + set ( THIRD_PARTY_GIT_SHA1 14040647202c199d059199550f82013855879ece ) set ( THIRD_PARTY_DIR ${EXTERNAL_ROOT}/src/ThirdParty ) # Generates a script to do the clone/update in tmp set ( _project_name ThirdParty ) diff --git a/buildconfig/CMake/CPackCommon.cmake b/buildconfig/CMake/CPackCommon.cmake index ecb8ee635b5d73f9d3cb6c853b8a8e7eae369b1b..9059f0c3b537aebf76354007e338fa890532a1ec 100644 --- a/buildconfig/CMake/CPackCommon.cmake +++ b/buildconfig/CMake/CPackCommon.cmake @@ -3,8 +3,8 @@ ############################################################################### # Common description stuff -set ( CPACK_PACKAGE_DESCRIPTION_SUMMARY "Neutron Scattering Data Analysis" ) -set ( CPACK_PACKAGE_VENDOR "ISIS Rutherford Appleton Laboratory and NScD Oak Ridge National Laboratory" ) +set ( CPACK_PACKAGE_DESCRIPTION_SUMMARY "Neutron Scattering Data Reduction and Analysis" ) +set ( CPACK_PACKAGE_VENDOR "ISIS Rutherford Appleton Laboratory UKRI, NScD Oak Ridge National Laboratory, European Spallation Source and Institut Laue - Langevin" ) set ( CPACK_PACKAGE_URL http://www.mantidproject.org/ ) set ( CPACK_PACKAGE_CONTACT mantid-help@mantidproject.org ) set ( CPACK_PACKAGE_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH} ) @@ -21,6 +21,8 @@ set ( CPACK_RPM_PACKAGE_LICENSE GPLv3+ ) set ( CPACK_RPM_PACKAGE_RELEASE 1 ) set ( CPACK_RPM_PACKAGE_GROUP Applications/Engineering ) -# DEB informatin - the package does not have an original +# DEB information - the package does not have an original # in debian archives so the debian release is 0 set ( CPACK_DEBIAN_PACKAGE_RELEASE 0 ) +set ( CPACK_DEBIAN_PACKAGE_MAINTAINER "Mantid Project <${CPACK_PACKAGE_CONTACT}>") +set ( CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE ) diff --git a/buildconfig/CMake/Packaging/launch_mantidplot.sh.in b/buildconfig/CMake/Packaging/launch_mantidplot.sh.in index dc4c9d534d9ecfa1500ea81080660b4553a1328a..1bdfb72223611c1da548a923113a495a5a213985 100644 --- a/buildconfig/CMake/Packaging/launch_mantidplot.sh.in +++ b/buildconfig/CMake/Packaging/launch_mantidplot.sh.in @@ -19,5 +19,5 @@ INSTALLDIR=$(dirname $INSTALLDIR) # root install directory # Launch LD_PRELOAD=${LOCAL_PRELOAD} TCMALLOC_RELEASE_RATE=${TCM_RELEASE} \ - TCMALLOC_LARGE_ALLOC_REPORT_THRESHOLD=${TCM_REPORT} \ + TCMALLOC_LARGE_ALLOC_REPORT_THRESHOLD=${TCM_REPORT} QT_API=pyqt \ @WRAPPER_PREFIX@$VGLRUN $GDB $INSTALLDIR/bin/@MANTIDPLOT_EXEC@ $*@WRAPPER_POSTFIX@ || @PYTHON_EXECUTABLE@ @SCRIPTSDIR@/@ERROR_CMD@ diff --git a/buildconfig/dev-packages/deb/mantid-developer/ns-control b/buildconfig/dev-packages/deb/mantid-developer/ns-control index fd4d31ba9d8f3bf406113e78732d84ad36b6edd8..49c6158b953df62774003397a1913a7e10ebe900 100644 --- a/buildconfig/dev-packages/deb/mantid-developer/ns-control +++ b/buildconfig/dev-packages/deb/mantid-developer/ns-control @@ -55,6 +55,7 @@ Depends: git, python-yaml, python-mock, python-psutil, + python-requests, ipython-qtconsole (>=1.2.0), texlive, texlive-latex-extra, @@ -77,7 +78,8 @@ Depends: git, python3-h5py, python3-yaml, python3-mock, - python3-psutil + python3-psutil, + python3-requests Description: Installs all packages required for a Mantid developer A metapackage which requires all the dependencies and tools that are required for Mantid development. It works for Ubuntu 16.04. diff --git a/buildconfig/dev-packages/rpm/mantid-developer/mantid-developer.spec b/buildconfig/dev-packages/rpm/mantid-developer/mantid-developer.spec index 64908d8e50390c36d9d96cfd3d7221837a43bb1c..ec575dd63a5b4ff9763aefa5a9dc72d054848300 100644 --- a/buildconfig/dev-packages/rpm/mantid-developer/mantid-developer.spec +++ b/buildconfig/dev-packages/rpm/mantid-developer/mantid-developer.spec @@ -43,6 +43,7 @@ Requires: PyQt4-devel Requires: python-qt5-devel Requires: python-QtPy Requires: python2-QtAwesome +Requires: python-requests Requires: python-devel Requires: python-setuptools Requires: python-ipython >= 1.1 @@ -105,6 +106,7 @@ Requires: python3-matplotlib Requires: python3-PyYAML Requires: python3-mock %{?fedora:Requires: python3-psutil} +%{?fedora:Requires: python3-requests} Requires: boost-python3-devel %endif diff --git a/dev-docs/source/BuildingOnOSX.rst b/dev-docs/source/BuildingOnOSX.rst index 092a7724e68576c7bc582729d0ab75b948f64ca7..703998d23b484a31a4506ca9b4a16b917ce83e3c 100644 --- a/dev-docs/source/BuildingOnOSX.rst +++ b/dev-docs/source/BuildingOnOSX.rst @@ -205,6 +205,7 @@ In order to be able to 'tap' the ``mantidproject/mantid`` 'tap' we need to have sudo -H pip install PyYAML==3.10 # Version matches Windows/RHEL/Ubuntu (trusty) sudo -H pip install mock==1.0.1 + sudo -H pip install requests==2.9.1 8. Install the theme for sphinx @@ -377,6 +378,7 @@ If, while configuring Mantid, cmake complains that it cannot find sip, uninstall sudo pip install psutil sudo pip install qtawesome sudo pip install "matplotlib>=2.1.2" + sudo pip install requests==2.9.1 9. Install h5py diff --git a/dev-docs/source/Testing/ErrorReporter-ProjectRecovery/ProjectRecoveryTesting.rst b/dev-docs/source/Testing/ErrorReporter-ProjectRecovery/ProjectRecoveryTesting.rst index 162fa22bb54ee9554525d9df5ec0bb2ea19b29e1..d1a09d7c6ffcd84035710a6206dea6209d386f1a 100644 --- a/dev-docs/source/Testing/ErrorReporter-ProjectRecovery/ProjectRecoveryTesting.rst +++ b/dev-docs/source/Testing/ErrorReporter-ProjectRecovery/ProjectRecoveryTesting.rst @@ -24,9 +24,9 @@ Project Recovery test -------------- -1. Simple tests and Muon-esque workflow +1. Simple tests -- Open MantidPlot - make sure no other instances of MantidPlot are running +- Open MantidPlot - Right-click in the Results Log and set `Log level` to `Debug` - The Results Log should be printing `Nothing to save` - Run the following command to create a simple workspace: @@ -75,7 +75,7 @@ Project Recovery test 2. Testing many workspaces -- Open up MantidPlot, ensure that it is the only instance running +- Open up MantidPlot - Run the following script: .. code-block:: python @@ -106,7 +106,7 @@ Project Recovery test 3. Testing workspaces of different types -- Open up MantidPlot, ensure that only one instance is running +- Open up MantidPlot - Run the following script: .. code-block:: python @@ -207,7 +207,7 @@ Project Recovery test 6. Opening script only -- Open MantidPlot - make sure no other instances of MantidPlot are running +- Open MantidPlot - Run the second script from test 1 - In the workspace window right-click the ``Sequential3`` workspace and choose `Plot spectrum` - Choose `Plot All` @@ -222,14 +222,14 @@ Project Recovery test 7. Not attempting recovery -- Open MantidPlot - make sure no other instances of MantidPlot are running +- Open MantidPlot - Run the second script from test 1 - In the workspace window right-click the ``Sequential3`` workspace and choose `Plot spectrum` - Choose `Plot All` - Crash Mantid with `Segfault` from the algorithm window - Reopen Mantid - You should be presented with the Project Recovery dialog -- Choose `No` +- Choose `Start mantid normally` - Mantid should open as normal - With the Results Log in debug level you should see the project saver starting up again @@ -237,7 +237,7 @@ Project Recovery test 8. Check old history is purged -- Open MantidPlot - make sure no other instances of MantidPlot are running +- Open MantidPlot .. code-block:: python diff --git a/dev-docs/source/Widgets/Plotting.rst b/dev-docs/source/Widgets/Plotting.rst index 524f5e06973f0b27f973351fa2e39a98f085db6b..68ffb0606a35879536c4d8f9c9bdd0d5388cc210 100644 --- a/dev-docs/source/Widgets/Plotting.rst +++ b/dev-docs/source/Widgets/Plotting.rst @@ -30,6 +30,8 @@ The four subplot arrangements are: *This layout can be changed by modifying ``plotting_utils.py``.* +The python toolbar is available for use. Two custom buttons have been added for adding and removing lines from a subplot. + Usage ^^^^^ :: @@ -62,9 +64,14 @@ The Plotting_ files are arranged in the following format: AxisChanger/ axis_changer_presenter.py axis_changer_view.py + edit_widows/ + remove_plot_window.py + select_subplot.py plotting_presenter.py plotting_view.py plotting_utils.py + subPlot_object.py + navigation_toolbar.py Both the Plotting and AxisChanger modules are in MVP (Model, View, Presenter) format, as this benefits maintainability. @@ -99,15 +106,19 @@ Handles the gridspec layouts: further gridspecs can be defined in ``plotting_uti TODO ^^^^ - Move plotting to a more central, accessible location in the mantid codebase -- Options for changing line colours, fonts etc. - Moveable lines with the related events (i.e. can check whether a line is moved within tolerance of a given x value) - Addition of more subplots -- Exporting subplots as PNGs etc. -- Removal of individual plots from a subplot (the way plotted lines are stored would have to be changed) +- Allow any spectrum number to be plotted + +Done +^^^^ +- Options for changing line colours, fonts etc. This is done by python toolbar. +- Exporting subplots as PNGs etc. This is done by python toolbar. +- Removal of individual plots from a subplot (the way plotted lines are stored would have to be changed). Needed some rewritting but done now. Known Issues ^^^^^^^^^^^^ -- TBC after testing +- Axis change goes blank after removing subplot. Related Bugs/Notes ^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/algorithms/AlignDetectors-v1.rst b/docs/source/algorithms/AlignDetectors-v1.rst index ab934df6daa1bc4d28e08337eb471b944d99af8d..c62f2287ebec304d14fa72b9739c9c6befefeaee 100644 --- a/docs/source/algorithms/AlignDetectors-v1.rst +++ b/docs/source/algorithms/AlignDetectors-v1.rst @@ -42,11 +42,10 @@ when :math:`DIFA < 0`. This algorithm always uses a :ref:`calibration table <DiffractionCalibrationWorkspace>` which it either reads from the `CalibrationWorkspace` property, or uses :ref:`ConvertDiffCal -<algm-ConvertDiffCal>` or :ref:`LoadCalFile <algm-LoadCalFile>` to -produce. +<algm-ConvertDiffCal>` to produce from the ``OffsetsWorkspace``. -**Note:** the workspace that this algorithms outputs is a -:ref:`ragged workspace <Ragged_Workspace>`. +.. note:: The workspace that this algorithms outputs is a + :ref:`ragged workspace <Ragged_Workspace>`. Restrictions on the input workspace ################################### diff --git a/docs/source/algorithms/BASISPowderDiffraction-v1.rst b/docs/source/algorithms/BASISPowderDiffraction-v1.rst index 7d1712edf88bb1eef875277acc13c94197ec8978..845bc36e36c94bcc76058258d9aea29822976ed5 100644 --- a/docs/source/algorithms/BASISPowderDiffraction-v1.rst +++ b/docs/source/algorithms/BASISPowderDiffraction-v1.rst @@ -68,6 +68,35 @@ Usage The color image shows the nine diffraction detectors with a Bragg peak spilling intensity on the edges of two tubes at a scattering angle of 60 degrees. +Developer's Corner +------------------ + +Adding the Previous Pulse +######################### + +because of the legacy hardware used at BASIS (ROC2 instead of ROC5), +the diffraction detectors frame is forced to coincide with the +inelastic detectors frame, introducing a shift in the minimal TOF. + +In the figures below, we have represented the TOF and wavelength dependence +of the intensities for the monitors (black), sample from the current +pulse (red), and sample from the previous pulse (green) + +**111 Reflection:** + +.. figure:: /images/BASISPowderDiffraction_2.png + +**311 Reflection:** + +.. figure:: /images/BASISPowderDiffraction_2.png + +The figures show that the "slow" neutrons from the previous pulse should be +accounted as the fast neutrons for the current pulse. + +The solution is to: (1) pile together events from the previous and current +pulses; (2) discard events with under-represented wavelegths. We use the +monitor counts for the last step. + .. categories:: .. sourcelink:: diff --git a/docs/source/algorithms/CalculateDynamicRange-v1.rst b/docs/source/algorithms/CalculateDynamicRange-v1.rst new file mode 100644 index 0000000000000000000000000000000000000000..8f1884b4539d507f8a504d9608e952a643fd1719 --- /dev/null +++ b/docs/source/algorithms/CalculateDynamicRange-v1.rst @@ -0,0 +1,50 @@ + +.. algorithm:: + +.. summary:: + +.. relatedalgorithms:: + +.. properties:: + +Description +----------- + +Calculates the minimum and maximum momentum transfer (Q) for a SANS workspace. +The input workspace must have instrument defined and data in units of wavelength [Angstroms]. +Elastic scattering is assumed. +Masked detectors and monitors do not enter the calculation. +The calculated values (in inverse Angstroms) will be set in sample logs as **qmin** and **qmax** respectively. + +Usage +----- + +**Example - CalculateDynamicRange** + +.. testcode:: CalculateDynamicRangeExample + + ws = CreateSampleWorkspace(XUnit='Wavelength', NumBanks=1, PixelSpacing=0.1, XMin=1, XMax=5, BinWidth=0.4) + MoveInstrumentComponent(Workspace=ws, RelativePosition=True, ComponentName="bank1", Y=-0.5, X=-0.5) + shapeXML = \ + """ + <infinite-cylinder id="A" > + <centre x="0" y="0" z="0" /> + <axis x="0" y="0" z="1" /> + <radius val="0.1" /> + </infinite-cylinder> + """ + MaskDetectorsInShape(ws, ShapeXML=shapeXML) + CalculateDynamicRange(Workspace=ws) + print("QMin = %.5f" % ws.getRun().getLogData("qmin").value) + print("QMax = %.5f" % ws.getRun().getLogData("qmax").value) + +Output: + +.. testoutput:: CalculateDynamicRangeExample + + QMin = 0.03553 + QMax = 0.88200 + +.. categories:: + +.. sourcelink:: diff --git a/docs/source/algorithms/ConvertDiffCal-v1.rst b/docs/source/algorithms/ConvertDiffCal-v1.rst index 77f2f08a7b6de18083f895388a7cff98dc388b9a..ca25a65831751af95976a05bcb310bdf07ac84dd 100644 --- a/docs/source/algorithms/ConvertDiffCal-v1.rst +++ b/docs/source/algorithms/ConvertDiffCal-v1.rst @@ -10,8 +10,12 @@ Description ----------- -This algorithm converts old diffraction OffsetsWorkspaces to -:ref:`calibration table <DiffractionCalibrationWorkspace>`. It uses the geometry of the OffsetsWorkspace to based the values of ``DIFC`` on. +This algorithm converts diffraction OffsetsWorkspaces to +:ref:`calibration table <DiffractionCalibrationWorkspace>`. It uses +the geometry of the OffsetsWorkspace to based the values of +:math:`DIFC` on. The effective :math:`DIFC` is calculated as + +.. math:: DIFC = \frac{1}{1+offset}\frac{2m_N}{h} L_{tot} sin \theta Usage ----- diff --git a/docs/source/algorithms/CreateTransmissionWorkspace-v2.rst b/docs/source/algorithms/CreateTransmissionWorkspace-v2.rst index b4bc5f1ffcda580efbf0f0026afade20586d0c86..8e8731024dafd2583f4ca83903fe77a1288038e8 100644 --- a/docs/source/algorithms/CreateTransmissionWorkspace-v2.rst +++ b/docs/source/algorithms/CreateTransmissionWorkspace-v2.rst @@ -63,7 +63,7 @@ Usage trans = Load(Filename='INTER00013463.nxs') transWS = CreateTransmissionWorkspace(FirstTransmissionRun = trans, I0MonitorIndex = 2, - ProcessingInstructions = '3,4', + ProcessingInstructions = '4,5', WavelengthMin = 1, WavelengthMax = 17, MonitorBackgroundWavelengthMin = 15, @@ -97,7 +97,7 @@ Output: StartOverlap = 10.0, EndOverlap = 12.0, I0MonitorIndex = 2, - ProcessingInstructions = '3,4', + ProcessingInstructions = '4,5', WavelengthMin = 1, WavelengthMax = 17, MonitorBackgroundWavelengthMin = 15, diff --git a/docs/source/algorithms/EditInstrumentGeometry-v1.rst b/docs/source/algorithms/EditInstrumentGeometry-v1.rst index 2ef6b3fb137bb7532b4afc1fedaf76ddcdb0c95a..2d0b960124184a408528d57687bcbfbdc6aa91ad 100644 --- a/docs/source/algorithms/EditInstrumentGeometry-v1.rst +++ b/docs/source/algorithms/EditInstrumentGeometry-v1.rst @@ -9,53 +9,67 @@ Description ----------- -This algorithm can +This algorithm can: -| ``1. add an Instrument to a Workspace without any real instrument associated with, or`` -| ``2. replace a Workspace's Instrument with a new Instrument, or`` -| ``3. edit all detectors' parameters of the instrument associated with a Workspace (partial instrument editing is not supported).`` +#. Add an Instrument to a Workspace without any real instrument associated with, or +#. Replace a Workspace's Instrument with a new Instrument, or +#. Edit all detectors' parameters of the instrument associated with a Workspace (partial instrument editing is not supported). Requirements on input properties -------------------------------- -1. PrimaryFightPath (L1): If it is not given, L1 will be the distance -between source and sample in the original instrument. Otherwise, L1 is -read from input. The source position of the modified instrument is (0, -0, -L1); - -2. SpectrumIDs: If not specified (empty list), then Spectrum Numbers will be -set up to any array such that SpectrumNos[wsindex] is the spectrum Number of -workspace index 'wsindex'; - -3. L2 and Polar cannot be empty list; - -4. SpectrumIDs[i], L2[i], Polar[i], Azimuthal[i] and optional -DetectorIDs[i] correspond to the detector of a same spectrum. - -5. Angles are specified in degrees. +#. PrimaryFightPath (L1): If it is not given, L1 will be the distance + between source and sample in the original instrument. Otherwise, L1 is + read from input. The source position of the modified instrument is (0, + 0, -L1); +#. SpectrumIDs: If not specified (empty list), then Spectrum Numbers will be + set up to any array such that SpectrumNos[wsindex] is the spectrum Number of + workspace index 'wsindex'; +#. L2 and Polar cannot be empty list; +#. SpectrumIDs[i], L2[i], Polar[i], Azimuthal[i] and optional + DetectorIDs[i] correspond to the detector of a same spectrum. +#. Angles are specified in degrees. Limitations ----------- There are some limitations of this algorithm. -1. The key to locate the detector is via spectrum Number; - -2. For each spectrum, there is only one and only one new detector. Thus, -if one spectrum is associated with a group of detectors previously, the -replacement (new) detector is the one which is (diffraction) focused on -after this algorithm is called. - -Instruction ------------ - -1. For powder diffractomer with 3 spectra, user can input - -| ``  SpectrumIDs = "1, 3, 2"`` -| ``  L2 = "3.1, 3.2, 3.3"`` -| ``  Polar = "90.01, 90.02, 90.03"`` -| ``  Azimuthal = "0.1,0.2,0.3"`` -| ``  to set up the focused detectors' parameters for spectrum 1, 3 and 2.`` +#. The key to locate the detector is via spectrum Number; +#. For each spectrum, there is only one and only one new detector. Thus, if one spectrum is associated with a group of detectors previously, the replacement (new) detector is the one which is (diffraction) focused on after this algorithm is called. + +Usage +----- + +**Example - Adding a new instrument to a workspace** + +.. testcode:: AddExample + + import numpy + ws = CreateWorkspace( + DataX=[0., 1.], + DataY=[1., 2., 3.], + NSpec=3) + EditInstrumentGeometry( + ws, + PrimaryFlightPath=5., + SpectrumIDs=[1, 2, 3], + L2=[2.0, 2.3, 2.6], + Polar=[10.0, 15.0, 23.0], + Azimuthal=[0.0, 0.0, 0.0], + DetectorIDs=[100, 101, 102], + InstrumentName='Bizarrio') + spectrumInfo = ws.spectrumInfo() + for i in range(ws.getNumberHistograms()): + print('Histogram {} scattering angle: {:.3} degrees'.format(i + 1, numpy.rad2deg(spectrumInfo.twoTheta(i)))) + +Output: + +.. testoutput:: AddExample + + Histogram 1 scattering angle: 10.0 degrees + Histogram 2 scattering angle: 15.0 degrees + Histogram 3 scattering angle: 23.0 degrees .. categories:: diff --git a/docs/source/algorithms/IntegratePeaksProfileFitting-v1.rst b/docs/source/algorithms/IntegratePeaksProfileFitting-v1.rst index e5c715ed8c79b2683b23636335616f99163b1679..de0e287509b74387497ca19ff4a9a88b86e2ca6f 100644 --- a/docs/source/algorithms/IntegratePeaksProfileFitting-v1.rst +++ b/docs/source/algorithms/IntegratePeaksProfileFitting-v1.rst @@ -28,7 +28,8 @@ The algorithms takes two input workspaces: This would be the output of :ref:`algm-ConvertToMD`. - As well as a PeaksWorkspace containing single-crystal peak locations. - This could be the output of :ref:`algm-FindPeaksMD` or :ref:`algm-PredictPeaks` + This could be the output of :ref:`algm-FindPeaksMD` or :ref:`algm-PredictPeaks`. All peaks should + be from the same run. - The OutputPeaksWorkspace will contain a copy of the input PeaksWorkspace, with the integrated intensities and errors changed. - The OutputParamsWorkspace is a TableWorkspace containing the fit parameters. @@ -46,11 +47,11 @@ values are below: | Parameter | Description | MaNDi | TOPAZ | CORELLI | +==============+============================+==========+==========+=========+ | DQPixel | The side length for each | | | | -| | voxel used for fitting. | 0.003 | 0.01 | 0.007 | +| | voxel used for fitting. | 0.003 | 0.006 | 0.007 | | | Units: 1/Angstrom | | | | +--------------+----------------------------+----------+----------+---------+ | FracHKL | The distance between peaks | | | | -| | (in fraction of hkl) that | 0.4 | 0.4 | 0.4 | +| | (in fraction of hkl) that | 0.25 | 0.25 | 0.25 | | | is used for fitting. | | | | +--------------+----------------------------+----------+----------+---------+ | MinDtBinWidth| The smallest time bin used | | | | @@ -115,7 +116,8 @@ The strong peaks library can be generated in two ways. First, it can be provide The **StrongPeakParamsFile** should be a .pkl file which contains a Numpy array containing the parameters used for strong peaks. Alternatively, if no file is provided, the algorithm will go through and fit strong peaks first, building the strong peaks library as it goes. After fitting all of the strong peaks, defined as peaks with spherical intensities above **IntensityCutoff** and further -than **EdgeCutoff** pixels from the edge, it will fit weak peaks using those profiles. +than **EdgeCutoff** pixels from the edge, it will fit weak peaks using those profiles. For initial guesses, the algorithm will fit +the first 30 peaks using the instrument default parameters. After that, it will use already fit peaks to determine initial guesses. Integrating the Model ##################### @@ -156,7 +158,7 @@ Usage LoadIsawPeaks(Filename='/SNS/MANDI/shared/ProfileFitting/demo_5921.integrate', OutputWorkspace='peaks_ws') IntegratePeaksProfileFitting(OutputPeaksWorkspace='peaks_ws_out', OutputParamsWorkspace='params_ws', - InputWorkspace='MANDI_5921_md', PeaksWorkspace='peaks_ws', RunNumber=5921, + InputWorkspace='MANDI_5921_md', PeaksWorkspace='peaks_ws', UBFile='/SNS/MANDI/shared/ProfileFitting/demo_5921.mat', MinpplFrac=0.9, MaxpplFrac=1.1, ModeratorCoefficientsFile='/SNS/MANDI/shared/ProfileFitting/franz_coefficients_2017.dat', StrongPeakParamsFile='/SNS/MANDI/shared/ProfileFitting/strongPeakParams_beta_lac_mut_mbvg.pkl', diff --git a/docs/source/algorithms/LoadCalFile-v1.rst b/docs/source/algorithms/LoadCalFile-v1.rst index 42d8a8673cdc6a138c4a2c23c0276c872721513b..8be0fa847021bbafbdc800727cdac9969ce04e38 100644 --- a/docs/source/algorithms/LoadCalFile-v1.rst +++ b/docs/source/algorithms/LoadCalFile-v1.rst @@ -9,15 +9,10 @@ Description ----------- -This algorithm loads an ARIEL-style 5-column ASCII .cal file into up to +This algorithm loads an ARIEL-style 5-column ASCII ``.cal`` file into up to 3 workspaces: a GroupingWorkspace, OffsetsWorkspace and/or MaskWorkspace. -The format is - -- Number: ignored.\* UDET: detector ID.\* Offset: calibration offset. - Goes to the OffsetsWorkspace. -- Select: 1 if selected (not masked out). Goes to the MaskWorkspace. -- Group: group number. Goes to the GroupingWorkspace. +The format is described :ref:`here <CalFile>`. Usage ----- diff --git a/docs/source/algorithms/LoadEMU-v1.rst b/docs/source/algorithms/LoadEMU-v1.rst new file mode 100644 index 0000000000000000000000000000000000000000..c3d7a711b723cb2be981455e48e38c2a8a1c7b66 --- /dev/null +++ b/docs/source/algorithms/LoadEMU-v1.rst @@ -0,0 +1,36 @@ +.. algorithm:: + +.. summary:: + +.. relatedalgorithms:: + +.. properties:: + +Description +----------- + +Load data from the EMU beamline at ANSTO. The workspace generated is a TOF EventWorkspace. + +Usage +----- + +.. include:: ../usagedata-note.txt + +**Example - Load an EMU dataset:** + +.. testcode:: ExSimple + + ws = LoadEMU('EMU0006330.tar'); + + print("Number of spectra: {}".format(ws.getNumberHistograms())) + +Output: + +.. testoutput:: ExSimple + + Number of spectra: 6528 + + +.. categories:: + +.. sourcelink:: \ No newline at end of file diff --git a/docs/source/algorithms/MSDFit-v1.rst b/docs/source/algorithms/MSDFit-v1.rst index f7b51e2cda468e30da3ebfe74171c9a69af73817..de2210a2ba884dbc990d24c7f8d39f6a4ce23adc 100644 --- a/docs/source/algorithms/MSDFit-v1.rst +++ b/docs/source/algorithms/MSDFit-v1.rst @@ -53,16 +53,17 @@ Usage print('A0: ' + str(y_msd.readY(0))) print('A1: ' + str(y_msd.readY(1))) -Output: +Output (the numbers on your machine my not match exactly): .. testoutput:: ExGeneratedDataFit - + :options: +ELLIPSIS, +NORMALIZE_WHITESPACE + Using Gauss Model - A0: [ 0.87079958] - A1: [ 0.03278263] + A0: [ 0.87...] + A1: [ 0.03...] Using Yi Model - A0: [ 0.75677983] - A1: [ 1.76943372] + A0: [ 0.95...] + A1: [ 0.58...] .. categories:: diff --git a/docs/source/algorithms/MaskBinsIf-v1.rst b/docs/source/algorithms/MaskBinsIf-v1.rst new file mode 100644 index 0000000000000000000000000000000000000000..7157b5a3a676bdfcccb8684450f49cdbd75dd219 --- /dev/null +++ b/docs/source/algorithms/MaskBinsIf-v1.rst @@ -0,0 +1,36 @@ + +.. algorithm:: + +.. summary:: + +.. relatedalgorithms:: + +.. properties:: + +Description +----------- + +This algorithm masks bins according to the criteria specified as a `muparser <http://beltoforion.de/article.php?a=muparser>`_ expression. +The variables entering the criteria are reserved as follows: + +- y : count in a bin (arbitrary units) +- x : the bin center (arbitrary units) +- e : the standard deviation on the count +- dx : the error on the bin center +- s : the value of spectrum axis, which has to be SpectraAxis or NumericAxis. + +Note that it preserves the data in the masked bins, just flags them as masked. + +Usage +----- + +**Example - MaskBinsIf** + +.. code-block:: python + + CreateSampleWorkspace(BankPixelWidth=100, NumBanks=1, OutputWorkspace='out') + MaskBinsIf(InputWorkspace='out', Criterion='s>10 && s<20 && x>1000 && x<2000', OutputWorkspace='out') + +.. categories:: + +.. sourcelink:: diff --git a/docs/source/algorithms/RecalculateTrajectoriesExtents-v1.rst b/docs/source/algorithms/RecalculateTrajectoriesExtents-v1.rst new file mode 100644 index 0000000000000000000000000000000000000000..883120d08ce1606bdd3be8dac157d3d1df3f9d30 --- /dev/null +++ b/docs/source/algorithms/RecalculateTrajectoriesExtents-v1.rst @@ -0,0 +1,95 @@ + +.. algorithm:: + +.. summary:: + +.. relatedalgorithms:: + +.. properties:: + +Description +----------- + +This algorithm is part of the new workflow for :ref:`normalizing <MDNorm>` multi-dimensional event workspaces. + +Once the ends of detector trajectories are stored in the original :ref:`EventWorkspace <EventWorkspace>` using +the :ref:`CropWorkspaceForMDNorm <algm-CropWorkspaceForMDNorm>` algorithm, one has to run the +:ref:`ConvertToMD <algm-ConvertToMD>` algorithm and convert it to **Q_sample**. During this conversion +some of the trajectories might be truncated. This recalculates the ends such as all the trajectory is +completely contained within the outside box of the :ref:`MDEventWorkspace <MDWorkspace>` + +The master equation for transforming from lab coordinate system to HKL units is given by + +.. math:: + + Q_l = 2 \pi R \cdot U \cdot B \left(\begin{array}{c} + h \\ + k \\ + l + \end{array}\right) + +We define the sample frame as + +.. math:: + + Q_s=R^{-1}Q_l=R^{-1} \left(\begin{array}{c} + -k_f\sin(\theta)\cos(\phi) \\ + -k_f\sin(\theta)\sin(\phi) \\ + k_i-k_f\cos(\theta) + \end{array}\right) + +For elasic scattering :math:`k_i=k_f`. +For given extents of the input workspace, one can now recalculate the minimum and maximum :math:`k_f` +such as the trajectory is completely contained inside the box. + +Usage +----- + +.. include:: ../usagedata-note.txt + +**Example - RecalculateTrajectoriesExtents** + +.. testcode:: RecalculateTrajectoriesExtentsExample + + # Create a host workspace + event = Load(Filename='CNCS_7860_event.nxs') + event = ConvertUnits(InputWorkspace=event, Target='DeltaE', EMode='Direct', EFixed=3) + event = CropWorkspaceForMDNorm(InputWorkspace=event, XMin=-2, XMax=3) + SetGoniometer(Workspace=event, Axis0='0,0,1,0,1') + md = ConvertToMD(InputWorkspace=event, + QDimensions='Q3D', + Q3DFrames='Q_sample', + OtherDimensions='SampleTemp', + MinValues='0.8,-2,-2,-2,-2', + MaxValues='1.,0,2,2,500') + recalculated = RecalculateTrajectoriesExtents(InputWorkspace=md) + + import numpy as np + #Original workspace + high=np.array(md.getExperimentInfo(0).run()['MDNorm_high'].value) + low=np.array(md.getExperimentInfo(0).run()['MDNorm_low'].value) + n=len(high[high-low>1]) + print("Number of trajectories in original workspace with length of more than 1meV: {}".format(n)) + #Recalculated workspace + high=np.array(recalculated.getExperimentInfo(0).run()['MDNorm_high'].value) + low=np.array(recalculated.getExperimentInfo(0).run()['MDNorm_low'].value) + n=len(high[high-low>1]) + print("Number of trajectories in recalculated workspace with length of more than 1meV: {}".format(n)) + +.. testcleanup:: RecalculateTrajectoriesExtentsExample + + DeleteWorkspace('event') + DeleteWorkspace('md') + DeleteWorkspace('recalculated') + +Output: + +.. testoutput:: RecalculateTrajectoriesExtentsExample + + Number of trajectories in original workspace with length of more than 1meV: 51200 + Number of trajectories in recalculated workspace with length of more than 1meV: 2590 + +.. categories:: + +.. sourcelink:: + diff --git a/docs/source/algorithms/ReflectometryBeamStatistics-v1.rst b/docs/source/algorithms/ReflectometryBeamStatistics-v1.rst new file mode 100644 index 0000000000000000000000000000000000000000..a117ae2684bc1e47d767d1fddd8e5229c0cc2fdc --- /dev/null +++ b/docs/source/algorithms/ReflectometryBeamStatistics-v1.rst @@ -0,0 +1,80 @@ + +.. algorithm:: + +.. summary:: + +.. relatedalgorithms:: + +.. properties:: + +Description +----------- + +This algorithm computes quantities needed by :ref:`ReflectometryMomentumTransfer <algm-ReflectometryMomentumTransfer>` and :ref:`ReflectometrySumInQ <algm-ReflectometrySumInQ>`, and adds the results to the sample logs of *ReflectedBeamWorkspace*. The following sample logs get added: + +``beam_stats.beam_rms_variation`` + :math:`=2 \sqrt{2 \ln 2} s \sqrt{\sigma}`, where :math:`s` is *PixelSize* and :math:`\sigma` is the variance of the intensity (integrated over all wavelengths) distribution of the detectors in the foreground region. + +``beam_stats.bent_sample`` + 1 if the sample can be regarded as non-flat and the beam is collimated, 0 in the case of divergent beam. + +``bean_stats.first_slit_angular_spread`` + :math:`=0.68 x_{slit1} / d_{slits}`, where :math:`x_{slit1}` is the size of the first slit and :math:`d_{slits}` is the distance between the first and second slit. + +``beam_stats.incident_angular_spread`` + :math:`=0.68 \sqrt{x_{slit1}^2 + x_{slit2}^2} / d_{slits}`, where :math:`x_{slit1}` is the size of the first and :math:`x_{slit2}` the size of the second slit while :math:`d_{slits}` is the distance between the slits. + +``beam_stats.sample_waviness`` + The heuristically calculated root mean squared sample waviness. + +``beam_stats.second_slit_angular_spread`` + :math:`=0.68 x_{slit2} / (d_{slit2} + l_2)`, where :math:`x_{slit2}` is the size of the second slit, :math:`d_{slit2}` is the second slit-to-sample distance and :math:`l_2` is the sample-to-reflected foreground centre distance. + +Additionally, ``beam_stats.beam_rms_variation`` is cached to the sample logs of *DirectBeamWorkspace* removing the need to recalculate the quantity every time the same direct beam passed to this algorithm. + +Usage +----- +.. Try not to use files in your examples, + but if you cannot avoid it then the (small) files must be added to + autotestdata\UsageData and the following tag unindented + .. include:: ../usagedata-note.txt + +**Example - ReflectometryBeamStatistics** + +.. testcode:: ReflectometryBeamStatisticsExample + + dir = Load('ILL/D17/317369.nxs') + ref = Load('ILL/D17/317370.nxs') + + ReflectometryBeamStatistics( + ReflectedBeamWorkspace=ref, + ReflectedForeground=[199, 202, 205], + DirectBeamWorkspace=dir, + DirectForeground=[200, 202, 205], + PixelSize=0.001195, + DetectorResolution=0.00022, + FirstSlitName='slit2', + FirstSlitSizeSampleLog='VirtualSlitAxis.s2w_actual_width', + SecondSlitName='slit3', + SecondSlitSizeSampleLog='VirtualSlitAxis.s3w_actual_width') + run = ref.run() + bent = run.getProperty('beam_stats.bent_sample').value + print('Bent sample? {}'.format('yes' if bent == 1 else 'no')) + rms = run.getProperty('beam_stats.beam_rms_variation').value + print('Beam RMS variation: {:.3}'.format(rms)) + run = dir.run() + rms = run.getProperty('beam_stats.beam_rms_variation').value + print('RMS variation cached in dir: {:.3}'.format(rms)) + +Output: + +.. testoutput:: ReflectometryBeamStatisticsExample + + Bent sample? no + Beam RMS variation: 0.00236 + RMS variation cached in dir: 0.00208 + +.. categories:: + +.. sourcelink:: + diff --git a/docs/source/algorithms/ReflectometryILLConvertToQ-v1.rst b/docs/source/algorithms/ReflectometryILLConvertToQ-v1.rst index 06ea891049883d948760cd5cb59c49ec1325420a..8d58b55894a4b85ad8a893ddb2f7e2d59b2d3780 100644 --- a/docs/source/algorithms/ReflectometryILLConvertToQ-v1.rst +++ b/docs/source/algorithms/ReflectometryILLConvertToQ-v1.rst @@ -15,9 +15,13 @@ The diagram below shows the workflow of this algorithm: .. diagram:: ReflectometryILLConvertToQ-v1_wkflw.dot +The algorithm expects to find a ``foreground.summation_type`` entry in the *InputWorkspace*'s sample logs containing either ``SumInLambda`` or ``SumInQ``. This entry is automatically added to the workspace by :ref:`ReflectometryILLSumForeground <algm-ReflectometryILLSumForeground>`. + Usage ----- +.. include:: ../usagedata-note.txt + **Example - nonpolarized reduction** .. testcode:: NonpolarizedEx @@ -26,18 +30,18 @@ Usage # beams. # Python dictionaries can be passed to algorithms as 'keyword arguments'. settings = { - 'ForegroundHalfWidth':[5], - 'LowAngleBkgOffset': 10, - 'LowAngleBkgWidth': 20, - 'HighAngleBkgOffset': 10, - 'HighAngleBkgWidth': 50, + 'ForegroundHalfWidth':[5], + 'LowAngleBkgOffset': 10, + 'LowAngleBkgWidth': 20, + 'HighAngleBkgOffset': 10, + 'HighAngleBkgWidth': 50, } # Direct beam direct = ReflectometryILLPreprocess( - Run='ILL/D17/317369.nxs', - OutputBeamPositionWorkspace='direct_beam_pos', # For reflected angle calibration. - **settings + Run='ILL/D17/317369.nxs', + OutputBeamPositionWorkspace='direct_beam_pos', # For reflected angle calibration. + **settings ) directFgd = ReflectometryILLSumForeground( Inputworkspace=direct.OutputWorkspace, @@ -45,23 +49,22 @@ Usage # Reflected beam reflected = ReflectometryILLPreprocess( - Run='ILL/D17/317370.nxs', - DirectBeamPositionWorkspace='direct_beam_pos', - **settings + Run='ILL/D17/317370.nxs', + DirectBeamPositionWorkspace='direct_beam_pos', + **settings ) reflectivityLambda = ReflectometryILLSumForeground( - InputWorkspace=reflected, - DirectForegroundWorkspace=directFgd, - WavelengthRange=[2, 15], + InputWorkspace=reflected, + DirectForegroundWorkspace=directFgd, + DirectBeamWorkspace=direct.OutputWorkspace, + WavelengthRange=[2, 15], ) reflectivityQ = ReflectometryILLConvertToQ( - InputWorkspace=reflectivityLambda, - ReflectedBeamWorkspace=reflected, # Needed for Q resolution - DirectBeamWorkspace=direct.OutputWorkspace, # Needed for Q resolution - # The next line is not needed if SumInQ was used in foreground summation - DirectForegroundWorkspace=directFgd, - GroupingQFraction=0.4 + InputWorkspace=reflectivityLambda, + # The next line is not needed if SumInQ was used in foreground summation + DirectForegroundWorkspace=directFgd, + GroupingQFraction=0.4 ) # The data is now in Q @@ -93,52 +96,54 @@ Output: # beams. # Python dictionaries can be passed to algorithms as 'keyword arguments'. settings = { - 'ForegroundHalfWidth':[5], - 'LowAngleBkgOffset': 10, - 'LowAngleBkgWidth': 20, - 'HighAngleBkgOffset': 10, - 'HighAngleBkgWidth': 50, + 'ForegroundHalfWidth':[5], + 'LowAngleBkgOffset': 10, + 'LowAngleBkgWidth': 20, + 'HighAngleBkgOffset': 10, + 'HighAngleBkgWidth': 50, } # Direct beam direct = ReflectometryILLPreprocess( - Run='ILL/D17/317369.nxs', - OutputBeamPositionWorkspace='direct_beam_pos', # For reflected angle calibration. - **settings + Run='ILL/D17/317369.nxs', + OutputBeamPositionWorkspace='direct_beam_pos', # For reflected angle calibration. + **settings ) directFgd = ReflectometryILLSumForeground( InputWorkspace=direct.OutputWorkspace, WavelengthRange=[2, 15] ) ReflectometryILLPolarizationCor( - InputWorkspaces='directFgd', - OutputWorkspace='pol_corrected_direct', # Name of the group workspace - EfficiencyFile='ILL/D17/PolarizationFactors.txt' + InputWorkspaces='directFgd', + OutputWorkspace='pol_corrected_direct', # Name of the group workspace + EfficiencyFile='ILL/D17/PolarizationFactors.txt' ) # Reflected beam. Flippers set to '++' reflected11 = ReflectometryILLPreprocess( - Run='ILL/D17/317370.nxs', - DirectBeamPositionWorkspace='direct_beam_pos', - **settings + Run='ILL/D17/317370.nxs', + DirectBeamPositionWorkspace='direct_beam_pos', + **settings ) reflectivity11 = ReflectometryILLSumForeground( - InputWorkspace=reflected11, - DirectForegroundWorkspace='pol_corrected_direct_++', - WavelengthRange=[2, 15] + InputWorkspace=reflected11, + DirectForegroundWorkspace='pol_corrected_direct_++', + DirectBeamWorkspace=direct.OutputWorkspace, + WavelengthRange=[2, 15] ) # Reload the reflected be. We will fake the '--' flipper settings reflected00 = ReflectometryILLPreprocess( - Run='ILL/D17/317370.nxs', - DirectBeamPositionWorkspace='direct_beam_pos', - **settings + Run='ILL/D17/317370.nxs', + DirectBeamPositionWorkspace='direct_beam_pos', + **settings ) reflectivity00 = ReflectometryILLSumForeground( - InputWorkspace=reflected00, - DirectForegroundWorkspace='pol_corrected_direct_++', - WavelengthRange=[2, 15] + InputWorkspace=reflected00, + DirectForegroundWorkspace='pol_corrected_direct_++', + DirectBeamWorkspace=direct.OutputWorkspace, + WavelengthRange=[2, 15] ) # Overwrite sample logs replace = True @@ -151,31 +156,25 @@ Output: # Polarization efficiency correction # The algorithm will think that the analyzer was off. ReflectometryILLPolarizationCor( - InputWorkspaces='reflectivity00, reflectivity11', - OutputWorkspace='pol_corrected', # Name of the group workspace - EfficiencyFile='ILL/D17/PolarizationFactors.txt' + InputWorkspaces='reflectivity00, reflectivity11', + OutputWorkspace='pol_corrected', # Name of the group workspace + EfficiencyFile='ILL/D17/PolarizationFactors.txt' ) # The polarization corrected workspaces get automatically generated names polcorr00 = mtd['pol_corrected_--'] polcorr11 = mtd['pol_corrected_++'] R00 = ReflectometryILLConvertToQ( - InputWorkspace=polcorr00, - ReflectedBeamWorkspace=reflected00, # Needed for Q resolution - DirectBeamWorkspace=direct.OutputWorkspace, # Needed for Q resolution - # The next line is not needed if SumInQ was used in foreground summation - DirectForegroundWorkspace='pol_corrected_direct_++', - Polarized=True, # Explicitly state it's polarized - GroupingQFraction=0.4 + InputWorkspace=polcorr00, + # The next line is not needed if SumInQ was used in foreground summation + DirectForegroundWorkspace='pol_corrected_direct_++', + GroupingQFraction=0.4 ) R11 = ReflectometryILLConvertToQ( - InputWorkspace=polcorr11, - ReflectedBeamWorkspace=reflected11, # Needed for Q resolution - DirectBeamWorkspace=direct.OutputWorkspace, # Needed for Q resolution - # The next line is not needed if SumInQ was used in foreground summation - DirectForegroundWorkspace='pol_corrected_direct_++', - Polarized=True, # Explicitly state it's polarized - GroupingQFraction=0.4 + InputWorkspace=polcorr11, + # The next line is not needed if SumInQ was used in foreground summation + DirectForegroundWorkspace='pol_corrected_direct_++', + GroupingQFraction=0.4 ) print('X unit in R00: ' + R00.getAxis(0).getUnit().unitID()) @@ -189,10 +188,10 @@ Output: .. testoutput:: PolarizedEx X unit in R00: MomentumTransfer - Number of points in R00: 259 + Number of points in R00: 189 X unit in R11: MomentumTransfer - Number of points in R11: 259 - Size of Q resolution data: 259 + Number of points in R11: 189 + Size of Q resolution data: 189 .. categories:: diff --git a/docs/source/algorithms/ReflectometryILLPolarizationCor-v1.rst b/docs/source/algorithms/ReflectometryILLPolarizationCor-v1.rst index b2bff0463287a4c424946d5779119f01617fe20a..b7956644cbb683adbf0e111ce67bdf117833c05b 100644 --- a/docs/source/algorithms/ReflectometryILLPolarizationCor-v1.rst +++ b/docs/source/algorithms/ReflectometryILLPolarizationCor-v1.rst @@ -59,6 +59,7 @@ Usage reflectivity11 = ReflectometryILLSumForeground( InputWorkspace=reflected11, DirectForegroundWorkspace=directFgd, + DirectBeamWorkspace=direct.OutputWorkspace, WavelengthRange=[2, 15], ) # Reload the reflected be. We will fake the '--' flipper settings @@ -71,6 +72,7 @@ Usage reflectivity00 = ReflectometryILLSumForeground( InputWorkspace=reflected00, DirectForegroundWorkspace=directFgd, + DirectBeamWorkspace=direct.OutputWorkspace, WavelengthRange=[2, 15], ) # Overwrite sample logs diff --git a/docs/source/algorithms/ReflectometryILLSumForeground-v1.rst b/docs/source/algorithms/ReflectometryILLSumForeground-v1.rst index c14d5cab93df239a8cfc18c171f45558e89f259f..67be404516f76c39c1b749410bf64fe25bf78b4b 100644 --- a/docs/source/algorithms/ReflectometryILLSumForeground-v1.rst +++ b/docs/source/algorithms/ReflectometryILLSumForeground-v1.rst @@ -9,10 +9,7 @@ Description ----------- -This algorithm is typically the second step in the reflectometry reduction workflow. It consumes the output of :ref:`ReflectometryILLPreprocess <algm-ReflectometryILLPreprocess>`, producing - -* the summed foreground if only *InputWorkspace* is given. This should be used for the direct beam. -* the reflectivity if both *InputWorkspace* and *DirectForegroundWorkspace* are given. *DirectForegroundWorkspace* should be the output of the above case, i.e. the summed direct foreground. +This algorithm is typically the second step in the reflectometry reduction workflow. It consumes the output of :ref:`ReflectometryILLPreprocess <algm-ReflectometryILLPreprocess>`, producing a workspace with a single single spectrum. The reflectivity output of this algorithm can be forwarded to :ref:`ReflectometryILLConvertToQ <algm-ReflectometryILLConvertToQ>` or, in case of polarization analysis, :ref:`ReflectometryILLPolarizationCor <algm-ReflectometryILLPolarizationCor>`. @@ -20,15 +17,19 @@ The following diagram gives an overview of the algorithm: .. diagram:: ReflectometryILLSumForeground-v1_wkflw.dot +The algorihtm runs :ref:`ReflectometryBeamStatistics <algm-ReflectometryBeamStatistics>` when processing the reflected beam. This adds some sample log entries to *OutputWorkspace* and *DirectBeamWorkspace*. See the :ref:`algorithm's documentation <algm-ReflectometryBeamStatistics>` for more details. + Summation type ############## The *SummationType* property controls how the foreground pixels are summed. *SumInLambda* - extracts the centre pixel histogram using :ref:`ExtractSingleSpectrum <algm-ExtractSingleSpectrum>` and adds the intensities of the rest of the foreground pixels. Errors are summed in squares. The summed data is subsequently divided by the direct beam. + extracts the centre pixel histogram using :ref:`ExtractSingleSpectrum <algm-ExtractSingleSpectrum>` and adds the intensities of the rest of the foreground pixels. *SumInQ* - sums the pixels using :ref:`ReflectometrySumInQ <algm-ReflectometrySumInQ>`. Before summation, the data is divided by the direct beam. + sums the foreground pixels using :ref:`ReflectometrySumInQ <algm-ReflectometrySumInQ>`. Before summation, the data is divided by the direct beam. + +The chosen *SummationType* will be added to the sample logs of *OutputWorkspace* under the ``foreground.summation_type`` entry. Foreground pixels ################# @@ -56,7 +57,7 @@ Usage 'HighAngleBkgOffset': 10, 'HighAngleBkgWidth': 50, } - + # Direct beam direct = ReflectometryILLPreprocess( Run='ILL/D17/317369.nxs', @@ -65,7 +66,7 @@ Usage ) # We need the summed direct beam for the reflectivity directFgd = ReflectometryILLSumForeground(direct.OutputWorkspace) - + # Reflected beam reflected = ReflectometryILLPreprocess( Run='ILL/D17/317370.nxs', @@ -75,9 +76,10 @@ Usage reflectivity = ReflectometryILLSumForeground( InputWorkspace=reflected, DirectForegroundWorkspace=directFgd, + DirectBeamWorkspace=direct.OutputWorkspace, WavelengthRange=[2, 15], ) - + # Reflectivity is a single histogram print('Histograms in reflectivity workspace: {}'.format(reflectivity.getNumberHistograms())) # The data is still in wavelength @@ -104,7 +106,7 @@ Output: 'HighAngleBkgOffset': 10, 'HighAngleBkgWidth': 50, } - + # Direct beam direct = ReflectometryILLPreprocess( Run='ILL/D17/317369.nxs', @@ -113,7 +115,7 @@ Output: ) # We need the summed direct beam for the reflectivity directFgd = ReflectometryILLSumForeground(direct.OutputWorkspace) - + # Reflected beam reflected = ReflectometryILLPreprocess( Run='ILL/D17/317370.nxs', @@ -123,10 +125,11 @@ Output: reflectivity = ReflectometryILLSumForeground( InputWorkspace=reflected, DirectForegroundWorkspace=directFgd, + DirectBeamWorkspace=direct.OutputWorkspace, SummationType='SumInQ', WavelengthRange=[0., 14.] ) - + # Reflectivity is a single histogram print('Histograms in reflectivity workspace: {}'.format(reflectivity.getNumberHistograms())) # The data is still in wavelength diff --git a/docs/source/algorithms/ReflectometryMomentumTransfer-v1.rst b/docs/source/algorithms/ReflectometryMomentumTransfer-v1.rst index 457afbb6aa6809df99f2a75b068b4164a4df8d3b..f026e0c946fa7d6d37b27d1625e3aa0be2307111 100644 --- a/docs/source/algorithms/ReflectometryMomentumTransfer-v1.rst +++ b/docs/source/algorithms/ReflectometryMomentumTransfer-v1.rst @@ -11,11 +11,9 @@ Description This algorithm converts a reflectivity workspace from wavelength to momentum transfer :math:`Q_{z}` and calculates the :math:`Q_{z}` resolution. The resolution is added as the Dx (X Errors) field in the output workspace. This algorithms processes workspaces in which the pixels containing the reflectected line have been integrated into a single histogram. For conversion of a 2D workspace into :math:`Q_{x}, Q_{z}` or equivalent momentum space, see :ref:`ConvertToReflectometryQ <algm-ConvertToReflectometryQ>`. -The two additional input workspaces, *ReflectedBeamWorkspace* and *DirectBeamWorkspace* are the raw reflected and direct beam workspaces before foreground summation. They are needed for the resolution calculation. +The algorithm requires the presence of certain sample log entries for the :math:`Q_{z}` resolution computation. These can be obtained by :ref:`ReflectometryBeamStatistics <algm-ReflectometryBeamStatistics>`. The log entries are: ``beam_stats.incident_angular_spread``, ``beam_stats.first_slit_angular_spread``, ``beam_stats.second_slit_angular_spread`` and ``beam_stats.sample_waviness``. See the documentation of :ref:`ReflectometryBeamStatistics <algm-ReflectometryBeamStatistics>` for more detailed information on the sample logs. -The instruments of all three input workspaces are expected contain two components representing the two slits in the beam before the sample. The names of these components are given to the algorithm as the *FirstSlitName* and *SecondSlitName* properties. The slit openings (width or height depending on reflectometer setup) should be written in the sample logs (units 'm' or 'mm'). The log enties are named by *FirstSlitSizeSampleLog* and *SecondSlitSizeSampleLog*. - -The *Polarized* property should be used to indicate whether *InputWorkspace* is part of a polarization analysis dataset. +The instrument of *InputWorkspace* is expected to contain two components representing the two slits in the beam before the sample. The names of these components are given to the algorithm as the *FirstSlitName* and *SecondSlitName* properties. The slit openings (width or height depending on reflectometer setup) should be written in the sample logs (units 'm' or 'mm'). The log enties are named by *FirstSlitSizeSampleLog* and *SecondSlitSizeSampleLog*. The *SummationType* property reflects the type of foreground summation used to obtain the reflectivity workspace. @@ -43,26 +41,40 @@ Usage ConvertToDistribution(reflectedWS) directWS = LoadILLReflectometry('ILL/D17/317369.nxs', XUnit='TimeOfFlight') ConvertToDistribution(directWS) - + # Extract some instrument parameters. chopperPairDistance = 1e-2 * reflectedWS.run().getProperty('Distance.ChopperGap').value chopperSpeed = reflectedWS.run().getProperty('VirtualChopper.chopper1_speed_average').value chopper1Phase = reflectedWS.run().getProperty('VirtualChopper.chopper1_phase_average').value chopper2Phase = reflectedWS.run().getProperty('VirtualChopper.chopper2_phase_average').value openoffset = reflectedWS.run().getProperty('VirtualChopper.open_offset').value - + # Normalize to time. duration = reflectedWS.run().getProperty('duration').value reflectedWS /= duration duration = directWS.run().getProperty('duration').value directWS /= duration - + + # Write statistics to sample logs. + ReflectometryBeamStatistics( + reflectedWS, + ReflectedForeground=[198, 204, 209], + DirectBeamWorkspace=directWS, + DirectForeground=[190, 200, 210], + PixelSize=0.001195, + DetectorResolution=0.0022, + FirstSlitName='slit2', + FirstSlitSizeSampleLog='VirtualSlitAxis.s2w_actual_width', + SecondSlitName='slit3', + SecondSlitSizeSampleLog='VirtualSlitAxis.s3w_actual_width', + ) + # Calculate reflectivity. refForeground = SumSpectra(reflectedWS, 198, 209) dirForeground = SumSpectra(directWS, 190, 210) refForeground = RebinToWorkspace(WorkspaceToRebin=refForeground, WorkspaceToMatch=dirForeground) R = refForeground / dirForeground - + # Convert TOF to wavelength, crop. R = ConvertUnits(R, 'Wavelength') R = CropWorkspace(R, XMin=4.3, XMax=14.0, StoreInADS=False) @@ -71,15 +83,11 @@ Usage reflectedWS = CropWorkspaceRagged(reflectedWS, XMin=n*[4.3], XMax=n*[14.0], StoreInADS=False) directWS = ConvertUnits(directWS, 'Wavelength') directWS = CropWorkspaceRagged(directWS, XMin=n*[4.3], XMax=n*[14.0]) - + outws = ReflectometryMomentumTransfer( R, - reflectedWS, - directWS, ReflectedForeground=[198, 209], - DirectForeground=[190, 210], SummationType='SumInLambda', - Polarized=False, PixelSize=0.001195, DetectorResolution=0.0022, ChopperRadius=0.36, diff --git a/docs/source/algorithms/ReflectometryReductionOne-v2.rst b/docs/source/algorithms/ReflectometryReductionOne-v2.rst index 0116f7f71ff330415cd5673c97a6fbdc3155f7bf..61a694bdcdd82d872aaa39b702f588c2f58f4010 100644 --- a/docs/source/algorithms/ReflectometryReductionOne-v2.rst +++ b/docs/source/algorithms/ReflectometryReductionOne-v2.rst @@ -113,10 +113,8 @@ transmission runs or specific correction algorithms. When normalizing by transmission runs, i.e. when one or two transmission runs are given, the spectrum numbers in the transmission workspaces must be the same as those in the input run -workspace. If spectrum numbers do not match, the algorithm will throw and exception -and execution of the algorithm will be stopped. This behaviour can be optionally -switched off by setting :literal:`StrictSpectrumChecking` to false, in which case -a warning message will be shown instead. +workspace. You can pass individual processing instructions to the transmission +runs. When normalizing by transmission run, this algorithm will run :ref:`algm-CreateTransmissionWorkspace` as a child algorithm, with properties :literal:`WavelengthMin`, @@ -224,7 +222,7 @@ Usage IvsQ, IvsLam = ReflectometryReductionOne(InputWorkspace=run, WavelengthMin=1.0, WavelengthMax=17.0, - ProcessingInstructions='3', + ProcessingInstructions='4', I0MonitorIndex=2, MonitorBackgroundWavelengthMin=15.0, MonitorBackgroundWavelengthMax=17.0, @@ -258,7 +256,7 @@ Output: IvsQ, IvsLam = ReflectometryReductionOne(InputWorkspace=run, WavelengthMin=1.0, WavelengthMax=17.0, - ProcessingInstructions='3', + ProcessingInstructions='4', I0MonitorIndex=2, MonitorBackgroundWavelengthMin=15.0, MonitorBackgroundWavelengthMax=17.0, diff --git a/docs/source/algorithms/SANSILLReduction-v1.rst b/docs/source/algorithms/SANSILLReduction-v1.rst new file mode 100644 index 0000000000000000000000000000000000000000..fe62a428cb43ebd75a35a63b772168ad4a0a740b --- /dev/null +++ b/docs/source/algorithms/SANSILLReduction-v1.rst @@ -0,0 +1,277 @@ +.. algorithm:: + +.. summary:: + +.. relatedalgorithms:: + +.. properties:: + +Description +----------- + +This algorithm performs SANS reduction for the instruments at the ILL. +With each call, this algorithm processes one type of data which is a part of the whole experiment. +The logic is resolved by the property **ProcessAs**, which governs the reduction steps based on the requested type. +It can be one of the 6: absorber, beam, transmission, container, reference and sample. +The full data treatment of the complete experiment should be build up as a chain with multiple calls of this algorithm over various types of acquisitions. +The sequence should be logical, typically as enumerated above, since the later processes need the outputs of earlier processes as input. +The common mandatory input is a run file (numor), or a list of them, in which case they will be summed at raw level, so right after loading. +The other common input is the normalisation type (time or monitor) that must be the same for all the runs in full reduction. +The common mandatory output is a workspace, but up to which step it is processed, depends on **ProcessAs**. + +ProcessAs +--------- +Different input properties can be specified depending on the value of **ProcessAs**, as summarized in the table: + ++--------------+-------------------------------+------------------------+ +| ProcessAs | Input Workspace Properties | Other Input Properties | ++==============+===============================+========================+ +| Absorber | | | ++--------------+-------------------------------+------------------------+ +| Beam | * AbsorberInputWorkspace | * BeamRadius | +| | | * BeamFinderMethod | ++--------------+-------------------------------+------------------------+ +| Transmission | * AbsorberInputWorkspace | * BeamRadius | +| | * **BeamInputWorkspace** | | ++--------------+-------------------------------+------------------------+ +| Container | * AbsorberInputWorkspace | | +| | * BeamInputWorkspace | | +| | * TransmissionInputWorkspace | | ++--------------+-------------------------------+------------------------+ +| Reference | * AbsorberInputWorkspace | * SampleThickness | +| | * BeamInputWorkspace | | +| | * TransmissionInputWorkspace | | +| | * ContainerInputWorkspace | | +| | * MaskedInputWorkspace | | ++--------------+-------------------------------+------------------------+ +| Sample | * AbsorberInputWorkspace | * SampleThickness | +| | * BeamInputWorkspace | | +| | * TransmissionInputWorkspace | | +| | * ContainerInputWorkspace | | +| | * MaskedInputWorkspace | | +| | * ReferenceInputWorkspace | | +| | * SensitivityInputWorkspace | | ++--------------+-------------------------------+------------------------+ + +All the input workspace properties above are optional. +For example, if processing as sample, if a container input is specified, subtraction will be performed, if not, the step will be skipped. +The only exception is when processing as transmission, when beam input workspace is mandatory. +When processing as reference there is an additional optional output workspace for sensitivity. + +In the flowcharts below the yellow ovals represent the inputs, the grey parallelograms are the outputs for each process type. + +Absorber +~~~~~~~~ + +.. diagram:: ILLSANS-v1_absorber_wkflw.dot + +Beam +~~~~ + +.. diagram:: ILLSANS-v1_beam_wkflw.dot + +Transmission +~~~~~~~~~~~~ + +.. diagram:: ILLSANS-v1_transmission_wkflw.dot + +Container +~~~~~~~~~ + +.. diagram:: ILLSANS-v1_container_wkflw.dot + +Reference +~~~~~~~~~ + +.. diagram:: ILLSANS-v1_reference_wkflw.dot + +Sample +~~~~~~ + +.. diagram:: ILLSANS-v1_sample_wkflw.dot + +Full Treatment +-------------- + +This example performs the complete reduction for D11. :ref:`Q1DWeighted <algm-Q1DWeighted>` can be called over the final result to produce the :math:'I(Q)'. + +.. note:: + + For transmission calculation, the beam run and the transmission run have to be recorded at the same instrument configuration. + For beam flux normalisation and beam center movement, the beam run and the sample run have to be recorded at the same configuration. + For container subtraction, the container and the sample run have to be recorded at the same configuration. + Otherwise a warning is logged, but the execution does not stop. + +.. include:: ../usagedata-note.txt + +**Example - full treatment of a sample** + +.. testsetup:: ExSANSILLReduction + + config['default.facility'] = 'ILL' + config.appendDataSearchSubDir('ILL/D11/') + +.. testcode:: ExSANSILLReduction + + # Process the dark current Cd/B4C for water + SANSILLReduction(Run='010455.nxs', ProcessAs='Absorber', OutputWorkspace='Cdw') + + # Process the empty beam for water + SANSILLReduction(Run='010414.nxs', ProcessAs='Beam', AbsorberInputWorkspace='Cdw', OutputWorkspace='Dbw') + + # Water container transmission + SANSILLReduction(Run='010446.nxs', ProcessAs='Transmission', + AbsorberInputWorkspace='Cdw', BeamInputWorkspace='Dbw', + OutputWorkspace='wc_tr') + print('Water container transmission is {0:.3f}'.format(mtd['wc_tr'].readY(0)[0])) + + # Water container + SANSILLReduction(Run='010454.nxs', ProcessAs='Container', + AbsorberInputWorkspace='Cdw', BeamInputWorkspace='Dbw', + TransmissionInputWorkspace='wc_tr', OutputWorkspace='wc') + + # Water transmission + SANSILLReduction(Run='010445.nxs', ProcessAs='Transmission', + AbsorberInputWorkspace='Cdw', BeamInputWorkspace='Dbw', OutputWorkspace='w_tr') + print('Water transmission is {0:.3f}'.format(mtd['w_tr'].readY(0)[0])) + + # Water + SANSILLReduction(Run='010453.nxs', ProcessAs='Reference', + AbsorberInputWorkspace='Cdw', ContainerInputWorkspace='wc', + BeamInputWorkspace='Dbw', TransmissionInputWorkspace='wc_tr', + SensitivityOutputWorkspace='sens', OutputWorkspace='water') + + # Process the dark current Cd/B4C for sample + SANSILLReduction(Run='010462.nxs', ProcessAs='Absorber', OutputWorkspace='Cd') + + # Process the empty beam for sample + SANSILLReduction(Run='010413.nxs', ProcessAs='Beam', AbsorberInputWorkspace='Cd', OutputWorkspace='Db') + + # Sample container transmission + SANSILLReduction(Run='010444.nxs', ProcessAs='Transmission', + AbsorberInputWorkspace='Cd', BeamInputWorkspace='Dbw', OutputWorkspace='sc_tr') + print('Sample container transmission is {0:.3f}'.format(mtd['sc_tr'].readY(0)[0])) + + # Sample container + SANSILLReduction(Run='010460.nxs', ProcessAs='Container', + AbsorberInputWorkspace='Cd', BeamInputWorkspace='Db', + TransmissionInputWorkspace='sc_tr', OutputWorkspace='sc') + + # Sample transmission + SANSILLReduction(Run='010585.nxs', ProcessAs='Transmission', + AbsorberInputWorkspace='Cd', BeamInputWorkspace='Dbw', OutputWorkspace='s_tr') + print('Sample transmission is {0:.3f}'.format(mtd['s_tr'].readY(0)[0])) + + # Sample + SANSILLReduction(Run='010569.nxs', ProcessAs='Sample', + AbsorberInputWorkspace='Cd', ContainerInputWorkspace='sc', + BeamInputWorkspace='Db', SensitivityInputWorkspace='sens', + TransmissionInputWorkspace='s_tr', OutputWorkspace='sample_flux') + + # Convert to I(Q) + Q1DWeighted(InputWorkspace='sample_flux', NumberOfWedges=0, OutputBinning='0.003,0.001,0.027', OutputWorkspace='iq') + +Output: + +.. testoutput:: ExSANSILLReduction + + Water container transmission is 0.945 + Water transmission is 0.500 + Sample container transmission is 0.665 + Sample transmission is 0.640 + +.. testcleanup:: ExSANSILLReduction + + mtd.clear() + +Plot the I(Q): + +.. code-block:: python + + import matplotlib.pyplot as plt + fig, ax = plt.subplots(subplot_kw={'projection':'mantid'}) + plt.xscale('log') + plt.yscale('log') + ax.errorbar(mtd['iq'],'-rs') + ax.set_ylabel('I [cm-1]') + ax.legend() + fig.show() + +.. plot:: + + from mantid.simpleapi import SANSILLReduction, Q1DWeighted, mtd, config + + config['default.facility'] = 'ILL' + config.appendDataSearchSubDir('ILL/D11/') + + # Process the dark current Cd/B4C for water + SANSILLReduction(Run='010455.nxs', ProcessAs='Absorber', OutputWorkspace='Cdw') + + # Process the empty beam for water + SANSILLReduction(Run='010414.nxs', ProcessAs='Beam', AbsorberInputWorkspace='Cdw', OutputWorkspace='Dbw') + + # Water container transmission + SANSILLReduction(Run='010446.nxs', ProcessAs='Transmission', + AbsorberInputWorkspace='Cdw', BeamInputWorkspace='Dbw', + OutputWorkspace='wc_tr') + print('Water container transmission is {0:.3f}'.format(mtd['wc_tr'].readY(0)[0])) + + # Water container + SANSILLReduction(Run='010454.nxs', ProcessAs='Container', + AbsorberInputWorkspace='Cdw', BeamInputWorkspace='Dbw', + TransmissionInputWorkspace='wc_tr', OutputWorkspace='wc') + + # Water transmission + SANSILLReduction(Run='010445.nxs', ProcessAs='Transmission', + AbsorberInputWorkspace='Cdw', BeamInputWorkspace='Dbw', OutputWorkspace='w_tr') + print('Water transmission is {0:.3f}'.format(mtd['w_tr'].readY(0)[0])) + + # Water + SANSILLReduction(Run='010453.nxs', ProcessAs='Reference', + AbsorberInputWorkspace='Cdw', ContainerInputWorkspace='wc', + BeamInputWorkspace='Dbw', TransmissionInputWorkspace='wc_tr', + SensitivityOutputWorkspace='sens', OutputWorkspace='water') + + # Process the dark current Cd/B4C for sample + SANSILLReduction(Run='010462.nxs', ProcessAs='Absorber', OutputWorkspace='Cd') + + # Process the empty beam for sample + SANSILLReduction(Run='010413.nxs', ProcessAs='Beam', AbsorberInputWorkspace='Cd', OutputWorkspace='Db') + + # Sample container transmission + SANSILLReduction(Run='010444.nxs', ProcessAs='Transmission', + AbsorberInputWorkspace='Cd', BeamInputWorkspace='Dbw', OutputWorkspace='sc_tr') + print('Sample container transmission is {0:.3f}'.format(mtd['sc_tr'].readY(0)[0])) + + # Sample container + SANSILLReduction(Run='010460.nxs', ProcessAs='Container', + AbsorberInputWorkspace='Cd', BeamInputWorkspace='Db', + TransmissionInputWorkspace='sc_tr', OutputWorkspace='sc') + + # Sample transmission + SANSILLReduction(Run='010585.nxs', ProcessAs='Transmission', + AbsorberInputWorkspace='Cd', BeamInputWorkspace='Dbw', OutputWorkspace='s_tr') + print('Sample transmission is {0:.3f}'.format(mtd['s_tr'].readY(0)[0])) + + # Sample + SANSILLReduction(Run='010569.nxs', ProcessAs='Sample', + AbsorberInputWorkspace='Cd', ContainerInputWorkspace='sc', + BeamInputWorkspace='Db', SensitivityInputWorkspace='sens', + TransmissionInputWorkspace='s_tr', OutputWorkspace='sample_flux') + + # Convert to I(Q) + Q1DWeighted(InputWorkspace='sample_flux', NumberOfWedges=0, OutputBinning='0.003,0.001,0.027', OutputWorkspace='iq') + + # Plot the output + import matplotlib.pyplot as plt + fig, ax = plt.subplots(subplot_kw={'projection':'mantid'}) + plt.xscale('log') + plt.yscale('log') + ax.errorbar(mtd['iq'],'-rs') + ax.set_ylabel('I [cm-1]') + ax.legend() + #fig.show() + +.. categories:: + +.. sourcelink:: diff --git a/docs/source/algorithms/SANSReduction-v1.rst b/docs/source/algorithms/SANSReduction-v1.rst index b971806dd524ed6c8c8ef8bc487b2044eac097aa..0fb1e1ff65f22f075253b27d735674a433de1ae8 100644 --- a/docs/source/algorithms/SANSReduction-v1.rst +++ b/docs/source/algorithms/SANSReduction-v1.rst @@ -9,15 +9,15 @@ Description ----------- -Executes the SANS reduction workflow according to the options set by -:ref:`SetupEQSANSReduction <algm-SetupEQSANSReduction>` or :ref:`SetupILLD33Reduction <algm-SetupILLD33Reduction>`. +Executes the SANS reduction workflow according to the options set by +:ref:`SetupEQSANSReduction <algm-SetupEQSANSReduction>`. Those options are saved in a PropertyManager object that is passed through *ReductionProperties*. The workflow proceeds as follows: 1. Execute the beam finder algorithm. Usually :ref:`SANSBeamFinder <algm-SANSBeamFinder>`. -2. Load the data to be reduced, usually with :ref:`EQSANSLoad <algm-EQSANSLoad>`, +2. Load the data to be reduced, usually with :ref:`EQSANSLoad <algm-EQSANSLoad>`, which will move the detector to the right position. 3. Subtract the dark current, usually with :ref:`EQSANSDarkCurrentSubtraction <algm-EQSANSDarkCurrentSubtraction>`. @@ -28,19 +28,19 @@ The workflow proceeds as follows: 6. Apply the solid angle correction, usually with :ref:`SANSSolidAngleCorrection <algm-SANSSolidAngleCorrection>`. -7. Apply the sensitivity correction, usually with :ref:`SANSSensitivityCorrection <algm-SANSSensitivityCorrection>`. +7. Apply the sensitivity correction, usually with :ref:`SANSSensitivityCorrection <algm-SANSSensitivityCorrection>`. When applicable, a separate beam center position can be determined for the sensitivity data. - + 8. Compute and apply the transmission correction, usually with :ref:`EQSANSDirectBeamTransmission <algm-EQSANSDirectBeamTransmission>`. When applicable, a separate beam center position can be determined for the transmission data. 9. Repeat steps 2 to 8 for the background run, then subtract the background from the sample data. -10. Perform the absolute scaling, usually with :ref:`SANSAbsoluteScale <algm-SANSAbsoluteScale>`. +10. Perform the absolute scaling, usually with :ref:`SANSAbsoluteScale <algm-SANSAbsoluteScale>`. -11. Perform any geometrical correction, which is usually a call to :ref:`NormaliseByThickness <algm-NormaliseByThickness>`. +11. Perform any geometrical correction, which is usually a call to :ref:`NormaliseByThickness <algm-NormaliseByThickness>`. -12. Perform the I(Q) calculation with :ref:`EQSANSAzimuthalAverage1D <algm-EQSANSAzimuthalAverage1D>`. +12. Perform the I(Q) calculation with :ref:`EQSANSAzimuthalAverage1D <algm-EQSANSAzimuthalAverage1D>`. 13. Perform the I(Qx,Qy) calculation with :ref:`EQSANSQ2D <algm-EQSANSQ2D>`. diff --git a/docs/source/algorithms/SaveCalFile-v1.rst b/docs/source/algorithms/SaveCalFile-v1.rst index 5efebfccc2b97f5ec4e369422d12f647ad04a8e0..2926edf17e65de4a9fdedba83763cf4f05c0b3c4 100644 --- a/docs/source/algorithms/SaveCalFile-v1.rst +++ b/docs/source/algorithms/SaveCalFile-v1.rst @@ -9,30 +9,10 @@ Description ----------- -This algorithm saves an -`ARIEL-style <http://www.isis.stfc.ac.uk/instruments/gem/software/ariel-installation-instructions6723.html>`__ -5-column ASCII .cal file. Here is an excerpt:: - - # Calibration file for instrument POWGEN written on 2014-02-14T18:46:31.610072000. - # Format: number UDET offset select group - 0 -1 0.0000000 1 0 - 1 110000 0.0001250 1 1 - 2 110001 0.0003750 1 1 - 3 110002 -0.0020000 1 1 - 4 110003 0.0000000 0 1 - 5 110004 0.0000000 0 1 - - -The format is - -- number: ignored. -- UDET: detector ID. -- offset: calibration offset used in :ref:`algm-AlignDetectors`. Comes from - the ``OffsetsWorkspace``, or 0.0 if none is given. -- select: 1 if selected (use the pixel). Comes from the ``MaskWorkspace``, - or 1 if none is given. -- group: what group to focus to in :ref:`algm-DiffractionFocussing`. Comes from the - ``GroupingWorkspace``, or 1 if none is given. +This algorithm saves an `ARIEL-style +<http://www.isis.stfc.ac.uk/instruments/gem/software/ariel-installation-instructions6723.html>`__ +5-column ASCII ``.cal`` file. The full description can be found +:ref:`here <CalFile>`. :ref:`algm-LoadCalFile` diff --git a/docs/source/algorithms/SetInstrumentParameter-v1.rst b/docs/source/algorithms/SetInstrumentParameter-v1.rst index b3280db781ee8ce758293fbe953307fe521fd939..6fec4a65348a803b58be717f160094834e985c30 100644 --- a/docs/source/algorithms/SetInstrumentParameter-v1.rst +++ b/docs/source/algorithms/SetInstrumentParameter-v1.rst @@ -12,7 +12,7 @@ Description This algorithm adds or replaces an parameter attached to an instrument component, or the entire instrument. Instrument parameters are specific to a workspace, they will get carried on to output workspaces created -from an input workspace to an algorithm, but will not appear one +from an input workspace to an algorithm, but will not appear on unrelated workspaces that happen to have been recorded on the same instrument. @@ -32,7 +32,7 @@ For `Bool` type, valid values are `1`, `0`, `true` or `false` (not case-sensitiv Usage ----- -**Example - a few simple parameters** +**Example - a few simple parameters** .. testcode:: Ex1 @@ -61,7 +61,7 @@ Usage #For this one call getNumberParameter as the number was a float print(" bank 2: " + str(bank2.getNumberParameter("NumberParam")[0])) #if you are not sure of the type of a parameter you can call getParameterType - print(" The type of NumberParam in bank 1: " + bank1.getParameterType("NumberParam")) + print(" The type of NumberParam in bank 1: " + bank1.getParameterType("NumberParam")) print(" The type of NumberParam in bank 2: " + bank2.getParameterType("NumberParam")) @@ -79,7 +79,7 @@ Output: The type of NumberParam in bank 1: int The type of NumberParam in bank 2: double -**Example - Overwriting existing values** +**Example - Overwriting existing values** .. testcode:: Ex2 @@ -91,10 +91,10 @@ Output: instrument=ws.getInstrument() bank1=instrument.getComponentByName("bank1") - + print("The SetInstrumentParameter overwrites previous values where the ParameterName and Component match.") print(" The test param for the instrument is: " + instrument.getStringParameter("TestParam")[0]) - print("Different Components can have the same Parameter Name with different values.") + print("Different Components can have the same Parameter Name with different values.") print("You will receive the closest value to the component you ask from.") print(" The test param for bank 1 is: " + bank1.getStringParameter("TestParam")[0]) diff --git a/docs/source/algorithms/SpecularReflectionPositionCorrect-v2.rst b/docs/source/algorithms/SpecularReflectionPositionCorrect-v2.rst index 1773378e9535431b5681566d76f58f7b87bf97e1..f2cc1a80666a2ea05e152cd7bdae4049bf5c7a11 100644 --- a/docs/source/algorithms/SpecularReflectionPositionCorrect-v2.rst +++ b/docs/source/algorithms/SpecularReflectionPositionCorrect-v2.rst @@ -9,15 +9,49 @@ Description ----------- -Moves the specified detector component so that the angle between the beam and -the sample-to-detector vector is :literal:`TwoTheta`. The detector component is moved as a -block. The rest of the instrument components remain in the original position. The component -can be shifted vertically (default), or rotated around the sample position. +Moves the specified detector component to a given angle between the beam and +the sample-to-detector vector. The detector component is moved as a +block. The rest of the instrument components remain in original positions. The component +can be shifted vertically (default), or rotated around the sample position. When rotating +around the sample, an optional rotation can be applied so that the detector always faces +the sample. + +In the most basic operation, the detector is moved to the angle specified by ``TwoTheta``. +This case is schematically shown below for rotation around the sample position and +``DetectorFacesSample`` set to ``True``. + +.. figure:: ../images/SpecularReflectionCorrection_figure1.png + :alt: Schematic representation of angle correction when only TwoTheta is given. + :scale: 100% + + +If ``LinePosition`` and ``PixelSize`` are also given, the detector will be moved such +that the angle will be ``TwoTheta`` for the pixel at workspace index ``LinePosition``. +Note that ``LinePosition`` can be a fractional index making it possible to adjust the +detector angle to a fitted line position. The figure below illustrates this option. + +.. figure:: ../images/SpecularReflectionCorrection_figure2.png + :alt: Schematic representation of angle correction when TwoTheta and LinePosition are given. + :scale: 100% + +The last possible detector position correction is calibration by a separate direct beam +measurement. The idea is to correct the detector angle by difference between the +nominal beam axis and the measured direct beam as shown in the figure below. In this mode +``TwoTheta`` is omitted but ``DirectLinePosition``, ``DirectLineWorkspace`` and ``PixelSize`` +have to be specified. + +.. figure:: ../images/SpecularReflectionCorrection_figure3.png + :alt: Schematic representation of angle correction when direct beam reference is used. + :scale: 100% + +.. note:: + The line positions can be acquired by using, for instance, + :ref:`FindReflectometryLines <algm-FindReflectometryLines>`. Previous Versions ----------------- -For version 1 of the algorithm, please see `SpecularReflectionPositionCorrect-v1. <SpecularReflectionPositionCorrect-v1.html>`_ +For version 1 of the algorithm, please see :ref:`SpecularReflectionPositionCorrect-v1. <algm-SpecularReflectionPositionCorrect-v1>` Usage ----- @@ -110,6 +144,81 @@ Output: Vertical shift: [26,0,0.0513177] Rotated: [25.9996,0,0.0513102] +**Example - Rotate given pixel** + +.. testcode:: SpecularReflectionPositionCorrectLinePosition + + import numpy + # We'll just use an empty workspace here. + ws = LoadEmptyInstrument(InstrumentName='D17') + # Get rid of monitors + ExtractMonitors(ws, DetectorWorkspace='ws') + ws = mtd['ws'] + line_position = 22. + spectrum_info = ws.spectrumInfo() + two_theta = numpy.rad2deg(spectrum_info.twoTheta(int(line_position))) + print('Pixel {} 2theta'.format(int(line_position))) + print('before angle correction: {:.3}'.format(two_theta)) + ws = SpecularReflectionPositionCorrect( + ws, + TwoTheta=1.5, + DetectorCorrectionType='RotateAroundSample', + DetectorComponentName='detector', + DetectorFacesSample=True, + LinePosition=line_position, + PixelSize=0.001195) + spectrum_info = ws.spectrumInfo() + two_theta = numpy.rad2deg(spectrum_info.twoTheta(int(line_position))) + print('after angle correction: {:.3}'.format(two_theta)) + +Output: + +.. testoutput:: SpecularReflectionPositionCorrectLinePosition + + Pixel 22 2theta + before angle correction: 2.33 + after angle correction: 1.5 + +**Example - Use direct beam for angle calibration** + +.. testcode:: SpecularReflectionPositionCorrectDirectBeamCalibration + + import numpy + # We'll just use empty workspaces here. + reflected = LoadEmptyInstrument(InstrumentName='Figaro') + direct = LoadEmptyInstrument(InstrumentName='Figaro') + # Get rid of monitors + ExtractMonitors(reflected, DetectorWorkspace='reflected') + reflected = mtd['reflected'] + ExtractMonitors(direct, DetectorWorkspace='direct') + direct = mtd['direct'] + line_position = 202. + spectrum_info = reflected.spectrumInfo() + two_theta = numpy.rad2deg(spectrum_info.twoTheta(int(line_position))) + print('Pixel {} 2theta'.format(int(line_position))) + print('before angle correction: {:.3}'.format(two_theta)) + direct_line_position = 130.7 # This could come from some fitting procedure + reflected = SpecularReflectionPositionCorrect( + reflected, + DetectorCorrectionType='RotateAroundSample', + DetectorComponentName='detector', + DetectorFacesSample=True, + PixelSize=0.001195, + DirectLineWorkspace=direct, + DirectLinePosition=direct_line_position) + spectrum_info = reflected.spectrumInfo() + two_theta = numpy.rad2deg(spectrum_info.twoTheta(int(line_position))) + print('after angle correction: {:.3}'.format(two_theta)) + + +Output: + +.. testoutput:: SpecularReflectionPositionCorrectDirectBeamCalibration + + Pixel 202 2theta + before angle correction: 5.11 + after angle correction: 4.89 + .. categories:: .. sourcelink:: diff --git a/docs/source/api/python/mantid/api/Algorithm.rst b/docs/source/api/python/mantid/api/Algorithm.rst index 9be0850dc4767c650529d606e3b3426e0417895c..946d81dad6bc562d597077adbf76c5e8c4a6f82e 100644 --- a/docs/source/api/python/mantid/api/Algorithm.rst +++ b/docs/source/api/python/mantid/api/Algorithm.rst @@ -2,7 +2,7 @@ Algorithm =========== -This a python binding to the C++ class Mantid::API::Algorithm. +This is a Python binding to the C++ class Mantid::API::Algorithm. *bases:* :py:obj:`mantid.api.IAlgorithm` diff --git a/docs/source/api/python/mantid/api/AlgorithmFactoryImpl.rst b/docs/source/api/python/mantid/api/AlgorithmFactoryImpl.rst index b5e7b28a912332a1728d4da963a7975c55b6a3ba..63c883f2a7d9306917913c81bce7f268e4e984c9 100644 --- a/docs/source/api/python/mantid/api/AlgorithmFactoryImpl.rst +++ b/docs/source/api/python/mantid/api/AlgorithmFactoryImpl.rst @@ -2,7 +2,7 @@ AlgorithmFactoryImpl ====================== -This a python binding to the C++ class Mantid::API::AlgorithmFactoryImpl. +This is a Python binding to the C++ class Mantid::API::AlgorithmFactoryImpl. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/AlgorithmHistory.rst b/docs/source/api/python/mantid/api/AlgorithmHistory.rst index 7fda0bd412b5e95dc866158ec0341483fcd4763d..64a2f86f530482799c5594d2bc9709486d814cd6 100644 --- a/docs/source/api/python/mantid/api/AlgorithmHistory.rst +++ b/docs/source/api/python/mantid/api/AlgorithmHistory.rst @@ -2,7 +2,7 @@ AlgorithmHistory ================== -This a python binding to the C++ class Mantid::API::AlgorithmHistory. +This is a Python binding to the C++ class Mantid::API::AlgorithmHistory. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/AlgorithmID.rst b/docs/source/api/python/mantid/api/AlgorithmID.rst index 2b3475f41eca3424efab70bb6a85798d3505ffcd..04041b46f6ae4d91952c3e748fc226a1ab008f1e 100644 --- a/docs/source/api/python/mantid/api/AlgorithmID.rst +++ b/docs/source/api/python/mantid/api/AlgorithmID.rst @@ -2,7 +2,7 @@ AlgorithmID ============= -This a python binding to the C++ class Mantid::API::AlgorithmID. +This is a Python binding to the C++ class Mantid::API::AlgorithmID. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/AlgorithmManagerImpl.rst b/docs/source/api/python/mantid/api/AlgorithmManagerImpl.rst index 551d121887ce8ed6eb0ae0a880c608bf4bf4d614..9c2cd1a43bffa5ee8459f03149c758ce4c1f8c4b 100644 --- a/docs/source/api/python/mantid/api/AlgorithmManagerImpl.rst +++ b/docs/source/api/python/mantid/api/AlgorithmManagerImpl.rst @@ -2,7 +2,7 @@ AlgorithmManagerImpl ====================== -This a python binding to the C++ class Mantid::API::AlgorithmManagerImpl. +This is a Python binding to the C++ class Mantid::API::AlgorithmManagerImpl. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/AlgorithmProperty.rst b/docs/source/api/python/mantid/api/AlgorithmProperty.rst index e1451bc800f350176ca3655bb8c8f0b5e014c47e..91aedcafd5b686b8a5c2f5d5ac8628c61231108c 100644 --- a/docs/source/api/python/mantid/api/AlgorithmProperty.rst +++ b/docs/source/api/python/mantid/api/AlgorithmProperty.rst @@ -2,7 +2,7 @@ AlgorithmProperty =================== -This a python binding to the C++ class Mantid::API::AlgorithmProperty. +This is a Python binding to the C++ class Mantid::API::AlgorithmProperty. *bases:* :py:obj:`mantid.api.AlgorithmPropertyWithValue` diff --git a/docs/source/api/python/mantid/api/AlgorithmPropertyWithValue.rst b/docs/source/api/python/mantid/api/AlgorithmPropertyWithValue.rst index 27395c07fc0712c9a9e1bcbf200fe131ff6e8fe2..815568edacd5c896b1929ec9afc5606bc58ee372 100644 --- a/docs/source/api/python/mantid/api/AlgorithmPropertyWithValue.rst +++ b/docs/source/api/python/mantid/api/AlgorithmPropertyWithValue.rst @@ -2,7 +2,7 @@ AlgorithmPropertyWithValue ============================ -This a python binding to the C++ class Mantid::API::AlgorithmPropertyWithValue. +This is a Python binding to the C++ class Mantid::API::AlgorithmPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/api/AlgorithmProxy.rst b/docs/source/api/python/mantid/api/AlgorithmProxy.rst index 824754449dab579aaf08b61a18b28e14ed58dac5..7fb0355fc010a695cbd27888782afe711c8aa6b2 100644 --- a/docs/source/api/python/mantid/api/AlgorithmProxy.rst +++ b/docs/source/api/python/mantid/api/AlgorithmProxy.rst @@ -2,7 +2,7 @@ AlgorithmProxy ================ -This a python binding to the C++ class Mantid::API::AlgorithmProxy. +This is a Python binding to the C++ class Mantid::API::AlgorithmProxy. *bases:* :py:obj:`mantid.api.IAlgorithm` diff --git a/docs/source/api/python/mantid/api/AnalysisDataServiceImpl.rst b/docs/source/api/python/mantid/api/AnalysisDataServiceImpl.rst index 3c656a934f83c8ab0f45f933e336b3f0d37a85bd..e52826a75d71983d24a4c15987a4bcdd0596bba3 100644 --- a/docs/source/api/python/mantid/api/AnalysisDataServiceImpl.rst +++ b/docs/source/api/python/mantid/api/AnalysisDataServiceImpl.rst @@ -2,7 +2,7 @@ AnalysisDataServiceImpl ========================= -This a python binding to the C++ class Mantid::API::AnalysisDataServiceImpl. +This is a Python binding to the C++ class Mantid::API::AnalysisDataServiceImpl. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/BinEdgeAxis.rst b/docs/source/api/python/mantid/api/BinEdgeAxis.rst index c5fa3ae2953ca1aac8cfac2ab89d3e3fef443c49..94924675eb07a4e5325704b40e574164ddc223bd 100644 --- a/docs/source/api/python/mantid/api/BinEdgeAxis.rst +++ b/docs/source/api/python/mantid/api/BinEdgeAxis.rst @@ -2,7 +2,7 @@ BinEdgeAxis ============= -This a python binding to the C++ class Mantid::API::BinEdgeAxis. +This is a Python binding to the C++ class Mantid::API::BinEdgeAxis. *bases:* :py:obj:`mantid.api.NumericAxis` diff --git a/docs/source/api/python/mantid/api/BoxController.rst b/docs/source/api/python/mantid/api/BoxController.rst index 7e42e71c2aa715415c57650fd1069f628f1770f6..5dccc338fae3e6c16e43cba8c27c2e996b1ee1e4 100644 --- a/docs/source/api/python/mantid/api/BoxController.rst +++ b/docs/source/api/python/mantid/api/BoxController.rst @@ -2,7 +2,7 @@ BoxController =============== -This a python binding to the C++ class Mantid::API::BoxController. +This is a Python binding to the C++ class Mantid::API::BoxController. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/CommonBinsValidator.rst b/docs/source/api/python/mantid/api/CommonBinsValidator.rst index 31a55a0a885ee4957565d1f79abb31ac099fae38..2697c761822dc879b54e952bfec008dc152a9ef4 100644 --- a/docs/source/api/python/mantid/api/CommonBinsValidator.rst +++ b/docs/source/api/python/mantid/api/CommonBinsValidator.rst @@ -2,7 +2,7 @@ CommonBinsValidator ===================== -This a python binding to the C++ class Mantid::API::CommonBinsValidator. +This is a Python binding to the C++ class Mantid::API::CommonBinsValidator. *bases:* :py:obj:`mantid.api.MatrixWorkspaceValidator` diff --git a/docs/source/api/python/mantid/api/DataProcessorAlgorithm.rst b/docs/source/api/python/mantid/api/DataProcessorAlgorithm.rst index 7a70cea90bbd7d760ed05ac737ac8c0d039a001a..f7dbfc62c03e6fbaf2a4200e758b093a7c60a946 100644 --- a/docs/source/api/python/mantid/api/DataProcessorAlgorithm.rst +++ b/docs/source/api/python/mantid/api/DataProcessorAlgorithm.rst @@ -2,7 +2,7 @@ DataProcessorAlgorithm ======================== -This a python binding to the C++ class Mantid::API::DataProcessorAlgorithm. +This is a Python binding to the C++ class Mantid::API::DataProcessorAlgorithm. *bases:* :py:obj:`mantid.api.Algorithm` diff --git a/docs/source/api/python/mantid/api/DeprecatedAlgorithmChecker.rst b/docs/source/api/python/mantid/api/DeprecatedAlgorithmChecker.rst index da7db9459cd3800d04b87b109b17456e553daaf6..584cb3c10ab31a691622ed6407096fca67ddadb7 100644 --- a/docs/source/api/python/mantid/api/DeprecatedAlgorithmChecker.rst +++ b/docs/source/api/python/mantid/api/DeprecatedAlgorithmChecker.rst @@ -2,7 +2,7 @@ DeprecatedAlgorithmChecker ============================ -This a python binding to the C++ class Mantid::API::DeprecatedAlgorithm. +This is a Python binding to the C++ class Mantid::API::DeprecatedAlgorithm. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/EventType.rst b/docs/source/api/python/mantid/api/EventType.rst index 493680f818c2ecc9e9ed9bfee99dca239421967a..39cf0d9ce261eeba3f13a1360d93e7fdc175941c 100644 --- a/docs/source/api/python/mantid/api/EventType.rst +++ b/docs/source/api/python/mantid/api/EventType.rst @@ -2,7 +2,7 @@ EventType =========== -This a python binding to the C++ class Mantid::API::EventType. +This is a Python binding to the C++ class Mantid::API::EventType. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/ExperimentInfo.rst b/docs/source/api/python/mantid/api/ExperimentInfo.rst index 3ce6f20c07ad6350728740da2869933f77142134..f0dd848220970419ac751c09026180f326505007 100644 --- a/docs/source/api/python/mantid/api/ExperimentInfo.rst +++ b/docs/source/api/python/mantid/api/ExperimentInfo.rst @@ -2,7 +2,7 @@ ExperimentInfo ================ -This a python binding to the C++ class Mantid::API::ExperimentInfo. +This is a Python binding to the C++ class Mantid::API::ExperimentInfo. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/ExperimentInfoValidator.rst b/docs/source/api/python/mantid/api/ExperimentInfoValidator.rst index 1087657bea2e51f6f37e087dea0add3f0321dac2..1080b03df7713d50d196bdc0400e2fb7d95306f9 100644 --- a/docs/source/api/python/mantid/api/ExperimentInfoValidator.rst +++ b/docs/source/api/python/mantid/api/ExperimentInfoValidator.rst @@ -2,7 +2,7 @@ ExperimentInfoValidator ========================= -This a python binding to the C++ class Mantid::API::ExperimentInfoValidator. +This is a Python binding to the C++ class Mantid::API::ExperimentInfoValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/api/FileAction.rst b/docs/source/api/python/mantid/api/FileAction.rst index 06fee1b03888023cd444285b3f133d3dea5c1eaf..6a3cc2b26023e8cd17b58724c09ce83fbcded8b6 100644 --- a/docs/source/api/python/mantid/api/FileAction.rst +++ b/docs/source/api/python/mantid/api/FileAction.rst @@ -2,7 +2,7 @@ FileAction ============ -This a python binding to the C++ class Mantid::API::FileProperty. +This is a Python binding to the C++ class Mantid::API::FileProperty. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/FileFinderImpl.rst b/docs/source/api/python/mantid/api/FileFinderImpl.rst index 99ba18411bd77d3e90999dc74db3985b8f09a7da..4ffde854f75ed21a85e8b70c920f7999b087375f 100644 --- a/docs/source/api/python/mantid/api/FileFinderImpl.rst +++ b/docs/source/api/python/mantid/api/FileFinderImpl.rst @@ -2,7 +2,7 @@ FileFinderImpl ================ -This a python binding to the C++ class Mantid::API::FileFinderImpl. +This is a Python binding to the C++ class Mantid::API::FileFinderImpl. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/FileProperty.rst b/docs/source/api/python/mantid/api/FileProperty.rst index 84065e30bb75053e41b09f8908bb573cfbf01475..124195b82b479994243ec85d8d1ab7d40f642ac3 100644 --- a/docs/source/api/python/mantid/api/FileProperty.rst +++ b/docs/source/api/python/mantid/api/FileProperty.rst @@ -2,7 +2,7 @@ FileProperty ============== -This a python binding to the C++ class Mantid::API::FileProperty. +This is a Python binding to the C++ class Mantid::API::FileProperty. *bases:* :py:obj:`mantid.kernel.StringPropertyWithValue` diff --git a/docs/source/api/python/mantid/api/FrameworkManagerImpl.rst b/docs/source/api/python/mantid/api/FrameworkManagerImpl.rst index 5b989eb951fd131a91412ab8c2c42dc4f01dfee5..6219184b659d6e6efe281fc1aec9d0bca04d97e8 100644 --- a/docs/source/api/python/mantid/api/FrameworkManagerImpl.rst +++ b/docs/source/api/python/mantid/api/FrameworkManagerImpl.rst @@ -2,7 +2,7 @@ FrameworkManagerImpl ====================== -This a python binding to the C++ class Mantid::API::FrameworkManagerImpl. +This is a Python binding to the C++ class Mantid::API::FrameworkManagerImpl. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/FunctionFactoryImpl.rst b/docs/source/api/python/mantid/api/FunctionFactoryImpl.rst index d279475509cde6c545767f00446e044fad45cf19..1f2da3a8d49fc0535a6b2783c46bb1c64c9a08a5 100644 --- a/docs/source/api/python/mantid/api/FunctionFactoryImpl.rst +++ b/docs/source/api/python/mantid/api/FunctionFactoryImpl.rst @@ -2,7 +2,7 @@ FunctionFactoryImpl ===================== -This a python binding to the C++ class Mantid::API::FunctionFactoryImpl. +This is a Python binding to the C++ class Mantid::API::FunctionFactoryImpl. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/FunctionProperty.rst b/docs/source/api/python/mantid/api/FunctionProperty.rst index 63e8783cb5a962cc26c3d73124515b04a6fda779..6079290a329bd664d729c38da47d264c04722ae1 100644 --- a/docs/source/api/python/mantid/api/FunctionProperty.rst +++ b/docs/source/api/python/mantid/api/FunctionProperty.rst @@ -2,7 +2,7 @@ FunctionProperty ================== -This a python binding to the C++ class Mantid::API::FunctionProperty. +This is a Python binding to the C++ class Mantid::API::FunctionProperty. *bases:* :py:obj:`mantid.api.FunctionPropertyWithValue` diff --git a/docs/source/api/python/mantid/api/FunctionPropertyWithValue.rst b/docs/source/api/python/mantid/api/FunctionPropertyWithValue.rst index a7c99e634633a008c956a08e4478f2fa9464336a..73c3ea7b75add25206b32f964503148b20f71ad6 100644 --- a/docs/source/api/python/mantid/api/FunctionPropertyWithValue.rst +++ b/docs/source/api/python/mantid/api/FunctionPropertyWithValue.rst @@ -2,7 +2,7 @@ FunctionPropertyWithValue =========================== -This a python binding to the C++ class Mantid::API::FunctionPropertyWithValue. +This is a Python binding to the C++ class Mantid::API::FunctionPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/api/HistogramValidator.rst b/docs/source/api/python/mantid/api/HistogramValidator.rst index f49ad2a6edaea6991cbd222912667ae16bcea9a5..f7cb98a8dc22e47ba1bc8ec2bb77ad9c8d4fcbbf 100644 --- a/docs/source/api/python/mantid/api/HistogramValidator.rst +++ b/docs/source/api/python/mantid/api/HistogramValidator.rst @@ -2,7 +2,7 @@ HistogramValidator ==================== -This a python binding to the C++ class Mantid::API::HistogramValidator. +This is a Python binding to the C++ class Mantid::API::HistogramValidator. *bases:* :py:obj:`mantid.api.MatrixWorkspaceValidator` diff --git a/docs/source/api/python/mantid/api/IAlgorithm.rst b/docs/source/api/python/mantid/api/IAlgorithm.rst index 2d2208a407990791bf5f15335e90227c7eb5c244..ea2f0177e4ecf5400bbcbdd8a0bfbf067a3c6cfd 100644 --- a/docs/source/api/python/mantid/api/IAlgorithm.rst +++ b/docs/source/api/python/mantid/api/IAlgorithm.rst @@ -2,7 +2,7 @@ IAlgorithm ============ -This a python binding to the C++ class Mantid::API::IAlgorithm. +This is a Python binding to the C++ class Mantid::API::IAlgorithm. *bases:* :py:obj:`mantid.kernel.IPropertyManager` diff --git a/docs/source/api/python/mantid/api/IEventList.rst b/docs/source/api/python/mantid/api/IEventList.rst index 639749a577e7fa4514813ee63cf00b5d25f73810..2272702fa45475734a319d96e7a94ccdbd4d213c 100644 --- a/docs/source/api/python/mantid/api/IEventList.rst +++ b/docs/source/api/python/mantid/api/IEventList.rst @@ -2,7 +2,7 @@ IEventList ============ -This a python binding to the C++ class Mantid::API::IEventList. +This is a Python binding to the C++ class Mantid::API::IEventList. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/IEventWorkspace.rst b/docs/source/api/python/mantid/api/IEventWorkspace.rst index bec9724dd59bf802f19ebd79b93bbc43c03ff72f..93576ffd0b1962bb10b6a99775c804b5f146c0bf 100644 --- a/docs/source/api/python/mantid/api/IEventWorkspace.rst +++ b/docs/source/api/python/mantid/api/IEventWorkspace.rst @@ -4,7 +4,7 @@ IEventWorkspace ================= -This a python binding to the C++ class Mantid::API::IEventWorkspace. +This is a Python binding to the C++ class Mantid::API::IEventWorkspace. *bases:* :py:obj:`mantid.api.MatrixWorkspace` diff --git a/docs/source/api/python/mantid/api/IEventWorkspaceProperty.rst b/docs/source/api/python/mantid/api/IEventWorkspaceProperty.rst index 069eac4b73728a3407bdfc77e0a9778991620288..9668c4cd038e99c4140516b5e2c0f6b5251d43c0 100644 --- a/docs/source/api/python/mantid/api/IEventWorkspaceProperty.rst +++ b/docs/source/api/python/mantid/api/IEventWorkspaceProperty.rst @@ -2,7 +2,7 @@ IEventWorkspaceProperty ========================= -This a python binding to the C++ class Mantid::API::WorkspaceProperty. +This is a Python binding to the C++ class Mantid::API::WorkspaceProperty. *bases:* :py:obj:`mantid.api.IEventWorkspacePropertyPropertyWithValue`, :py:obj:`mantid.api.IWorkspaceProperty` diff --git a/docs/source/api/python/mantid/api/IEventWorkspacePropertyPropertyWithValue.rst b/docs/source/api/python/mantid/api/IEventWorkspacePropertyPropertyWithValue.rst index 324a7dd39db819811a71d62cf5a16f39f6f5cc9e..8b617996f5859c0c8a4688588345e0cba00600a3 100644 --- a/docs/source/api/python/mantid/api/IEventWorkspacePropertyPropertyWithValue.rst +++ b/docs/source/api/python/mantid/api/IEventWorkspacePropertyPropertyWithValue.rst @@ -2,7 +2,7 @@ IEventWorkspacePropertyPropertyWithValue ========================================== -This a python binding to the C++ class Mantid::API::IEventWorkspacePropertyPropertyWithValue. +This is a Python binding to the C++ class Mantid::API::IEventWorkspacePropertyPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/api/IFunction.rst b/docs/source/api/python/mantid/api/IFunction.rst index 57b91f662edf580a5d08fda79dc3fe55ed3b682b..6306a0b297c74e41b14db1d67ca6be834301d36a 100644 --- a/docs/source/api/python/mantid/api/IFunction.rst +++ b/docs/source/api/python/mantid/api/IFunction.rst @@ -2,7 +2,7 @@ IFunction =========== -This a python binding to the C++ class Mantid::API::IFunction. +This is a Python binding to the C++ class Mantid::API::IFunction. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/IFunction1D.rst b/docs/source/api/python/mantid/api/IFunction1D.rst index 5d4665d84213182f140e1b95eb75f61d80e1eb14..ad381f84593993aa744fb5a9d0b23e687d5e1b02 100644 --- a/docs/source/api/python/mantid/api/IFunction1D.rst +++ b/docs/source/api/python/mantid/api/IFunction1D.rst @@ -2,7 +2,7 @@ IFunction1D ============= -This a python binding to the C++ class Mantid::API::IFunction1D. +This is a Python binding to the C++ class Mantid::API::IFunction1D. *bases:* :py:obj:`mantid.api.IFunction` diff --git a/docs/source/api/python/mantid/api/IMDEventWorkspace.rst b/docs/source/api/python/mantid/api/IMDEventWorkspace.rst index a1f23bac9ea859c6dcb279ce9fa2b4db9784ca44..c28c6cc7c7fac4179185c586d5d20c26cad82580 100644 --- a/docs/source/api/python/mantid/api/IMDEventWorkspace.rst +++ b/docs/source/api/python/mantid/api/IMDEventWorkspace.rst @@ -2,7 +2,7 @@ IMDEventWorkspace =================== -This a python binding to the C++ class Mantid::API::IMDEventWorkspace. +This is a Python binding to the C++ class Mantid::API::IMDEventWorkspace. *bases:* :py:obj:`mantid.api.IMDWorkspace`, :py:obj:`mantid.api.MultipleExperimentInfos` diff --git a/docs/source/api/python/mantid/api/IMDHistoWorkspace.rst b/docs/source/api/python/mantid/api/IMDHistoWorkspace.rst index b589834d3efc58c3ce645846646f836bc374dad4..e5112da098f57b11ba3dda6aa913775e5c627516 100644 --- a/docs/source/api/python/mantid/api/IMDHistoWorkspace.rst +++ b/docs/source/api/python/mantid/api/IMDHistoWorkspace.rst @@ -2,7 +2,7 @@ IMDHistoWorkspace =================== -This a python binding to the C++ class Mantid::API::IMDHistoWorkspace. +This is a Python binding to the C++ class Mantid::API::IMDHistoWorkspace. *bases:* :py:obj:`mantid.api.IMDWorkspace`, :py:obj:`mantid.api.MultipleExperimentInfos` diff --git a/docs/source/api/python/mantid/api/IMDHistoWorkspaceProperty.rst b/docs/source/api/python/mantid/api/IMDHistoWorkspaceProperty.rst index af712d99eab11801067ccf6216952529ea072b30..06a1da7444e8726a88229c7156433fbd93138688 100644 --- a/docs/source/api/python/mantid/api/IMDHistoWorkspaceProperty.rst +++ b/docs/source/api/python/mantid/api/IMDHistoWorkspaceProperty.rst @@ -2,7 +2,7 @@ IMDHistoWorkspaceProperty =========================== -This a python binding to the C++ class Mantid::API::WorkspaceProperty. +This is a Python binding to the C++ class Mantid::API::WorkspaceProperty. *bases:* :py:obj:`mantid.api.IMDHistoWorkspacePropertyPropertyWithValue`, :py:obj:`mantid.api.IWorkspaceProperty` diff --git a/docs/source/api/python/mantid/api/IMDHistoWorkspacePropertyPropertyWithValue.rst b/docs/source/api/python/mantid/api/IMDHistoWorkspacePropertyPropertyWithValue.rst index 286f12824739f4447052d8d8c4ef746d89a45ea7..80d831ea764faf7f0c035efbc813fa70f2546900 100644 --- a/docs/source/api/python/mantid/api/IMDHistoWorkspacePropertyPropertyWithValue.rst +++ b/docs/source/api/python/mantid/api/IMDHistoWorkspacePropertyPropertyWithValue.rst @@ -2,7 +2,7 @@ IMDHistoWorkspacePropertyPropertyWithValue ============================================ -This a python binding to the C++ class Mantid::API::IMDHistoWorkspacePropertyPropertyWithValue. +This is a Python binding to the C++ class Mantid::API::IMDHistoWorkspacePropertyPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/api/IMDWorkspace.rst b/docs/source/api/python/mantid/api/IMDWorkspace.rst index 1653fba63ab6caab9e658f54546bc1e17ed97041..5fa073fd5d583b8d64b3243999f204f9be0e6872 100644 --- a/docs/source/api/python/mantid/api/IMDWorkspace.rst +++ b/docs/source/api/python/mantid/api/IMDWorkspace.rst @@ -2,7 +2,7 @@ IMDWorkspace ============== -This a python binding to the C++ class Mantid::API::IMDWorkspace. +This is a Python binding to the C++ class Mantid::API::IMDWorkspace. *bases:* :py:obj:`mantid.api.Workspace`, :py:obj:`mantid.api.MDGeometry` diff --git a/docs/source/api/python/mantid/api/IPeak.rst b/docs/source/api/python/mantid/api/IPeak.rst index cce9eb07ff954d807eb70d90fda51f49cdd25041..a21314871654c6e6c9c3d4e51fd65c4b4a8506d6 100644 --- a/docs/source/api/python/mantid/api/IPeak.rst +++ b/docs/source/api/python/mantid/api/IPeak.rst @@ -2,7 +2,7 @@ IPeak ======= -This a python binding to the C++ class Mantid::API::IPeak. +This is a Python binding to the C++ class Mantid::API::IPeak. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/IPeakFunction.rst b/docs/source/api/python/mantid/api/IPeakFunction.rst index 86fc2f37384ddd5a629e40ce126c9f44b4bdb2fb..cea35fedbe6afad189f8010c47537128aa04e1b2 100644 --- a/docs/source/api/python/mantid/api/IPeakFunction.rst +++ b/docs/source/api/python/mantid/api/IPeakFunction.rst @@ -2,7 +2,7 @@ IPeakFunction =============== -This a python binding to the C++ class Mantid::API::IPeakFunction. +This is a Python binding to the C++ class Mantid::API::IPeakFunction. *bases:* :py:obj:`mantid.api.IFunction1D` diff --git a/docs/source/api/python/mantid/api/IPeaksWorkspace.rst b/docs/source/api/python/mantid/api/IPeaksWorkspace.rst index e4c11234f0f431e6535f921ec088256f2ba77c5d..eb28217de03f33e9abaadd81750e2b60a8184cd9 100644 --- a/docs/source/api/python/mantid/api/IPeaksWorkspace.rst +++ b/docs/source/api/python/mantid/api/IPeaksWorkspace.rst @@ -2,7 +2,7 @@ IPeaksWorkspace ================= -This a python binding to the C++ class Mantid::API::IPeaksWorkspace. +This is a Python binding to the C++ class Mantid::API::IPeaksWorkspace. *bases:* :py:obj:`mantid.api.ITableWorkspace`, :py:obj:`mantid.api.ExperimentInfo` diff --git a/docs/source/api/python/mantid/api/ISpectrum.rst b/docs/source/api/python/mantid/api/ISpectrum.rst index 21dd596ef30bdba970917a3c03a453e555bf6148..c8a5911f25c42bd20128c568cc92c34ec84d6b8d 100644 --- a/docs/source/api/python/mantid/api/ISpectrum.rst +++ b/docs/source/api/python/mantid/api/ISpectrum.rst @@ -2,7 +2,7 @@ ISpectrum =========== -This a python binding to the C++ class Mantid::API::ISpectrum. +This is a Python binding to the C++ class Mantid::API::ISpectrum. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/ITableWorkspace.rst b/docs/source/api/python/mantid/api/ITableWorkspace.rst index bca08c77dd19bc8547fc490830f64c7fd9b2c6d1..2065d1496957aa5c26461c86f9f2a229c51aee15 100644 --- a/docs/source/api/python/mantid/api/ITableWorkspace.rst +++ b/docs/source/api/python/mantid/api/ITableWorkspace.rst @@ -2,7 +2,7 @@ ITableWorkspace ================= -This a python binding to the C++ class Mantid::API::ITableWorkspace. +This is a Python binding to the C++ class Mantid::API::ITableWorkspace. *bases:* :py:obj:`mantid.api.Workspace` diff --git a/docs/source/api/python/mantid/api/ITableWorkspaceProperty.rst b/docs/source/api/python/mantid/api/ITableWorkspaceProperty.rst index 4748196ccd15d1b336cd6f9d454cb1d8bf1a94ce..74140b09917318cae1e8afaa1481458d57338fe7 100644 --- a/docs/source/api/python/mantid/api/ITableWorkspaceProperty.rst +++ b/docs/source/api/python/mantid/api/ITableWorkspaceProperty.rst @@ -2,7 +2,7 @@ ITableWorkspaceProperty ========================= -This a python binding to the C++ class Mantid::API::WorkspaceProperty. +This is a Python binding to the C++ class Mantid::API::WorkspaceProperty. *bases:* :py:obj:`mantid.api.ITableWorkspacePropertyPropertyWithValue`, :py:obj:`mantid.api.IWorkspaceProperty` diff --git a/docs/source/api/python/mantid/api/ITableWorkspacePropertyPropertyWithValue.rst b/docs/source/api/python/mantid/api/ITableWorkspacePropertyPropertyWithValue.rst index e7e46c06228b5e9daa64a24a8606a4c722e3fdf9..b3e78603f85030d22152cd3a0ab3081111bf3a41 100644 --- a/docs/source/api/python/mantid/api/ITableWorkspacePropertyPropertyWithValue.rst +++ b/docs/source/api/python/mantid/api/ITableWorkspacePropertyPropertyWithValue.rst @@ -2,7 +2,7 @@ ITableWorkspacePropertyPropertyWithValue ========================================== -This a python binding to the C++ class Mantid::API::ITableWorkspacePropertyPropertyWithValue. +This is a Python binding to the C++ class Mantid::API::ITableWorkspacePropertyPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/api/IWorkspaceProperty.rst b/docs/source/api/python/mantid/api/IWorkspaceProperty.rst index b34f7f9cce0fe3e29b48231d3717d243b83f0e5b..317987b6fbd9883eb02fda95d3577a02c1a60df7 100644 --- a/docs/source/api/python/mantid/api/IWorkspaceProperty.rst +++ b/docs/source/api/python/mantid/api/IWorkspaceProperty.rst @@ -2,7 +2,7 @@ IWorkspaceProperty ==================== -This a python binding to the C++ class Mantid::API::WorkspaceProperty. +This is a Python binding to the C++ class Mantid::API::WorkspaceProperty. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/InstrumentValidator.rst b/docs/source/api/python/mantid/api/InstrumentValidator.rst index b4d5b21411d0a7f45706bcb5e9063bd83a07b87e..dea355bba6a95b91d017b668b0e6e9d96b055616 100644 --- a/docs/source/api/python/mantid/api/InstrumentValidator.rst +++ b/docs/source/api/python/mantid/api/InstrumentValidator.rst @@ -2,7 +2,7 @@ InstrumentValidator ===================== -This a python binding to the C++ class Mantid::API::InstrumentValidator. +This is a Python binding to the C++ class Mantid::API::InstrumentValidator. *bases:* :py:obj:`mantid.api.ExperimentInfoValidator` diff --git a/docs/source/api/python/mantid/api/Jacobian.rst b/docs/source/api/python/mantid/api/Jacobian.rst index 67e0ec7f159a3af6fe457fcee814d402a426efa0..afbc351460c2db34c6622c7fabea50940726df50 100644 --- a/docs/source/api/python/mantid/api/Jacobian.rst +++ b/docs/source/api/python/mantid/api/Jacobian.rst @@ -2,7 +2,7 @@ Jacobian ========== -This a python binding to the C++ class Mantid::API::Jacobian. +This is a Python binding to the C++ class Mantid::API::Jacobian. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/LockMode.rst b/docs/source/api/python/mantid/api/LockMode.rst index 197b5af819a265bb6ecc3b64947e8c4e00d97b2e..356acdf37cb2519c82d9a15901a6e846a27bdfbc 100644 --- a/docs/source/api/python/mantid/api/LockMode.rst +++ b/docs/source/api/python/mantid/api/LockMode.rst @@ -2,7 +2,7 @@ LockMode ========== -This a python binding to the C++ class Mantid::API::LockMode. +This is a Python binding to the C++ class Mantid::API::LockMode. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/MDGeometry.rst b/docs/source/api/python/mantid/api/MDGeometry.rst index 8238cd4dcad50cf57c16d9226cb4b65344ff055f..c3b6c292364496a33158a904ce0a3cf77e2b2198 100644 --- a/docs/source/api/python/mantid/api/MDGeometry.rst +++ b/docs/source/api/python/mantid/api/MDGeometry.rst @@ -2,7 +2,7 @@ MDGeometry ============ -This a python binding to the C++ class Mantid::API::MDGeometry. +This is a Python binding to the C++ class Mantid::API::MDGeometry. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/MDNormalization.rst b/docs/source/api/python/mantid/api/MDNormalization.rst index fc26778a3fb3727e9fa037da34472b01d4883de6..43005deb9a34821a3a43fab296790c531d493cb0 100644 --- a/docs/source/api/python/mantid/api/MDNormalization.rst +++ b/docs/source/api/python/mantid/api/MDNormalization.rst @@ -2,7 +2,7 @@ MDNormalization ================= -This a python binding to the C++ class Mantid::API::MDNormalization. +This is a Python binding to the C++ class Mantid::API::MDNormalization. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/MantidAxis.rst b/docs/source/api/python/mantid/api/MantidAxis.rst index 1adff1a30e9fc4515ac31b4b5e77fa1a3718ba28..a55e64197e07fa4086306856943dac982a7978ab 100644 --- a/docs/source/api/python/mantid/api/MantidAxis.rst +++ b/docs/source/api/python/mantid/api/MantidAxis.rst @@ -2,7 +2,7 @@ MantidAxis ============ -This a python binding to the C++ class Mantid::API::MantidAxis. +This is a Python binding to the C++ class Mantid::API::MantidAxis. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/MatrixWorkspace.rst b/docs/source/api/python/mantid/api/MatrixWorkspace.rst index e83b1e1f45383be1fb0a119f4e0a32afce9efad4..2e29a3e6e89296c7f3c9bb4b1639d3fa5d92fce4 100644 --- a/docs/source/api/python/mantid/api/MatrixWorkspace.rst +++ b/docs/source/api/python/mantid/api/MatrixWorkspace.rst @@ -4,7 +4,7 @@ MatrixWorkspace ================= -This a python binding to the C++ class Mantid::API::MatrixWorkspace. +This is a Python binding to the C++ class Mantid::API::MatrixWorkspace. *bases:* :py:obj:`mantid.api.ExperimentInfo`, :py:obj:`mantid.api.IMDWorkspace` diff --git a/docs/source/api/python/mantid/api/MatrixWorkspaceProperty.rst b/docs/source/api/python/mantid/api/MatrixWorkspaceProperty.rst index 514dddd9462f1188b99e336c5c3a8cb3fdf4c807..cfac0149d6d296583c4c84362c78044c33f7334e 100644 --- a/docs/source/api/python/mantid/api/MatrixWorkspaceProperty.rst +++ b/docs/source/api/python/mantid/api/MatrixWorkspaceProperty.rst @@ -2,7 +2,7 @@ MatrixWorkspaceProperty ========================= -This a python binding to the C++ class Mantid::API::WorkspaceProperty. +This is a Python binding to the C++ class Mantid::API::WorkspaceProperty. *bases:* :py:obj:`mantid.api.MatrixWorkspacePropertyPropertyWithValue`, :py:obj:`mantid.api.IWorkspaceProperty` diff --git a/docs/source/api/python/mantid/api/MatrixWorkspacePropertyPropertyWithValue.rst b/docs/source/api/python/mantid/api/MatrixWorkspacePropertyPropertyWithValue.rst index ab515f8bad18118210d7c9eaa85dafbb7153fbe3..a2a72aa00d306aa5275680cacf16a2fa985a12ba 100644 --- a/docs/source/api/python/mantid/api/MatrixWorkspacePropertyPropertyWithValue.rst +++ b/docs/source/api/python/mantid/api/MatrixWorkspacePropertyPropertyWithValue.rst @@ -2,7 +2,7 @@ MatrixWorkspacePropertyPropertyWithValue ========================================== -This a python binding to the C++ class Mantid::API::MatrixWorkspacePropertyPropertyWithValue. +This is a Python binding to the C++ class Mantid::API::MatrixWorkspacePropertyPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/api/MatrixWorkspaceValidator.rst b/docs/source/api/python/mantid/api/MatrixWorkspaceValidator.rst index 45239daeec9af6c647ac03a8d2ef8d32bbc66719..706608f9effca7da753b4ae999afc343469238da 100644 --- a/docs/source/api/python/mantid/api/MatrixWorkspaceValidator.rst +++ b/docs/source/api/python/mantid/api/MatrixWorkspaceValidator.rst @@ -2,7 +2,7 @@ MatrixWorkspaceValidator ========================== -This a python binding to the C++ class Mantid::API::MatrixWorkspaceValidator. +This is a Python binding to the C++ class Mantid::API::MatrixWorkspaceValidator. *bases:* :py:obj:`mantid.api.MatrixWorkspaceValidator` diff --git a/docs/source/api/python/mantid/api/MultipleExperimentInfos.rst b/docs/source/api/python/mantid/api/MultipleExperimentInfos.rst index 93aac8d0f37f5ea3ce3845899bd1daf95f8da4ae..9da85b12a807600e7b296b74ab5f5e927f69dd13 100644 --- a/docs/source/api/python/mantid/api/MultipleExperimentInfos.rst +++ b/docs/source/api/python/mantid/api/MultipleExperimentInfos.rst @@ -2,7 +2,7 @@ MultipleExperimentInfos ========================= -This a python binding to the C++ class Mantid::API::MultipleExperimentInfos. +This is a Python binding to the C++ class Mantid::API::MultipleExperimentInfos. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/MultipleFileProperty.rst b/docs/source/api/python/mantid/api/MultipleFileProperty.rst index 93ce37babc72043bad69a6dc9e6f0d4d9fdb870e..f25a3682865e96f0df4fd923e6ee6f114330434c 100644 --- a/docs/source/api/python/mantid/api/MultipleFileProperty.rst +++ b/docs/source/api/python/mantid/api/MultipleFileProperty.rst @@ -2,7 +2,7 @@ MultipleFileProperty ====================== -This a python binding to the C++ class +This is a Python binding to the C++ class ``Mantid::API::MultipleFileProperty``. .. contents:: diff --git a/docs/source/api/python/mantid/api/NumericAxis.rst b/docs/source/api/python/mantid/api/NumericAxis.rst index 90a0a91fbfcf717897df19ce7c49c4540f1c87e7..953fd5622f041681a88bb6ee2ae5e98583b61821 100644 --- a/docs/source/api/python/mantid/api/NumericAxis.rst +++ b/docs/source/api/python/mantid/api/NumericAxis.rst @@ -2,7 +2,7 @@ NumericAxis ============= -This a python binding to the C++ class Mantid::API::NumericAxis. +This is a Python binding to the C++ class Mantid::API::NumericAxis. *bases:* :py:obj:`mantid.api.MantidAxis` diff --git a/docs/source/api/python/mantid/api/NumericAxisValidator.rst b/docs/source/api/python/mantid/api/NumericAxisValidator.rst index eed2b8025bcb538e904a52f02c34cb21bcf6b060..ebd0b0327c18e464d3df9169a86f0869f10d253b 100644 --- a/docs/source/api/python/mantid/api/NumericAxisValidator.rst +++ b/docs/source/api/python/mantid/api/NumericAxisValidator.rst @@ -2,7 +2,7 @@ NumericAxisValidator ====================== -This a python binding to the C++ class Mantid::API::NumericAxisValidator. +This is a Python binding to the C++ class Mantid::API::NumericAxisValidator. *bases:* :py:obj:`mantid.api.MatrixWorkspaceValidator` diff --git a/docs/source/api/python/mantid/api/Progress.rst b/docs/source/api/python/mantid/api/Progress.rst index e630b128c974e95568706b7d01c3c7b3e93e61e9..859b6394b58e9a7d7f33aa0afb6f079a912ab910 100644 --- a/docs/source/api/python/mantid/api/Progress.rst +++ b/docs/source/api/python/mantid/api/Progress.rst @@ -2,7 +2,7 @@ Progress ========== -This a python binding to the C++ class Mantid::API::Progress. +This is a Python binding to the C++ class Mantid::API::Progress. *bases:* :py:obj:`mantid.kernel.ProgressBase` diff --git a/docs/source/api/python/mantid/api/PropertyMode.rst b/docs/source/api/python/mantid/api/PropertyMode.rst index 371c7d2ba54a21a62f358d09489d6ed0662a7135..5519aebdd82962b12d7f7f153f74badc9f33a5dd 100644 --- a/docs/source/api/python/mantid/api/PropertyMode.rst +++ b/docs/source/api/python/mantid/api/PropertyMode.rst @@ -2,7 +2,7 @@ PropertyMode ============== -This a python binding to the C++ class Mantid::API::PropertyMode. +This is a Python binding to the C++ class Mantid::API::PropertyMode. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/RawCountValidator.rst b/docs/source/api/python/mantid/api/RawCountValidator.rst index 6d080bad41d4ba11bd4fece56b605da5c2443ff1..0c1e986ba0614a8b676cc34f0138a58a69d9a04b 100644 --- a/docs/source/api/python/mantid/api/RawCountValidator.rst +++ b/docs/source/api/python/mantid/api/RawCountValidator.rst @@ -2,7 +2,7 @@ RawCountValidator =================== -This a python binding to the C++ class Mantid::API::RawCountValidator. +This is a Python binding to the C++ class Mantid::API::RawCountValidator. *bases:* :py:obj:`mantid.api.MatrixWorkspaceValidator` diff --git a/docs/source/api/python/mantid/api/Run.rst b/docs/source/api/python/mantid/api/Run.rst index 0138e1103f6a25297f5978b003753878507e4934..1a51506bbff7a15d05abc1204e3b9792f35cbff3 100644 --- a/docs/source/api/python/mantid/api/Run.rst +++ b/docs/source/api/python/mantid/api/Run.rst @@ -4,7 +4,7 @@ Run ===== -This a python binding to the C++ class Mantid::API::Run. +This is a Python binding to the C++ class Mantid::API::Run. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/Sample.rst b/docs/source/api/python/mantid/api/Sample.rst index 8cb15a0ab8da3ccc366bdc60c880e812400477b4..22634d087028ac719b4c5d3e4bd2ee57f8ab278c 100644 --- a/docs/source/api/python/mantid/api/Sample.rst +++ b/docs/source/api/python/mantid/api/Sample.rst @@ -4,7 +4,7 @@ Sample ======== -This a python binding to the C++ class Mantid::API::Sample. +This is a Python binding to the C++ class Mantid::API::Sample. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/ScriptRepository.rst b/docs/source/api/python/mantid/api/ScriptRepository.rst index 4d68b47c7a282c0752db7b7b91917b8b6a66813d..ef1df92efc09c4f0ed91f025d2882ace5e63efd8 100644 --- a/docs/source/api/python/mantid/api/ScriptRepository.rst +++ b/docs/source/api/python/mantid/api/ScriptRepository.rst @@ -2,7 +2,7 @@ ScriptRepository ================== -This a python binding to the C++ class Mantid::API::ScriptRepository. +This is a Python binding to the C++ class Mantid::API::ScriptRepository. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/ScriptRepositoryFactory.rst b/docs/source/api/python/mantid/api/ScriptRepositoryFactory.rst index 6c98217f65844f83e390734ffbed344defe73e42..ff4f39df068fa8a45facfbf5b399da5240146dea 100644 --- a/docs/source/api/python/mantid/api/ScriptRepositoryFactory.rst +++ b/docs/source/api/python/mantid/api/ScriptRepositoryFactory.rst @@ -2,7 +2,7 @@ ScriptRepositoryFactory ========================= -This a python binding to the C++ class Mantid::API::ScriptRepositoryFactory. +This is a Python binding to the C++ class Mantid::API::ScriptRepositoryFactory. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/SpectraAxisValidator.rst b/docs/source/api/python/mantid/api/SpectraAxisValidator.rst index 082e9f6117adb0140169b443462dc70968bf94ba..7cf2102718696c3dcbf369d3ca1e770afd26dc51 100644 --- a/docs/source/api/python/mantid/api/SpectraAxisValidator.rst +++ b/docs/source/api/python/mantid/api/SpectraAxisValidator.rst @@ -2,7 +2,7 @@ SpectraAxisValidator ====================== -This a python binding to the C++ class Mantid::API::SpectraAxisValidator. +This is a Python binding to the C++ class Mantid::API::SpectraAxisValidator. *bases:* :py:obj:`mantid.api.MatrixWorkspaceValidator` diff --git a/docs/source/api/python/mantid/api/TextAxis.rst b/docs/source/api/python/mantid/api/TextAxis.rst index 629fee6b719ae471280f75ad1fe31a967993f188..362d35f62029b50557ad06afa5e491effeadb3e2 100644 --- a/docs/source/api/python/mantid/api/TextAxis.rst +++ b/docs/source/api/python/mantid/api/TextAxis.rst @@ -2,7 +2,7 @@ TextAxis ========== -This a python binding to the C++ class Mantid::API::TextAxis. +This is a Python binding to the C++ class Mantid::API::TextAxis. *bases:* :py:obj:`mantid.api.MantidAxis` diff --git a/docs/source/api/python/mantid/api/VectorVectorStringPropertyWithValue.rst b/docs/source/api/python/mantid/api/VectorVectorStringPropertyWithValue.rst index 8e3cc2fc8cbe0a67fa3a5b7bfda2a991f6470c0a..7dfbc084cb8e0d9015aff238c53242dd8d8f3493 100644 --- a/docs/source/api/python/mantid/api/VectorVectorStringPropertyWithValue.rst +++ b/docs/source/api/python/mantid/api/VectorVectorStringPropertyWithValue.rst @@ -2,7 +2,7 @@ VectorVectorStringPropertyWithValue ===================================== -This a python binding to the C++ class Mantid::API::VectorVectorStringPropertyWithValue. +This is a Python binding to the C++ class Mantid::API::VectorVectorStringPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/api/Workspace.rst b/docs/source/api/python/mantid/api/Workspace.rst index a552bf94f99f75d3c4a11053aadd6a9f37acea99..4e3837ad20afa7f8409cb485bd3cb77813849b42 100644 --- a/docs/source/api/python/mantid/api/Workspace.rst +++ b/docs/source/api/python/mantid/api/Workspace.rst @@ -4,7 +4,7 @@ Workspace =========== -This a python binding to the C++ class Mantid::API::Workspace. +This is a Python binding to the C++ class Mantid::API::Workspace. *bases:* :py:obj:`mantid.kernel.DataItem` diff --git a/docs/source/api/python/mantid/api/WorkspaceFactoryImpl.rst b/docs/source/api/python/mantid/api/WorkspaceFactoryImpl.rst index 3fead333df54082d5db339cf30a9bf4841a7795d..8946d7111a2c63bf5fb09a6e753ff493a734c72f 100644 --- a/docs/source/api/python/mantid/api/WorkspaceFactoryImpl.rst +++ b/docs/source/api/python/mantid/api/WorkspaceFactoryImpl.rst @@ -2,7 +2,7 @@ WorkspaceFactoryImpl ====================== -This a python binding to the C++ class Mantid::API::WorkspaceFactoryImpl. +This is a Python binding to the C++ class Mantid::API::WorkspaceFactoryImpl. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/WorkspaceGroup.rst b/docs/source/api/python/mantid/api/WorkspaceGroup.rst index 31facfec47b2402c84a741de803a1559b0419b4a..0b08e41d1baa64680b3eebb55b81d8f85e001b09 100644 --- a/docs/source/api/python/mantid/api/WorkspaceGroup.rst +++ b/docs/source/api/python/mantid/api/WorkspaceGroup.rst @@ -2,7 +2,7 @@ WorkspaceGroup ================ -This a python binding to the C++ class Mantid::API::WorkspaceGroup. +This is a Python binding to the C++ class Mantid::API::WorkspaceGroup. *bases:* :py:obj:`mantid.api.Workspace` diff --git a/docs/source/api/python/mantid/api/WorkspaceGroupProperty.rst b/docs/source/api/python/mantid/api/WorkspaceGroupProperty.rst index e5147a2ea08c633bb9d4084d3bd12dded8c422b6..6aeb719c24ecf50024993e1381459ae87d06e7af 100644 --- a/docs/source/api/python/mantid/api/WorkspaceGroupProperty.rst +++ b/docs/source/api/python/mantid/api/WorkspaceGroupProperty.rst @@ -2,7 +2,7 @@ WorkspaceGroupProperty ======================== -This a python binding to the C++ class Mantid::API::WorkspaceGroupProperty. +This is a Python binding to the C++ class Mantid::API::WorkspaceGroupProperty. *bases:* :py:obj:`mantid.api.WorkspaceGroupPropertyPropertyWithValue`, :py:obj:`mantid.api.IWorkspaceProperty` diff --git a/docs/source/api/python/mantid/api/WorkspaceGroupPropertyPropertyWithValue.rst b/docs/source/api/python/mantid/api/WorkspaceGroupPropertyPropertyWithValue.rst index 8e32a69b8f73615cdf8ca2308b65a6c614c32243..2ace041a204a0f9ebb0029dcb3d91cc7b0438ffa 100644 --- a/docs/source/api/python/mantid/api/WorkspaceGroupPropertyPropertyWithValue.rst +++ b/docs/source/api/python/mantid/api/WorkspaceGroupPropertyPropertyWithValue.rst @@ -2,7 +2,7 @@ WorkspaceGroupPropertyPropertyWithValue ========================================= -This a python binding to the C++ class Mantid::API::WorkspaceGroupPropertyPropertyWithValue. +This is a Python binding to the C++ class Mantid::API::WorkspaceGroupPropertyPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/api/WorkspaceHistory.rst b/docs/source/api/python/mantid/api/WorkspaceHistory.rst index 8ea4232b7109e66f834e53f378c62f043c9c7a8a..87bc424a0e66359283bcdaa0b77df3e9b22d5e71 100644 --- a/docs/source/api/python/mantid/api/WorkspaceHistory.rst +++ b/docs/source/api/python/mantid/api/WorkspaceHistory.rst @@ -2,7 +2,7 @@ WorkspaceHistory ================== -This a python binding to the C++ class Mantid::API::WorkspaceHistory. +This is a Python binding to the C++ class Mantid::API::WorkspaceHistory. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/api/WorkspaceProperty.rst b/docs/source/api/python/mantid/api/WorkspaceProperty.rst index 1dcf407a801c4c72342ed97daae1b9a7e2ec3c5c..373ba96e882bad30869f870ff5a94f2215568330 100644 --- a/docs/source/api/python/mantid/api/WorkspaceProperty.rst +++ b/docs/source/api/python/mantid/api/WorkspaceProperty.rst @@ -2,7 +2,7 @@ WorkspaceProperty =================== -This a python binding to the C++ class Mantid::API::WorkspaceProperty. +This is a Python binding to the C++ class Mantid::API::WorkspaceProperty. *bases:* :py:obj:`mantid.api.WorkspacePropertyPropertyWithValue`, :py:obj:`mantid.api.IWorkspaceProperty` diff --git a/docs/source/api/python/mantid/api/WorkspacePropertyPropertyWithValue.rst b/docs/source/api/python/mantid/api/WorkspacePropertyPropertyWithValue.rst index b36b2fb8c174154ae23df46fc8641c7e06722b2a..e7f7fba109690039b7fd5bd116ab1a06efa3aea1 100644 --- a/docs/source/api/python/mantid/api/WorkspacePropertyPropertyWithValue.rst +++ b/docs/source/api/python/mantid/api/WorkspacePropertyPropertyWithValue.rst @@ -2,7 +2,7 @@ WorkspacePropertyPropertyWithValue ==================================== -This a python binding to the C++ class Mantid::API::WorkspacePropertyPropertyWithValue. +This is a Python binding to the C++ class Mantid::API::WorkspacePropertyPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/api/WorkspaceUnitValidator.rst b/docs/source/api/python/mantid/api/WorkspaceUnitValidator.rst index f28b607912457c28bc61a0db1ff5e46618983579..28482b04af36d7f01bb46b209d8b60ee3f002074 100644 --- a/docs/source/api/python/mantid/api/WorkspaceUnitValidator.rst +++ b/docs/source/api/python/mantid/api/WorkspaceUnitValidator.rst @@ -2,7 +2,7 @@ WorkspaceUnitValidator ======================== -This a python binding to the C++ class Mantid::API::WorkspaceUnitValidator. +This is a Python binding to the C++ class Mantid::API::WorkspaceUnitValidator. *bases:* :py:obj:`mantid.api.MatrixWorkspaceValidator` diff --git a/docs/source/api/python/mantid/geometry/AngleUnits.rst b/docs/source/api/python/mantid/geometry/AngleUnits.rst index 6949caa44ff3d7048433b3c25b10fce55b5ba583..409051953ff0506f9ca89e5e5992550f744975c4 100644 --- a/docs/source/api/python/mantid/geometry/AngleUnits.rst +++ b/docs/source/api/python/mantid/geometry/AngleUnits.rst @@ -2,7 +2,7 @@ AngleUnits ============ -This a python binding to the C++ class Mantid::Geometry::AngleUnits. +This is a Python binding to the C++ class Mantid::Geometry::AngleUnits. .. module:`mantid.geometry` diff --git a/docs/source/api/python/mantid/geometry/BoundingBox.rst b/docs/source/api/python/mantid/geometry/BoundingBox.rst index 9544a95cf83f687929064af43d2b4453359b8db1..1d1077a1f8235f03ba5ad5f70b0a4dc538ae273a 100644 --- a/docs/source/api/python/mantid/geometry/BoundingBox.rst +++ b/docs/source/api/python/mantid/geometry/BoundingBox.rst @@ -2,7 +2,7 @@ BoundingBox ============= -This a python binding to the C++ class Mantid::Geometry::BoundingBox. +This is a Python binding to the C++ class Mantid::Geometry::BoundingBox. .. module:`mantid.geometry` diff --git a/docs/source/api/python/mantid/geometry/CompAssembly.rst b/docs/source/api/python/mantid/geometry/CompAssembly.rst index b19798bf25b57ee941548a934c73656b0e708f65..5ac09f0895862ed27bc7e446870393c7479246d4 100644 --- a/docs/source/api/python/mantid/geometry/CompAssembly.rst +++ b/docs/source/api/python/mantid/geometry/CompAssembly.rst @@ -2,7 +2,7 @@ CompAssembly ============== -This a python binding to the C++ class Mantid::Geometry::CompAssembly. +This is a Python binding to the C++ class Mantid::Geometry::CompAssembly. *bases:* :py:obj:`mantid.geometry.ICompAssembly`, :py:obj:`mantid.geometry.Component` diff --git a/docs/source/api/python/mantid/geometry/Component.rst b/docs/source/api/python/mantid/geometry/Component.rst index d4d7bde5c2c15c3186c6c4ae681f3b2bb6817420..f977b6ab52db90cb9d27bc6a2c2940338eaf946f 100644 --- a/docs/source/api/python/mantid/geometry/Component.rst +++ b/docs/source/api/python/mantid/geometry/Component.rst @@ -2,7 +2,7 @@ Component =========== -This a python binding to the C++ class Mantid::Geometry::Component. +This is a Python binding to the C++ class Mantid::Geometry::Component. *bases:* :py:obj:`mantid.geometry.IComponent` diff --git a/docs/source/api/python/mantid/geometry/Detector.rst b/docs/source/api/python/mantid/geometry/Detector.rst index 69d0a0e4d92465a6aff0a62b24df7334c9eb29cd..c657b1eb586b8f9bad43e9558157e05c85e60133 100644 --- a/docs/source/api/python/mantid/geometry/Detector.rst +++ b/docs/source/api/python/mantid/geometry/Detector.rst @@ -2,7 +2,7 @@ Detector ========== -This a python binding to the C++ class Mantid::Geometry::Detector. +This is a Python binding to the C++ class Mantid::Geometry::Detector. *bases:* :py:obj:`mantid.geometry.IDetector`, :py:obj:`mantid.geometry.ObjComponent` diff --git a/docs/source/api/python/mantid/geometry/DetectorGroup.rst b/docs/source/api/python/mantid/geometry/DetectorGroup.rst index a120a43a2f45641b4cc87c20633c2c0531bfe962..b8bb0a44af6edebe613f23b878728677e7a0af66 100644 --- a/docs/source/api/python/mantid/geometry/DetectorGroup.rst +++ b/docs/source/api/python/mantid/geometry/DetectorGroup.rst @@ -2,7 +2,7 @@ DetectorGroup =============== -This a python binding to the C++ class Mantid::Geometry::DetectorGroup. +This is a Python binding to the C++ class Mantid::Geometry::DetectorGroup. *bases:* :py:obj:`mantid.geometry.IDetector` diff --git a/docs/source/api/python/mantid/geometry/Goniometer.rst b/docs/source/api/python/mantid/geometry/Goniometer.rst index d55576e061682b69a76a587fe683bafcc9efdd3f..b98a0f9708b911aa7cd7f64653083b4925e18b80 100644 --- a/docs/source/api/python/mantid/geometry/Goniometer.rst +++ b/docs/source/api/python/mantid/geometry/Goniometer.rst @@ -2,7 +2,7 @@ Goniometer ============ -This a python binding to the C++ class Mantid::Geometry::Goniometer. +This is a Python binding to the C++ class Mantid::Geometry::Goniometer. .. module:`mantid.geometry` diff --git a/docs/source/api/python/mantid/geometry/GridDetectorPixel.rst b/docs/source/api/python/mantid/geometry/GridDetectorPixel.rst index 4ce4a04bca1e522e778f1d8e2fffb1b152d67e8d..a9f278412d030191b8f9f67ce1fb752245e39785 100644 --- a/docs/source/api/python/mantid/geometry/GridDetectorPixel.rst +++ b/docs/source/api/python/mantid/geometry/GridDetectorPixel.rst @@ -2,7 +2,7 @@ GridDetectorPixel ========================== -This a python binding to the C++ class Mantid::Geometry::GridDetectorPixel. +This is a Python binding to the C++ class Mantid::Geometry::GridDetectorPixel. *bases:* :py:obj:`mantid.geometry.Detector` diff --git a/docs/source/api/python/mantid/geometry/ICompAssembly.rst b/docs/source/api/python/mantid/geometry/ICompAssembly.rst index 66d325a35dfb093f616ef8ee61cf7e43b3cbf96a..7ca2fe1ed122a0db0c32192d406d97754c559d5e 100644 --- a/docs/source/api/python/mantid/geometry/ICompAssembly.rst +++ b/docs/source/api/python/mantid/geometry/ICompAssembly.rst @@ -2,7 +2,7 @@ ICompAssembly =============== -This a python binding to the C++ class Mantid::Geometry::ICompAssembly. +This is a Python binding to the C++ class Mantid::Geometry::ICompAssembly. *bases:* :py:obj:`mantid.geometry.IComponent` diff --git a/docs/source/api/python/mantid/geometry/IComponent.rst b/docs/source/api/python/mantid/geometry/IComponent.rst index 4a56098196499b3a92827003700d1986f15e615d..19b449c4a947968dd7d50bb66bcaed5a57a93e34 100644 --- a/docs/source/api/python/mantid/geometry/IComponent.rst +++ b/docs/source/api/python/mantid/geometry/IComponent.rst @@ -2,7 +2,7 @@ IComponent ============ -This a python binding to the C++ class Mantid::Geometry::IComponent. +This is a Python binding to the C++ class Mantid::Geometry::IComponent. .. module:`mantid.geometry` diff --git a/docs/source/api/python/mantid/geometry/IDetector.rst b/docs/source/api/python/mantid/geometry/IDetector.rst index dab8f82377b4be0ffdb6abe7994eb74712c48c30..c1db0e2487a95e7eaa3c87789e711076643d8763 100644 --- a/docs/source/api/python/mantid/geometry/IDetector.rst +++ b/docs/source/api/python/mantid/geometry/IDetector.rst @@ -2,7 +2,7 @@ IDetector =========== -This a python binding to the C++ class Mantid::Geometry::IDetector. +This is a Python binding to the C++ class Mantid::Geometry::IDetector. *bases:* :py:obj:`mantid.geometry.IObjComponent` diff --git a/docs/source/api/python/mantid/geometry/IMDDimension.rst b/docs/source/api/python/mantid/geometry/IMDDimension.rst index 0dec8ddc6369c167dcd4e54b93c418f39befcd9c..168696c3ce5f938c6fd7b9d6b7e405a9d7debc5a 100644 --- a/docs/source/api/python/mantid/geometry/IMDDimension.rst +++ b/docs/source/api/python/mantid/geometry/IMDDimension.rst @@ -2,7 +2,7 @@ IMDDimension ============== -This a python binding to the C++ class Mantid::Geometry::IMDDimension. +This is a Python binding to the C++ class Mantid::Geometry::IMDDimension. .. module:`mantid.geometry` diff --git a/docs/source/api/python/mantid/geometry/IObjCompAssembly.rst b/docs/source/api/python/mantid/geometry/IObjCompAssembly.rst index fd0ac08f857e905dcb9a84f5c12e65696f6cc33a..d052f660c768bd3bf1ddfd9e6b4706c9d2b94944 100644 --- a/docs/source/api/python/mantid/geometry/IObjCompAssembly.rst +++ b/docs/source/api/python/mantid/geometry/IObjCompAssembly.rst @@ -2,7 +2,7 @@ IObjCompAssembly ================== -This a python binding to the C++ class Mantid::Geometry::ObjCompAssembly. +This is a Python binding to the C++ class Mantid::Geometry::ObjCompAssembly. *bases:* :py:obj:`mantid.geometry.ICompAssembly`, :py:obj:`mantid.geometry.ObjComponent` diff --git a/docs/source/api/python/mantid/geometry/IObjComponent.rst b/docs/source/api/python/mantid/geometry/IObjComponent.rst index 35152e206e46c617394ae8acfb57b8a35d7c2700..1ed300ff7a7e79f38aa3168a07ee9b443745ed8f 100644 --- a/docs/source/api/python/mantid/geometry/IObjComponent.rst +++ b/docs/source/api/python/mantid/geometry/IObjComponent.rst @@ -2,7 +2,7 @@ IObjComponent =============== -This a python binding to the C++ class Mantid::Geometry::IObjComponent. +This is a Python binding to the C++ class Mantid::Geometry::IObjComponent. *bases:* :py:obj:`mantid.geometry.IComponent` diff --git a/docs/source/api/python/mantid/geometry/IObject.rst b/docs/source/api/python/mantid/geometry/IObject.rst index 1b629868cf0345b9627fef7d794f3c28d06f9415..e2f503ce0e765db5cdb8ada8cd3f956a7316def1 100644 --- a/docs/source/api/python/mantid/geometry/IObject.rst +++ b/docs/source/api/python/mantid/geometry/IObject.rst @@ -2,7 +2,7 @@ Object ======== -This a python binding to the C++ class Mantid::Geometry::Object. +This is a Python binding to the C++ class Mantid::Geometry::Object. .. module:`mantid.geometry` diff --git a/docs/source/api/python/mantid/geometry/Instrument.rst b/docs/source/api/python/mantid/geometry/Instrument.rst index 281c87a04dd33f162af989413f4417c551ae4b82..098f95811fd82dfe9bcac0b23f4e3119b52c01c6 100644 --- a/docs/source/api/python/mantid/geometry/Instrument.rst +++ b/docs/source/api/python/mantid/geometry/Instrument.rst @@ -2,7 +2,7 @@ Instrument ============ -This a python binding to the C++ class Mantid::Geometry::Instrument. +This is a Python binding to the C++ class Mantid::Geometry::Instrument. *bases:* :py:obj:`mantid.geometry.CompAssembly` diff --git a/docs/source/api/python/mantid/geometry/ObjComponent.rst b/docs/source/api/python/mantid/geometry/ObjComponent.rst index 2f4bd77d470bd443d1796583ad848adb5aca9c73..138547515ebc9d169bb89bbcc7241f240788b355 100644 --- a/docs/source/api/python/mantid/geometry/ObjComponent.rst +++ b/docs/source/api/python/mantid/geometry/ObjComponent.rst @@ -2,7 +2,7 @@ ObjComponent ============== -This a python binding to the C++ class Mantid::Geometry::ObjComponent. +This is a Python binding to the C++ class Mantid::Geometry::ObjComponent. *bases:* :py:obj:`mantid.geometry.IObjComponent`, :py:obj:`mantid.geometry.Component` diff --git a/docs/source/api/python/mantid/geometry/OrientedLattice.rst b/docs/source/api/python/mantid/geometry/OrientedLattice.rst index 66b7c2aa33152f5801f486f9a218f74e42cff3b5..29befe3211427fac8deba0466606421854befdcb 100644 --- a/docs/source/api/python/mantid/geometry/OrientedLattice.rst +++ b/docs/source/api/python/mantid/geometry/OrientedLattice.rst @@ -2,7 +2,7 @@ OrientedLattice ================= -This a python binding to the C++ class Mantid::Geometry::OrientedLattice. The +This is a Python binding to the C++ class Mantid::Geometry::OrientedLattice. The methods on this class follow naming conventions for parameters as defined in the `International Tables for Crystallography <http://it.iucr.org/Ba/ch1o1v0001/>`__. See also the diff --git a/docs/source/api/python/mantid/geometry/PointingAlong.rst b/docs/source/api/python/mantid/geometry/PointingAlong.rst index 4c0d1022881da73583c17b16fac52f1100e7be11..6e88ff305338ede298124756f81976deee2f93c4 100644 --- a/docs/source/api/python/mantid/geometry/PointingAlong.rst +++ b/docs/source/api/python/mantid/geometry/PointingAlong.rst @@ -2,7 +2,7 @@ PointingAlong =============== -This a python binding to the C++ class Mantid::Geometry::PointingAlong. +This is a Python binding to the C++ class Mantid::Geometry::PointingAlong. .. module:`mantid.geometry` diff --git a/docs/source/api/python/mantid/geometry/RectangularDetector.rst b/docs/source/api/python/mantid/geometry/RectangularDetector.rst index c52ef9a2bc2e8175a34a534cae03df028b6351fe..15417c9cea73057f8b7303dd0d3bb6517adfedbf 100644 --- a/docs/source/api/python/mantid/geometry/RectangularDetector.rst +++ b/docs/source/api/python/mantid/geometry/RectangularDetector.rst @@ -2,7 +2,7 @@ RectangularDetector ===================== -This a python binding to the C++ class Mantid::Geometry::RectangularDetector. +This is a Python binding to the C++ class Mantid::Geometry::RectangularDetector. *bases:* :py:obj:`mantid.geometry.CompAssembly`, :py:obj:`mantid.geometry.IObjComponent` diff --git a/docs/source/api/python/mantid/geometry/ReferenceFrame.rst b/docs/source/api/python/mantid/geometry/ReferenceFrame.rst index 84bd97e43113e82058523f64eb83aef0d8514c56..23383e2037e7c6e5ef21c964790a087e77613d8c 100644 --- a/docs/source/api/python/mantid/geometry/ReferenceFrame.rst +++ b/docs/source/api/python/mantid/geometry/ReferenceFrame.rst @@ -2,7 +2,7 @@ ReferenceFrame ================ -This a python binding to the C++ class Mantid::Geometry::ReferenceFrame. +This is a Python binding to the C++ class Mantid::Geometry::ReferenceFrame. .. module:`mantid.geometry` diff --git a/docs/source/api/python/mantid/geometry/UnitCell.rst b/docs/source/api/python/mantid/geometry/UnitCell.rst index c9898926e8a0caefff500d182c7e4a92e262c35b..e0461011a4015896d8045e19cf8cdb6246e0e5f7 100644 --- a/docs/source/api/python/mantid/geometry/UnitCell.rst +++ b/docs/source/api/python/mantid/geometry/UnitCell.rst @@ -2,7 +2,7 @@ UnitCell ========== -This a python binding to the C++ class Mantid::Geometry::UnitCell. The methods +This is a Python binding to the C++ class Mantid::Geometry::UnitCell. The methods on this class follow naming conventions for parameters as defined in the `International Tables for Crystallography <http://it.iucr.org/Ba/ch1o1v0001/>`__. diff --git a/docs/source/api/python/mantid/kernel/Atom.rst b/docs/source/api/python/mantid/kernel/Atom.rst index 584bb09236c54dd7c815ec33a387d3385b17e7da..848890371b69db11398ed56eb55fe2d1b7e2765f 100644 --- a/docs/source/api/python/mantid/kernel/Atom.rst +++ b/docs/source/api/python/mantid/kernel/Atom.rst @@ -2,7 +2,7 @@ Atom ====== -This a python binding to the C++ class Mantid::PhysicalConstants::Atom. +This is a Python binding to the C++ class Mantid::PhysicalConstants::Atom. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/BoolFilteredTimeSeriesProperty.rst b/docs/source/api/python/mantid/kernel/BoolFilteredTimeSeriesProperty.rst index 6ba2aebeefc821bb3b521a5529062d918792274c..3a7167ac1aa051fe6191997b7155fddbef6857ee 100644 --- a/docs/source/api/python/mantid/kernel/BoolFilteredTimeSeriesProperty.rst +++ b/docs/source/api/python/mantid/kernel/BoolFilteredTimeSeriesProperty.rst @@ -2,7 +2,7 @@ BoolFilteredTimeSeriesProperty ================================ -This a python binding to the C++ class Mantid::Kernel::FilteredTimeSeriesProperty. +This is a Python binding to the C++ class Mantid::Kernel::FilteredTimeSeriesProperty. *bases:* :py:obj:`mantid.kernel.BoolTimeSeriesProperty` diff --git a/docs/source/api/python/mantid/kernel/BoolPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/BoolPropertyWithValue.rst index d80ae132ffaa3200d32473e5cea7ef115abaddf8..a41599fb68be22398546776cf190fd4d96f0b753 100644 --- a/docs/source/api/python/mantid/kernel/BoolPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/BoolPropertyWithValue.rst @@ -2,7 +2,7 @@ BoolPropertyWithValue ======================= -This a python binding to the C++ class Mantid::Kernel::BoolPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::BoolPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/BoolTimeSeriesProperty.rst b/docs/source/api/python/mantid/kernel/BoolTimeSeriesProperty.rst index b9e621e50e2d484091dc93cb70538941eb5da0fe..6ddc20468fc74eb957ca688f696164f4a1974de5 100644 --- a/docs/source/api/python/mantid/kernel/BoolTimeSeriesProperty.rst +++ b/docs/source/api/python/mantid/kernel/BoolTimeSeriesProperty.rst @@ -2,7 +2,7 @@ BoolTimeSeriesProperty ======================== -This a python binding to the C++ class Mantid::Kernel::TimeSeriesProperty. +This is a Python binding to the C++ class Mantid::Kernel::TimeSeriesProperty. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/CIntArrayProperty.rst b/docs/source/api/python/mantid/kernel/CIntArrayProperty.rst index 46e22bbdd0cbc1be2de746e28d8fbb4fc19ec8fe..d9ad65ca8dc3846778e9aec1311f006e26852ee6 100644 --- a/docs/source/api/python/mantid/kernel/CIntArrayProperty.rst +++ b/docs/source/api/python/mantid/kernel/CIntArrayProperty.rst @@ -2,7 +2,7 @@ CIntArrayProperty =================== -This a python binding to the C++ class Mantid::Kernel::ArrayProperty. +This is a Python binding to the C++ class Mantid::Kernel::ArrayProperty. *bases:* :py:obj:`mantid.kernel.VectorIntPropertyWithValue` diff --git a/docs/source/api/python/mantid/kernel/CompositeValidator.rst b/docs/source/api/python/mantid/kernel/CompositeValidator.rst index 2e930f463e0e14dbe5143bb9b2e421b6f369ae62..a3d95a0d646d241f213f81a2df6536b76e3b60fa 100644 --- a/docs/source/api/python/mantid/kernel/CompositeValidator.rst +++ b/docs/source/api/python/mantid/kernel/CompositeValidator.rst @@ -2,7 +2,7 @@ CompositeValidator ==================== -This a python binding to the C++ class Mantid::Kernel::CompositeValidator. +This is a Python binding to the C++ class Mantid::Kernel::CompositeValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/ConfigObserver.rst b/docs/source/api/python/mantid/kernel/ConfigObserver.rst index 448015702e389fbbc64bb22024c738279ef64dd6..f5c6604dbb9e74ff34e110d5b000f4cef4e5327d 100644 --- a/docs/source/api/python/mantid/kernel/ConfigObserver.rst +++ b/docs/source/api/python/mantid/kernel/ConfigObserver.rst @@ -2,7 +2,7 @@ ConfigObserver =================== -This a python binding to the C++ class Mantid::Kernel::ConfigObserver. +This is a Python binding to the C++ class Mantid::Kernel::ConfigObserver. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/ConfigPropertyObserver.rst b/docs/source/api/python/mantid/kernel/ConfigPropertyObserver.rst index 993d60da58ad3b45901a9b880ee9e2114b69881f..e0b2478aac8c421af27048d75f4d8d6489000998 100644 --- a/docs/source/api/python/mantid/kernel/ConfigPropertyObserver.rst +++ b/docs/source/api/python/mantid/kernel/ConfigPropertyObserver.rst @@ -2,7 +2,7 @@ ConfigPropertyObserver ======================== -This a python binding to the C++ class Mantid::Kernel::ConfigPropertyObserver. +This is a Python binding to the C++ class Mantid::Kernel::ConfigPropertyObserver. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/ConfigServiceImpl.rst b/docs/source/api/python/mantid/kernel/ConfigServiceImpl.rst index 3f7f525e802e87707dc4e87d0daf1a0f48493017..221220430d2badf7e81020349b440a69fbf5f1c3 100644 --- a/docs/source/api/python/mantid/kernel/ConfigServiceImpl.rst +++ b/docs/source/api/python/mantid/kernel/ConfigServiceImpl.rst @@ -2,7 +2,7 @@ ConfigServiceImpl =================== -This a python binding to the C++ class Mantid::Kernel::ConfigServiceImpl. +This is a Python binding to the C++ class Mantid::Kernel::ConfigServiceImpl. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/DataItem.rst b/docs/source/api/python/mantid/kernel/DataItem.rst index 24607229689d2e745d7dc4c09fb5c75556e889d4..b6d8bae897355822537e30874cea0fe9f9a92cb6 100644 --- a/docs/source/api/python/mantid/kernel/DataItem.rst +++ b/docs/source/api/python/mantid/kernel/DataItem.rst @@ -2,7 +2,7 @@ DataItem ========== -This a python binding to the C++ class Mantid::Kernel::DataItem. +This is a Python binding to the C++ class Mantid::Kernel::DataItem. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/DateAndTime.rst b/docs/source/api/python/mantid/kernel/DateAndTime.rst index 058d8a9ea652b120de2940af408afec7a43b495d..33082742f62dcab295ff5f651052b2c93a563477 100644 --- a/docs/source/api/python/mantid/kernel/DateAndTime.rst +++ b/docs/source/api/python/mantid/kernel/DateAndTime.rst @@ -2,7 +2,7 @@ DateAndTime ============= -This a python binding to the C++ class Mantid::Kernel::DateAndTime. +This is a Python binding to the C++ class Mantid::Kernel::DateAndTime. The equivalent object in python is :class:`numpy.datetime64`. The two classes have a different EPOCH. Note that diff --git a/docs/source/api/python/mantid/kernel/DeltaEMode.rst b/docs/source/api/python/mantid/kernel/DeltaEMode.rst index 5ffa55550f174449c5712065e64118617e153c83..d420e7d77d9fddf3b99600f80c3e823847a52d5e 100644 --- a/docs/source/api/python/mantid/kernel/DeltaEMode.rst +++ b/docs/source/api/python/mantid/kernel/DeltaEMode.rst @@ -2,7 +2,7 @@ DeltaEMode ============ -This a python binding to the C++ class Mantid::Kernel::DeltaEMode. +This is a Python binding to the C++ class Mantid::Kernel::DeltaEMode. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/DeltaEModeType.rst b/docs/source/api/python/mantid/kernel/DeltaEModeType.rst index 2e474f4948ba17d2a52b212d5f8e131c97c50f93..3f8dc776f348d69fb67b5aead5b30a38f75bdd2c 100644 --- a/docs/source/api/python/mantid/kernel/DeltaEModeType.rst +++ b/docs/source/api/python/mantid/kernel/DeltaEModeType.rst @@ -2,7 +2,7 @@ DeltaEModeType ================ -This a python binding to the C++ class Mantid::Kernel::DeltaEMode. +This is a Python binding to the C++ class Mantid::Kernel::DeltaEMode. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/Direction.rst b/docs/source/api/python/mantid/kernel/Direction.rst index d60874ac61ef88fdf269947415c5ea1d2955f8ba..d3be65a6a71a47cf9e1e82a0905db70442267e71 100644 --- a/docs/source/api/python/mantid/kernel/Direction.rst +++ b/docs/source/api/python/mantid/kernel/Direction.rst @@ -2,7 +2,7 @@ Direction =========== -This a python binding to the C++ class Mantid::Kernel::Direction. +This is a Python binding to the C++ class Mantid::Kernel::Direction. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/EnabledWhenProperty.rst b/docs/source/api/python/mantid/kernel/EnabledWhenProperty.rst index ad95e60f2cb339f7a5db5e9dfd796813b0e2e8ab..624e72ca2495fa2b66678d748cdfb22c628ffc46 100644 --- a/docs/source/api/python/mantid/kernel/EnabledWhenProperty.rst +++ b/docs/source/api/python/mantid/kernel/EnabledWhenProperty.rst @@ -2,7 +2,7 @@ EnabledWhenProperty ===================== -This a python binding to the C++ class Mantid::Kernel::EnabledWhenProperty. +This is a Python binding to the C++ class Mantid::Kernel::EnabledWhenProperty. *bases:* :py:obj:`mantid.kernel.IPropertySettings` diff --git a/docs/source/api/python/mantid/kernel/FacilityInfo.rst b/docs/source/api/python/mantid/kernel/FacilityInfo.rst index dcfefa1eefbe98dac3200a8384cce4369076961d..402f05f622599c4242ddae3215917d36675a1387 100644 --- a/docs/source/api/python/mantid/kernel/FacilityInfo.rst +++ b/docs/source/api/python/mantid/kernel/FacilityInfo.rst @@ -2,7 +2,7 @@ FacilityInfo ============== -This a python binding to the C++ class Mantid::Kernel::FacilityInfo. +This is a Python binding to the C++ class Mantid::Kernel::FacilityInfo. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/FloatArrayBoundedValidator.rst b/docs/source/api/python/mantid/kernel/FloatArrayBoundedValidator.rst index f8d5e24b22b988a805e495230586d45537225244..b0fd3570b5fceab9d27fe5738821ae6c5f41a40f 100644 --- a/docs/source/api/python/mantid/kernel/FloatArrayBoundedValidator.rst +++ b/docs/source/api/python/mantid/kernel/FloatArrayBoundedValidator.rst @@ -2,7 +2,7 @@ FloatArrayBoundedValidator ============================ -This a python binding to the C++ class Mantid::Kernel::ArrayBoundedValidator. +This is a Python binding to the C++ class Mantid::Kernel::ArrayBoundedValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/FloatArrayLengthValidator.rst b/docs/source/api/python/mantid/kernel/FloatArrayLengthValidator.rst index dc34b964351fa71b1108f6b70d6334c4f76b02df..94d301dbce6dffe15e65806681d2f2dbcbc1ae3c 100644 --- a/docs/source/api/python/mantid/kernel/FloatArrayLengthValidator.rst +++ b/docs/source/api/python/mantid/kernel/FloatArrayLengthValidator.rst @@ -2,7 +2,7 @@ FloatArrayLengthValidator =========================== -This a python binding to the C++ class Mantid::Kernel::ArrayLengthValidator. +This is a Python binding to the C++ class Mantid::Kernel::ArrayLengthValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/FloatArrayMandatoryValidator.rst b/docs/source/api/python/mantid/kernel/FloatArrayMandatoryValidator.rst index 78ee99712df0672cee662e11bea764152eedbcf6..5e791b635f1b658b247d77e2ea048914631fb1d0 100644 --- a/docs/source/api/python/mantid/kernel/FloatArrayMandatoryValidator.rst +++ b/docs/source/api/python/mantid/kernel/FloatArrayMandatoryValidator.rst @@ -2,7 +2,7 @@ FloatArrayMandatoryValidator ============================== -This a python binding to the C++ class Mantid::Kernel::MandatoryValidator. +This is a Python binding to the C++ class Mantid::Kernel::MandatoryValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/FloatArrayProperty.rst b/docs/source/api/python/mantid/kernel/FloatArrayProperty.rst index e1cfab9fbeb21e4995e30c1962b2edb1f0e88a48..8bafae48fbce001b8f2153ae0fe24934bd62a55f 100644 --- a/docs/source/api/python/mantid/kernel/FloatArrayProperty.rst +++ b/docs/source/api/python/mantid/kernel/FloatArrayProperty.rst @@ -2,7 +2,7 @@ FloatArrayProperty ==================== -This a python binding to the C++ class Mantid::Kernel::ArrayProperty. +This is a Python binding to the C++ class Mantid::Kernel::ArrayProperty. *bases:* :py:obj:`mantid.kernel.VectorFloatPropertyWithValue` diff --git a/docs/source/api/python/mantid/kernel/FloatBoundedValidator.rst b/docs/source/api/python/mantid/kernel/FloatBoundedValidator.rst index 7ffbf1776ecd5e3c0b18404bb96185a9af25cca7..fd29166b5a335067a713ff78552b21784d7a84bd 100644 --- a/docs/source/api/python/mantid/kernel/FloatBoundedValidator.rst +++ b/docs/source/api/python/mantid/kernel/FloatBoundedValidator.rst @@ -2,7 +2,7 @@ FloatBoundedValidator ======================= -This a python binding to the C++ class Mantid::Kernel::FloatBoundedValidator. +This is a Python binding to the C++ class Mantid::Kernel::FloatBoundedValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/FloatFilteredTimeSeriesProperty.rst b/docs/source/api/python/mantid/kernel/FloatFilteredTimeSeriesProperty.rst index 4f1c5ec0dfb909296944a2169b6b3ad730a16da0..20f25cc27e8a07c6d91dc70728d0ce808e127396 100644 --- a/docs/source/api/python/mantid/kernel/FloatFilteredTimeSeriesProperty.rst +++ b/docs/source/api/python/mantid/kernel/FloatFilteredTimeSeriesProperty.rst @@ -2,7 +2,7 @@ FloatFilteredTimeSeriesProperty ================================= -This a python binding to the C++ class Mantid::Kernel::FilteredTimeSeriesProperty. +This is a Python binding to the C++ class Mantid::Kernel::FilteredTimeSeriesProperty. *bases:* :py:obj:`mantid.kernel.FloatTimeSeriesProperty` diff --git a/docs/source/api/python/mantid/kernel/FloatMandatoryValidator.rst b/docs/source/api/python/mantid/kernel/FloatMandatoryValidator.rst index 813510b0d673eecced3595cfdc043eaf2859af04..30d6bb13d1d581367da9ea5facef669cd577c917 100644 --- a/docs/source/api/python/mantid/kernel/FloatMandatoryValidator.rst +++ b/docs/source/api/python/mantid/kernel/FloatMandatoryValidator.rst @@ -2,7 +2,7 @@ FloatMandatoryValidator ========================= -This a python binding to the C++ class Mantid::Kernel::MandatoryValidator. +This is a Python binding to the C++ class Mantid::Kernel::MandatoryValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/FloatPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/FloatPropertyWithValue.rst index d85e285d9f6422a8587c6963f041061d77ac01cb..486edadc873b1dd7b08b2b33f4c91529b4c66799 100644 --- a/docs/source/api/python/mantid/kernel/FloatPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/FloatPropertyWithValue.rst @@ -2,7 +2,7 @@ FloatPropertyWithValue ======================== -This a python binding to the C++ class Mantid::Kernel::FloatPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::FloatPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/FloatTimeSeriesProperty.rst b/docs/source/api/python/mantid/kernel/FloatTimeSeriesProperty.rst index f4dfe350c096b3fa5d14ee48805bc4a59ae62faa..6239517b7eea7da93d7b257106068952bf4f4a6d 100644 --- a/docs/source/api/python/mantid/kernel/FloatTimeSeriesProperty.rst +++ b/docs/source/api/python/mantid/kernel/FloatTimeSeriesProperty.rst @@ -2,7 +2,7 @@ FloatTimeSeriesProperty ========================= -This a python binding to the C++ class Mantid::Kernel::TimeSeriesProperty. +This is a Python binding to the C++ class Mantid::Kernel::TimeSeriesProperty. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/IPropertyManager.rst b/docs/source/api/python/mantid/kernel/IPropertyManager.rst index 97faef639cb73d049c6d3eeeb85f607dedb8a484..791569f18cba0d8b53bae09a7dc42a4672ebff9b 100644 --- a/docs/source/api/python/mantid/kernel/IPropertyManager.rst +++ b/docs/source/api/python/mantid/kernel/IPropertyManager.rst @@ -2,7 +2,7 @@ IPropertyManager ================== -This a python binding to the C++ class Mantid::Kernel::IPropertyManager. +This is a Python binding to the C++ class Mantid::Kernel::IPropertyManager. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/IPropertySettings.rst b/docs/source/api/python/mantid/kernel/IPropertySettings.rst index 18ace3e8034489591353722976d50724083872d8..1e9de915eaae2bb272444886bc755321f8bf4e3a 100644 --- a/docs/source/api/python/mantid/kernel/IPropertySettings.rst +++ b/docs/source/api/python/mantid/kernel/IPropertySettings.rst @@ -2,7 +2,7 @@ IPropertySettings =================== -This a python binding to the C++ class Mantid::Kernel::IPropertySettings. +This is a Python binding to the C++ class Mantid::Kernel::IPropertySettings. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/IValidator.rst b/docs/source/api/python/mantid/kernel/IValidator.rst index 350b9c58a430461d2ce5be0c4d2a168eb30f367a..ee68a9f5e1c9e7d8a68948abb8638263ea2af3f7 100644 --- a/docs/source/api/python/mantid/kernel/IValidator.rst +++ b/docs/source/api/python/mantid/kernel/IValidator.rst @@ -2,7 +2,7 @@ IValidator ============ -This a python binding to the C++ class Mantid::Kernel::IValidator. +This is a Python binding to the C++ class Mantid::Kernel::IValidator. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/InstrumentInfo.rst b/docs/source/api/python/mantid/kernel/InstrumentInfo.rst index d15e0d67f4c181dfdcd2a2dce888f1e198430ba2..7cbb8c2d296196d956d1a3bb9060be8867f66cab 100644 --- a/docs/source/api/python/mantid/kernel/InstrumentInfo.rst +++ b/docs/source/api/python/mantid/kernel/InstrumentInfo.rst @@ -2,7 +2,7 @@ InstrumentInfo ================ -This a python binding to the C++ class Mantid::Kernel::InstrumentInfo. +This is a Python binding to the C++ class Mantid::Kernel::InstrumentInfo. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/Int32FilteredTimeSeriesProperty.rst b/docs/source/api/python/mantid/kernel/Int32FilteredTimeSeriesProperty.rst index 247464d4eac42b376598d08f9c73e1ba4cc960a1..f6be1e75b3cfe570a641d3079e02801102b3984d 100644 --- a/docs/source/api/python/mantid/kernel/Int32FilteredTimeSeriesProperty.rst +++ b/docs/source/api/python/mantid/kernel/Int32FilteredTimeSeriesProperty.rst @@ -2,7 +2,7 @@ Int32FilteredTimeSeriesProperty ================================= -This a python binding to the C++ class Mantid::Kernel::FilteredTimeSeriesProperty. +This is a Python binding to the C++ class Mantid::Kernel::FilteredTimeSeriesProperty. *bases:* :py:obj:`mantid.kernel.Int32TimeSeriesProperty` diff --git a/docs/source/api/python/mantid/kernel/Int32TimeSeriesProperty.rst b/docs/source/api/python/mantid/kernel/Int32TimeSeriesProperty.rst index 163a700f52c8c341a2718a94bad6db94d2e0768e..7d5b1a6a9af34c2011241b9f081671e98d37d7f6 100644 --- a/docs/source/api/python/mantid/kernel/Int32TimeSeriesProperty.rst +++ b/docs/source/api/python/mantid/kernel/Int32TimeSeriesProperty.rst @@ -2,7 +2,7 @@ Int32TimeSeriesProperty ========================= -This a python binding to the C++ class Mantid::Kernel::TimeSeriesProperty. +This is a Python binding to the C++ class Mantid::Kernel::TimeSeriesProperty. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/Int64FilteredTimeSeriesProperty.rst b/docs/source/api/python/mantid/kernel/Int64FilteredTimeSeriesProperty.rst index 75d1dae072d16e0f11fa6caee1321c108109977f..80d09719020057eb5bed1dfff135a2e90694d154 100644 --- a/docs/source/api/python/mantid/kernel/Int64FilteredTimeSeriesProperty.rst +++ b/docs/source/api/python/mantid/kernel/Int64FilteredTimeSeriesProperty.rst @@ -2,7 +2,7 @@ Int64FilteredTimeSeriesProperty ================================= -This a python binding to the C++ class Mantid::Kernel::FilteredTimeSeriesProperty. +This is a Python binding to the C++ class Mantid::Kernel::FilteredTimeSeriesProperty. *bases:* :py:obj:`mantid.kernel.Int64TimeSeriesProperty` diff --git a/docs/source/api/python/mantid/kernel/Int64TimeSeriesProperty.rst b/docs/source/api/python/mantid/kernel/Int64TimeSeriesProperty.rst index 3e6b5d7703efcd7175eca04ae8805bd455bee9d5..c56ef0c2989ee25d7a2ccd480230214db728fe0c 100644 --- a/docs/source/api/python/mantid/kernel/Int64TimeSeriesProperty.rst +++ b/docs/source/api/python/mantid/kernel/Int64TimeSeriesProperty.rst @@ -2,7 +2,7 @@ Int64TimeSeriesProperty ========================= -This a python binding to the C++ class Mantid::Kernel::TimeSeriesProperty. +This is a Python binding to the C++ class Mantid::Kernel::TimeSeriesProperty. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/IntArrayBoundedValidator.rst b/docs/source/api/python/mantid/kernel/IntArrayBoundedValidator.rst index fea93a4eb77ea05f5835c8ef91ef5ac2c4d165ef..11dd5c63c9e56df0a8e5d5f3397b1c4663e12344 100644 --- a/docs/source/api/python/mantid/kernel/IntArrayBoundedValidator.rst +++ b/docs/source/api/python/mantid/kernel/IntArrayBoundedValidator.rst @@ -2,7 +2,7 @@ IntArrayBoundedValidator ========================== -This a python binding to the C++ class Mantid::Kernel::ArrayBoundedValidator. +This is a Python binding to the C++ class Mantid::Kernel::ArrayBoundedValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/IntArrayLengthValidator.rst b/docs/source/api/python/mantid/kernel/IntArrayLengthValidator.rst index 2c298f2547244229a4f74f0b892c93c96a69a02c..68787462b52a5798d68f2ab6bc01827f216208a3 100644 --- a/docs/source/api/python/mantid/kernel/IntArrayLengthValidator.rst +++ b/docs/source/api/python/mantid/kernel/IntArrayLengthValidator.rst @@ -2,7 +2,7 @@ IntArrayLengthValidator ========================= -This a python binding to the C++ class Mantid::Kernel::ArrayLengthValidator. +This is a Python binding to the C++ class Mantid::Kernel::ArrayLengthValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/IntArrayMandatoryValidator.rst b/docs/source/api/python/mantid/kernel/IntArrayMandatoryValidator.rst index d5db798c5d18ad4e2157b77a6dcd9fa6c4eba6d5..776ac8576c8cb7b7205e91d795db27bf2fd158d8 100644 --- a/docs/source/api/python/mantid/kernel/IntArrayMandatoryValidator.rst +++ b/docs/source/api/python/mantid/kernel/IntArrayMandatoryValidator.rst @@ -2,7 +2,7 @@ IntArrayMandatoryValidator ============================ -This a python binding to the C++ class Mantid::Kernel::MandatoryValidator. +This is a Python binding to the C++ class Mantid::Kernel::MandatoryValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/IntArrayProperty.rst b/docs/source/api/python/mantid/kernel/IntArrayProperty.rst index 0739af28d58689ee23cf47e2312c8d69643f0a94..2f563c64f0d73b7a695d97e2651584fbc3b0bfec 100644 --- a/docs/source/api/python/mantid/kernel/IntArrayProperty.rst +++ b/docs/source/api/python/mantid/kernel/IntArrayProperty.rst @@ -2,7 +2,7 @@ IntArrayProperty ================== -This a python binding to the C++ class Mantid::Kernel::ArrayProperty. +This is a Python binding to the C++ class Mantid::Kernel::ArrayProperty. *bases:* :py:obj:`mantid.kernel.VectorLongPropertyWithValue` diff --git a/docs/source/api/python/mantid/kernel/IntBoundedValidator.rst b/docs/source/api/python/mantid/kernel/IntBoundedValidator.rst index 9776273dda2d4ca391f7f6725299c399494589fc..e918504551906a8ae326d6747d351fd0c2dc7460 100644 --- a/docs/source/api/python/mantid/kernel/IntBoundedValidator.rst +++ b/docs/source/api/python/mantid/kernel/IntBoundedValidator.rst @@ -2,7 +2,7 @@ IntBoundedValidator ===================== -This a python binding to the C++ class Mantid::Kernel::IntBoundedValidator. +This is a Python binding to the C++ class Mantid::Kernel::IntBoundedValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/IntListValidator.rst b/docs/source/api/python/mantid/kernel/IntListValidator.rst index 52b10f540bd297408eb766a8b35dcbc340175130..289ffb7fddfcb08bfb43db55aa085f50031a1b3e 100644 --- a/docs/source/api/python/mantid/kernel/IntListValidator.rst +++ b/docs/source/api/python/mantid/kernel/IntListValidator.rst @@ -2,7 +2,7 @@ IntListValidator ================== -This a python binding to the C++ class Mantid::Kernel::IntListValidator. +This is a Python binding to the C++ class Mantid::Kernel::IntListValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/IntMandatoryValidator.rst b/docs/source/api/python/mantid/kernel/IntMandatoryValidator.rst index e575390ee854a005e4ce315b028d9c1085ca0f27..a49486a8da102f343a7395fd7ec4224af0cc4317 100644 --- a/docs/source/api/python/mantid/kernel/IntMandatoryValidator.rst +++ b/docs/source/api/python/mantid/kernel/IntMandatoryValidator.rst @@ -2,7 +2,7 @@ IntMandatoryValidator ======================= -This a python binding to the C++ class Mantid::Kernel::MandatoryValidator. +This is a Python binding to the C++ class Mantid::Kernel::MandatoryValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/IntPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/IntPropertyWithValue.rst index f575a00e9b8f4e167b115bac95da243b61af1cd2..37c2c37db4e2c2d83e5cd7a8454727c4f346e95d 100644 --- a/docs/source/api/python/mantid/kernel/IntPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/IntPropertyWithValue.rst @@ -2,7 +2,7 @@ IntPropertyWithValue ====================== -This a python binding to the C++ class Mantid::Kernel::IntPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::IntPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/Label.rst b/docs/source/api/python/mantid/kernel/Label.rst index d7c11899f141e5476372985dfdd26c2107431086..04586fd6dddaa6fa3db09d545f1a0deac981fa2f 100644 --- a/docs/source/api/python/mantid/kernel/Label.rst +++ b/docs/source/api/python/mantid/kernel/Label.rst @@ -2,7 +2,7 @@ Label ======= -This a python binding to the C++ class Mantid::Kernel::Label. +This is a Python binding to the C++ class Mantid::Kernel::Label. *bases:* :py:obj:`mantid.kernel.Unit` diff --git a/docs/source/api/python/mantid/kernel/LiveListenerInfo.rst b/docs/source/api/python/mantid/kernel/LiveListenerInfo.rst index 51572389cc864bfdaec97070be20646d537b3255..9e40ff7520bb8990d2fa111af636f1a499ac8300 100644 --- a/docs/source/api/python/mantid/kernel/LiveListenerInfo.rst +++ b/docs/source/api/python/mantid/kernel/LiveListenerInfo.rst @@ -2,7 +2,7 @@ LiveListenerInfo ================== -This a python binding to the C++ class Mantid::Kernel::LiveListenerInfo. +This is a Python binding to the C++ class Mantid::Kernel::LiveListenerInfo. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/LogFilter.rst b/docs/source/api/python/mantid/kernel/LogFilter.rst index 63ad422d754e9553c7f8a39ec3f029c226153ca2..25c32c4ddce27c8a352b08d442149c7fb804be3c 100644 --- a/docs/source/api/python/mantid/kernel/LogFilter.rst +++ b/docs/source/api/python/mantid/kernel/LogFilter.rst @@ -2,7 +2,7 @@ LogFilter =========== -This a python binding to the C++ class Mantid::Kernel::LogFilter. +This is a Python binding to the C++ class Mantid::Kernel::LogFilter. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/Logger.rst b/docs/source/api/python/mantid/kernel/Logger.rst index cb2b4c2fcbcdf1d0b3218fd4e2102f0ea9fbc32f..50e8e446e37186f8bb49eaee725ab5054c7de369 100644 --- a/docs/source/api/python/mantid/kernel/Logger.rst +++ b/docs/source/api/python/mantid/kernel/Logger.rst @@ -2,7 +2,7 @@ Logger ======== -This a python binding to the C++ class Mantid::Kernel::Logger. +This is a Python binding to the C++ class Mantid::Kernel::Logger. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/LongLongPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/LongLongPropertyWithValue.rst index 91f2879447dd7d2e14e3fd69832bf75051b762f6..75a77bb0789ca3c4cfe273ac220057c7b7d60fd5 100644 --- a/docs/source/api/python/mantid/kernel/LongLongPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/LongLongPropertyWithValue.rst @@ -2,7 +2,7 @@ LongLongPropertyWithValue =========================== -This a python binding to the C++ class Mantid::Kernel::LongLongPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::LongLongPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/LongPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/LongPropertyWithValue.rst index b14955194b02a0f3ec3134f767158268cc6449aa..b7d3053573e78b54359c2b99eaa5266d95664c0c 100644 --- a/docs/source/api/python/mantid/kernel/LongPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/LongPropertyWithValue.rst @@ -2,7 +2,7 @@ LongPropertyWithValue ======================= -This a python binding to the C++ class Mantid::Kernel::LongPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::LongPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/Material.rst b/docs/source/api/python/mantid/kernel/Material.rst index a6fa3b14f0f18fd19b63f34c3d3c5bfd876873ee..589fbd5f0e4a9401fb65e5bcdfb4ebe4e9c14383 100644 --- a/docs/source/api/python/mantid/kernel/Material.rst +++ b/docs/source/api/python/mantid/kernel/Material.rst @@ -2,7 +2,7 @@ Material ========== -This a python binding to the C++ class Mantid::Kernel::Material. +This is a Python binding to the C++ class Mantid::Kernel::Material. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/MaterialBuilder.rst b/docs/source/api/python/mantid/kernel/MaterialBuilder.rst index a788f66229f7e8264e95c719a6c4adf816cb20ed..6325ec92ff989ad4fb80785814aab5f039c97de4 100644 --- a/docs/source/api/python/mantid/kernel/MaterialBuilder.rst +++ b/docs/source/api/python/mantid/kernel/MaterialBuilder.rst @@ -2,7 +2,7 @@ MaterialBuilder ================= -This a python binding to the C++ class +This is a Python binding to the C++ class Mantid::Kernel::MaterialBuilder. It provides an interface for generating :py:obj:`mantid.kernel.Material` objects. diff --git a/docs/source/api/python/mantid/kernel/MemoryStats.rst b/docs/source/api/python/mantid/kernel/MemoryStats.rst index 4ae522f2c71f59bba9c935120a573539d65f7332..553c15b8139e24146bd9b22aafa146a7189351d1 100644 --- a/docs/source/api/python/mantid/kernel/MemoryStats.rst +++ b/docs/source/api/python/mantid/kernel/MemoryStats.rst @@ -2,7 +2,7 @@ MemoryStats ============= -This a python binding to the C++ class Mantid::Kernel::MemoryStats. +This is a Python binding to the C++ class Mantid::Kernel::MemoryStats. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/NullValidator.rst b/docs/source/api/python/mantid/kernel/NullValidator.rst index 5d3719ec62a90e451a18a4b6bd826427d03aff35..3ef2b0ab68daa9adbd3ebbc01ea5de69cd58242e 100644 --- a/docs/source/api/python/mantid/kernel/NullValidator.rst +++ b/docs/source/api/python/mantid/kernel/NullValidator.rst @@ -2,7 +2,7 @@ NullValidator =============== -This a python binding to the C++ class Mantid::Kernel::NullValidator. +This is a Python binding to the C++ class Mantid::Kernel::NullValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/ProgressBase.rst b/docs/source/api/python/mantid/kernel/ProgressBase.rst index c3b9c2eec7798ef15b036e65508d874ae0ba74f5..cb3e70440a2e2dee2404e85f10988d539e83d88b 100644 --- a/docs/source/api/python/mantid/kernel/ProgressBase.rst +++ b/docs/source/api/python/mantid/kernel/ProgressBase.rst @@ -2,7 +2,7 @@ ProgressBase ============== -This a python binding to the C++ class Mantid::Kernel::ProgressBase. +This is a Python binding to the C++ class Mantid::Kernel::ProgressBase. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/Property.rst b/docs/source/api/python/mantid/kernel/Property.rst index a9bb49c9368391d046159e3bd8e60c3e836147ef..7f9b12f28c3880028298ebacaa7c1f0c3bcaf15e 100644 --- a/docs/source/api/python/mantid/kernel/Property.rst +++ b/docs/source/api/python/mantid/kernel/Property.rst @@ -2,7 +2,7 @@ Property ========== -This a python binding to the C++ class Mantid::Kernel::Property. +This is a Python binding to the C++ class Mantid::Kernel::Property. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/PropertyCriterion.rst b/docs/source/api/python/mantid/kernel/PropertyCriterion.rst index 98651f87a83d2fc4e153cf0ae2444883f273edfd..56ebfe7b2dfa3e807ceff36d13265f219fb7424e 100644 --- a/docs/source/api/python/mantid/kernel/PropertyCriterion.rst +++ b/docs/source/api/python/mantid/kernel/PropertyCriterion.rst @@ -2,7 +2,7 @@ PropertyCriterion =================== -This a python binding to the C++ class Mantid::Kernel::PropertyCriterion. +This is a Python binding to the C++ class Mantid::Kernel::PropertyCriterion. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/PropertyHistory.rst b/docs/source/api/python/mantid/kernel/PropertyHistory.rst index 1053382913edf6ea96a41492a65335e3312885dd..6caa2dc34f7f6a0f608956e036dbdb378fd98e6b 100644 --- a/docs/source/api/python/mantid/kernel/PropertyHistory.rst +++ b/docs/source/api/python/mantid/kernel/PropertyHistory.rst @@ -2,7 +2,7 @@ PropertyHistory ================= -This a python binding to the C++ class Mantid::Kernel::PropertyHistory. +This is a Python binding to the C++ class Mantid::Kernel::PropertyHistory. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/PropertyManager.rst b/docs/source/api/python/mantid/kernel/PropertyManager.rst index 260467770db7864a214236b959138aaf97b1355c..075758257035b38474142e88f90b571b1c8ee1be 100644 --- a/docs/source/api/python/mantid/kernel/PropertyManager.rst +++ b/docs/source/api/python/mantid/kernel/PropertyManager.rst @@ -2,7 +2,7 @@ PropertyManager ================= -This a python binding to the C++ class Mantid::Kernel::PropertyManager. +This is a Python binding to the C++ class Mantid::Kernel::PropertyManager. *bases:* :py:obj:`mantid.kernel.IPropertyManager` diff --git a/docs/source/api/python/mantid/kernel/PropertyManagerDataServiceImpl.rst b/docs/source/api/python/mantid/kernel/PropertyManagerDataServiceImpl.rst index ad7b2cc1f90420357126f6d321c979d59571e880..6d1bf369d34a1063a7302bcd51e32885bebb4762 100644 --- a/docs/source/api/python/mantid/kernel/PropertyManagerDataServiceImpl.rst +++ b/docs/source/api/python/mantid/kernel/PropertyManagerDataServiceImpl.rst @@ -2,7 +2,7 @@ PropertyManagerDataServiceImpl ================================ -This a python binding to the C++ class Mantid::Kernel::PropertyManagerDataServiceImpl. +This is a Python binding to the C++ class Mantid::Kernel::PropertyManagerDataServiceImpl. .. module:`mantid.api` diff --git a/docs/source/api/python/mantid/kernel/Quat.rst b/docs/source/api/python/mantid/kernel/Quat.rst index cc2cd3702584d6e4964a1bcfef795a13656d083d..9a7e8cd20d12215422b4e7189eaafd13a520f0c0 100644 --- a/docs/source/api/python/mantid/kernel/Quat.rst +++ b/docs/source/api/python/mantid/kernel/Quat.rst @@ -2,7 +2,7 @@ Quat ====== -This a python binding to the C++ class Mantid::Kernel::Quat. +This is a Python binding to the C++ class Mantid::Kernel::Quat. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/Stats.rst b/docs/source/api/python/mantid/kernel/Stats.rst index 46bdfe52c349126f29c136e8fe3646f524e2afe9..eb93536fe0c9e00671fe62a40590018cdc7cfb07 100644 --- a/docs/source/api/python/mantid/kernel/Stats.rst +++ b/docs/source/api/python/mantid/kernel/Stats.rst @@ -2,7 +2,7 @@ Stats ======= -This a python binding to the C++ class Mantid::Kernel::Stats. +This is a Python binding to the C++ class Mantid::Kernel::Stats. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/StringArrayLengthValidator.rst b/docs/source/api/python/mantid/kernel/StringArrayLengthValidator.rst index 1d797d0433ee6d5594f3e7fa97c9e4f72ffa9756..0254bb995736ead83ae7b9fb20189e4f2dbd7e24 100644 --- a/docs/source/api/python/mantid/kernel/StringArrayLengthValidator.rst +++ b/docs/source/api/python/mantid/kernel/StringArrayLengthValidator.rst @@ -2,7 +2,7 @@ StringArrayLengthValidator ============================ -This a python binding to the C++ class Mantid::Kernel::ArrayLengthValidator. +This is a Python binding to the C++ class Mantid::Kernel::ArrayLengthValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/StringArrayMandatoryValidator.rst b/docs/source/api/python/mantid/kernel/StringArrayMandatoryValidator.rst index 17fd1ccf75509f2003a47bb5dfac1d3f453fff50..b2a15fb60f978bfdc725f2d3c833e754d9d008e9 100644 --- a/docs/source/api/python/mantid/kernel/StringArrayMandatoryValidator.rst +++ b/docs/source/api/python/mantid/kernel/StringArrayMandatoryValidator.rst @@ -2,7 +2,7 @@ StringArrayMandatoryValidator =============================== -This a python binding to the C++ class Mantid::Kernel::MandatoryValidator. +This is a Python binding to the C++ class Mantid::Kernel::MandatoryValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/StringArrayProperty.rst b/docs/source/api/python/mantid/kernel/StringArrayProperty.rst index d8e271a117e730f874a109af0b7a08f4a80d293a..59e4b41620421472ccfcf7065f17e33694b75fcb 100644 --- a/docs/source/api/python/mantid/kernel/StringArrayProperty.rst +++ b/docs/source/api/python/mantid/kernel/StringArrayProperty.rst @@ -2,7 +2,7 @@ StringArrayProperty ===================== -This a python binding to the C++ class Mantid::Kernel::ArrayProperty. +This is a Python binding to the C++ class Mantid::Kernel::ArrayProperty. *bases:* :py:obj:`mantid.kernel.VectorStringPropertyWithValue` diff --git a/docs/source/api/python/mantid/kernel/StringContainsValidator.rst b/docs/source/api/python/mantid/kernel/StringContainsValidator.rst index 365aec4294fc6c915a9a5f061afe994e8f9eec1c..82deeb54dae13e35fb57f5e443d38ae1d70ac3fd 100644 --- a/docs/source/api/python/mantid/kernel/StringContainsValidator.rst +++ b/docs/source/api/python/mantid/kernel/StringContainsValidator.rst @@ -2,7 +2,7 @@ StringContainsValidator ======================= -This a python binding to the C++ class Mantid::Kernel::StringContainsValidator. +This is a Python binding to the C++ class Mantid::Kernel::StringContainsValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/StringFilteredTimeSeriesProperty.rst b/docs/source/api/python/mantid/kernel/StringFilteredTimeSeriesProperty.rst index 004c7906f13ffe2cb33512017207464b449ea39a..987232d025c222acbe51c2659e0ab8d7fefd1aae 100644 --- a/docs/source/api/python/mantid/kernel/StringFilteredTimeSeriesProperty.rst +++ b/docs/source/api/python/mantid/kernel/StringFilteredTimeSeriesProperty.rst @@ -2,7 +2,7 @@ StringFilteredTimeSeriesProperty ================================== -This a python binding to the C++ class Mantid::Kernel::FilteredTimeSeriesProperty. +This is a Python binding to the C++ class Mantid::Kernel::FilteredTimeSeriesProperty. *bases:* :py:obj:`mantid.kernel.StringTimeSeriesProperty` diff --git a/docs/source/api/python/mantid/kernel/StringListValidator.rst b/docs/source/api/python/mantid/kernel/StringListValidator.rst index 9b8a4ce0588a169d1c83bd54b0f91b7ca47e65f6..81fc7c7ec166bb51aafbcbbd678dad752a91a2b0 100644 --- a/docs/source/api/python/mantid/kernel/StringListValidator.rst +++ b/docs/source/api/python/mantid/kernel/StringListValidator.rst @@ -2,7 +2,7 @@ StringListValidator ===================== -This a python binding to the C++ class Mantid::Kernel::StringListValidator. +This is a Python binding to the C++ class Mantid::Kernel::StringListValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/StringMandatoryValidator.rst b/docs/source/api/python/mantid/kernel/StringMandatoryValidator.rst index 9bb4da9c412a5ad71ebbf704e0cafa7d7cc94e57..11be4aa51266d3b8f057d1cd23757b227bc8c700 100644 --- a/docs/source/api/python/mantid/kernel/StringMandatoryValidator.rst +++ b/docs/source/api/python/mantid/kernel/StringMandatoryValidator.rst @@ -2,7 +2,7 @@ StringMandatoryValidator ========================== -This a python binding to the C++ class Mantid::Kernel::MandatoryValidator. +This is a Python binding to the C++ class Mantid::Kernel::MandatoryValidator. *bases:* :py:obj:`mantid.kernel.IValidator` diff --git a/docs/source/api/python/mantid/kernel/StringPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/StringPropertyWithValue.rst index dc658734720c46032d69e4fcb5ab7d390d3e241c..429c604080e9fbcf5bd7902d92a6797f3ea5a6fc 100644 --- a/docs/source/api/python/mantid/kernel/StringPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/StringPropertyWithValue.rst @@ -2,7 +2,7 @@ StringPropertyWithValue ========================= -This a python binding to the C++ class Mantid::Kernel::StringPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::StringPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/StringTimeSeriesProperty.rst b/docs/source/api/python/mantid/kernel/StringTimeSeriesProperty.rst index 363219ce64ba3df7f6f56f418a4fade16addf2c6..fa2778ecf50d2623ca2867795433247d2451da5f 100644 --- a/docs/source/api/python/mantid/kernel/StringTimeSeriesProperty.rst +++ b/docs/source/api/python/mantid/kernel/StringTimeSeriesProperty.rst @@ -2,7 +2,7 @@ StringTimeSeriesProperty ========================== -This a python binding to the C++ class Mantid::Kernel::TimeSeriesProperty. +This is a Python binding to the C++ class Mantid::Kernel::TimeSeriesProperty. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/TimeSeriesPropertyStatistics.rst b/docs/source/api/python/mantid/kernel/TimeSeriesPropertyStatistics.rst index 4042a5f0ed7a113e8771837bc028f64f4877a1a6..6e5b955a44181dcb5cc041111b5520f6eebf11c2 100644 --- a/docs/source/api/python/mantid/kernel/TimeSeriesPropertyStatistics.rst +++ b/docs/source/api/python/mantid/kernel/TimeSeriesPropertyStatistics.rst @@ -2,7 +2,7 @@ TimeSeriesPropertyStatistics ============================== -This a python binding to the C++ class Mantid::Kernel::TimeSeriesPropertyStatistics. +This is a Python binding to the C++ class Mantid::Kernel::TimeSeriesPropertyStatistics. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/UIntPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/UIntPropertyWithValue.rst index e3cc4207ceb993ea67b196e113ba86c8236123d1..eb186cb1741549d73a4dbd302e5b47bcd4cbae2c 100644 --- a/docs/source/api/python/mantid/kernel/UIntPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/UIntPropertyWithValue.rst @@ -2,7 +2,7 @@ UIntPropertyWithValue ======================= -This a python binding to the C++ class Mantid::Kernel::UIntPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::UIntPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/ULongLongPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/ULongLongPropertyWithValue.rst index 78d7e087dc26bebda9b66f4a1f50d5a7c43f4c76..2e0d8a63132c6a1d3a74d5e7dc3e48b4c5a780c8 100644 --- a/docs/source/api/python/mantid/kernel/ULongLongPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/ULongLongPropertyWithValue.rst @@ -2,7 +2,7 @@ ULongLongPropertyWithValue ============================ -This a python binding to the C++ class Mantid::Kernel::ULongLongPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::ULongLongPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/ULongPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/ULongPropertyWithValue.rst index 387b564492ae5265477e8db6f4b2968ebcb1f681..8abc4011235857e5e9abb326ffc83bf73d01b1e0 100644 --- a/docs/source/api/python/mantid/kernel/ULongPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/ULongPropertyWithValue.rst @@ -2,7 +2,7 @@ ULongPropertyWithValue ======================== -This a python binding to the C++ class Mantid::Kernel::ULongPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::ULongPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/Unit.rst b/docs/source/api/python/mantid/kernel/Unit.rst index 954862014cbec886e36429b3460be98c383401e9..8be7e1f5539291d7486243d2933486b82f215d18 100644 --- a/docs/source/api/python/mantid/kernel/Unit.rst +++ b/docs/source/api/python/mantid/kernel/Unit.rst @@ -2,7 +2,7 @@ Unit ====== -This a python binding to the C++ class Mantid::Kernel::Unit. +This is a Python binding to the C++ class Mantid::Kernel::Unit. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/UnitConversion.rst b/docs/source/api/python/mantid/kernel/UnitConversion.rst index ff9bf885939590262c15eb4eda4c35916d13066d..9ae7c3a19c76d9a98298e9cd2ac561db78389334 100644 --- a/docs/source/api/python/mantid/kernel/UnitConversion.rst +++ b/docs/source/api/python/mantid/kernel/UnitConversion.rst @@ -2,7 +2,7 @@ UnitConversion ================ -This a python binding to the C++ class Mantid::Kernel::UnitConversion. +This is a Python binding to the C++ class Mantid::Kernel::UnitConversion. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/UnitFactoryImpl.rst b/docs/source/api/python/mantid/kernel/UnitFactoryImpl.rst index 9bd035bc138c4e19e57793bcab610434aa6f626f..bcbaef252aebe140f90b0d05f2525360aefed23b 100644 --- a/docs/source/api/python/mantid/kernel/UnitFactoryImpl.rst +++ b/docs/source/api/python/mantid/kernel/UnitFactoryImpl.rst @@ -2,7 +2,7 @@ UnitFactoryImpl ================= -This a python binding to the C++ class Mantid::Kernel::UnitFactoryImpl. +This is a Python binding to the C++ class Mantid::Kernel::UnitFactoryImpl. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/UnitLabel.rst b/docs/source/api/python/mantid/kernel/UnitLabel.rst index 838a891d76d2dc33593c53072c1f39d5fa068139..d48b25594782a04069dcb9b456edc2bda4af07f8 100644 --- a/docs/source/api/python/mantid/kernel/UnitLabel.rst +++ b/docs/source/api/python/mantid/kernel/UnitLabel.rst @@ -2,7 +2,7 @@ UnitLabel =========== -This a python binding to the C++ class Mantid::Kernel::UnitLabel. +This is a Python binding to the C++ class Mantid::Kernel::UnitLabel. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/UnsignedIntArrayProperty.rst b/docs/source/api/python/mantid/kernel/UnsignedIntArrayProperty.rst index 13a0710d57567f0562b73ccbed55441dc810d82d..ee3897ab4f3db599f6e93821a4bbd21902806446 100644 --- a/docs/source/api/python/mantid/kernel/UnsignedIntArrayProperty.rst +++ b/docs/source/api/python/mantid/kernel/UnsignedIntArrayProperty.rst @@ -2,7 +2,7 @@ UnsignedIntArrayProperty ========================== -This a python binding to the C++ class Mantid::Kernel::ArrayProperty. +This is a Python binding to the C++ class Mantid::Kernel::ArrayProperty. *bases:* :py:obj:`mantid.kernel.VectorULongPropertyWithValue` diff --git a/docs/source/api/python/mantid/kernel/V3D.rst b/docs/source/api/python/mantid/kernel/V3D.rst index fdb5c4f166f7dce40218325fc95404542a86fa12..ab4777e58df98796b0db3a2100a5851104bdec20 100644 --- a/docs/source/api/python/mantid/kernel/V3D.rst +++ b/docs/source/api/python/mantid/kernel/V3D.rst @@ -2,7 +2,7 @@ V3D ===== -This a python binding to the C++ class Mantid::Kernel::V3D. +This is a Python binding to the C++ class Mantid::Kernel::V3D. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/VMD.rst b/docs/source/api/python/mantid/kernel/VMD.rst index 1178b414046d2a42662b95225461407ebcfd26f1..a7a3145a1f8d4a351dcec613569e21b371c936f8 100644 --- a/docs/source/api/python/mantid/kernel/VMD.rst +++ b/docs/source/api/python/mantid/kernel/VMD.rst @@ -2,7 +2,7 @@ VMD ===== -This a python binding to the C++ class Mantid::Kernel::VMD. +This is a Python binding to the C++ class Mantid::Kernel::VMD. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/VectorBoolPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/VectorBoolPropertyWithValue.rst index 6e395edbc27ae7e0f4d7042d5fd0dda194c26b5d..89ed216b23f07d6f85af645838757581b642c00a 100644 --- a/docs/source/api/python/mantid/kernel/VectorBoolPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/VectorBoolPropertyWithValue.rst @@ -2,7 +2,7 @@ VectorBoolPropertyWithValue ============================= -This a python binding to the C++ class Mantid::Kernel::VectorBoolPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::VectorBoolPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/VectorFloatPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/VectorFloatPropertyWithValue.rst index 684b1bc5284621c07d17629e203a1ac272608043..60fad3354489d7037989c2def0c2a169f61ae284 100644 --- a/docs/source/api/python/mantid/kernel/VectorFloatPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/VectorFloatPropertyWithValue.rst @@ -2,7 +2,7 @@ VectorFloatPropertyWithValue ============================== -This a python binding to the C++ class Mantid::Kernel::VectorFloatPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::VectorFloatPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/VectorIntPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/VectorIntPropertyWithValue.rst index 0fa0c89e0fcbf9b3bc131b42cf2f5fffd49e8e7f..9717aa9526f3edeed808078468e8112e1056a06e 100644 --- a/docs/source/api/python/mantid/kernel/VectorIntPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/VectorIntPropertyWithValue.rst @@ -2,7 +2,7 @@ VectorIntPropertyWithValue ============================ -This a python binding to the C++ class Mantid::Kernel::VectorIntPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::VectorIntPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/VectorLongLongPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/VectorLongLongPropertyWithValue.rst index e40e8b6a672fe14f93fd6740ae82bab5a00fa24e..a6398bebcfcfffdd9d94f7eaa1a4d7723f875e83 100644 --- a/docs/source/api/python/mantid/kernel/VectorLongLongPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/VectorLongLongPropertyWithValue.rst @@ -2,7 +2,7 @@ VectorLongLongPropertyWithValue ================================= -This a python binding to the C++ class Mantid::Kernel::VectorLongLongPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::VectorLongLongPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/VectorLongPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/VectorLongPropertyWithValue.rst index f46a57fac942721f0d2175a4138f1ed35acdf310..e77dd9749895d771539e0e2d185dfb61b3bbf0ca 100644 --- a/docs/source/api/python/mantid/kernel/VectorLongPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/VectorLongPropertyWithValue.rst @@ -2,7 +2,7 @@ VectorLongPropertyWithValue ============================= -This a python binding to the C++ class Mantid::Kernel::VectorLongPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::VectorLongPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/VectorStringPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/VectorStringPropertyWithValue.rst index e8af81f9eb4dd4fa2706100942f417610d1b80aa..bf608e7d0847915409c26944697b0458972092fd 100644 --- a/docs/source/api/python/mantid/kernel/VectorStringPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/VectorStringPropertyWithValue.rst @@ -2,7 +2,7 @@ VectorStringPropertyWithValue =============================== -This a python binding to the C++ class Mantid::Kernel::VectorStringPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::VectorStringPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/VectorUIntPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/VectorUIntPropertyWithValue.rst index c17f7b0b70c1fc923061618760601d43636c6081..5c618f73a3f713abbbed6bc21fb7c7fca186ff6b 100644 --- a/docs/source/api/python/mantid/kernel/VectorUIntPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/VectorUIntPropertyWithValue.rst @@ -2,7 +2,7 @@ VectorUIntPropertyWithValue ============================= -This a python binding to the C++ class Mantid::Kernel::VectorUIntPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::VectorUIntPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/VectorULongLongPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/VectorULongLongPropertyWithValue.rst index 70fb98a83f4d3f106bd83a75f74f70ec98513611..9063cd526c819c75b460870ebb31f617906c4318 100644 --- a/docs/source/api/python/mantid/kernel/VectorULongLongPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/VectorULongLongPropertyWithValue.rst @@ -2,7 +2,7 @@ VectorULongLongPropertyWithValue ================================== -This a python binding to the C++ class Mantid::Kernel::VectorULongLongPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::VectorULongLongPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/VectorULongPropertyWithValue.rst b/docs/source/api/python/mantid/kernel/VectorULongPropertyWithValue.rst index 74b26248db2faa20771ffd95b76492f4e6a372f7..8a07b419e69a02a7d7ee372bc5e407c85df6ced5 100644 --- a/docs/source/api/python/mantid/kernel/VectorULongPropertyWithValue.rst +++ b/docs/source/api/python/mantid/kernel/VectorULongPropertyWithValue.rst @@ -2,7 +2,7 @@ VectorULongPropertyWithValue ============================== -This a python binding to the C++ class Mantid::Kernel::VectorULongPropertyWithValue. +This is a Python binding to the C++ class Mantid::Kernel::VectorULongPropertyWithValue. *bases:* :py:obj:`mantid.kernel.Property` diff --git a/docs/source/api/python/mantid/kernel/VisibleWhenProperty.rst b/docs/source/api/python/mantid/kernel/VisibleWhenProperty.rst index eec52dd2f0d11873248c3515555fc511fc5c5dfe..309cf95692e0008f6074f97498b03b1719c632cb 100644 --- a/docs/source/api/python/mantid/kernel/VisibleWhenProperty.rst +++ b/docs/source/api/python/mantid/kernel/VisibleWhenProperty.rst @@ -2,7 +2,7 @@ VisibleWhenProperty ===================== -This a python binding to the C++ class Mantid::Kernel::VisibleWhenProperty. +This is a Python binding to the C++ class Mantid::Kernel::VisibleWhenProperty. *bases:* :py:obj:`mantid.kernel.EnabledWhenProperty` diff --git a/docs/source/api/python/mantid/kernel/std_set_int.rst b/docs/source/api/python/mantid/kernel/std_set_int.rst index f684db478e49c8e3cf427dfb3570d7498feee60a..f68a7e073afa1cab14ee910aeb0250a76289aceb 100644 --- a/docs/source/api/python/mantid/kernel/std_set_int.rst +++ b/docs/source/api/python/mantid/kernel/std_set_int.rst @@ -2,7 +2,7 @@ std_set_int ============= -This a python binding to the C++ class Mantid::Kernel::std_set_int. +This is a Python binding to the C++ class Mantid::Kernel::std_set_int. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/std_set_str.rst b/docs/source/api/python/mantid/kernel/std_set_str.rst index 515f8b1cb98c5d058526c710363b9f9b77a2d3fc..7576e1dc048b301e4e4eae5e8ef615e6e630cfcb 100644 --- a/docs/source/api/python/mantid/kernel/std_set_str.rst +++ b/docs/source/api/python/mantid/kernel/std_set_str.rst @@ -2,7 +2,7 @@ std_set_str ============= -This a python binding to the C++ class Mantid::Kernel::std_set_str. +This is a Python binding to the C++ class Mantid::Kernel::std_set_str. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/std_vector_InstrumentInfo.rst b/docs/source/api/python/mantid/kernel/std_vector_InstrumentInfo.rst index 78493f29c9f29ba3f2c316ed47ade8e91d23e214..bd17c73cde8cf11272d9f4f93109289460deaa3b 100644 --- a/docs/source/api/python/mantid/kernel/std_vector_InstrumentInfo.rst +++ b/docs/source/api/python/mantid/kernel/std_vector_InstrumentInfo.rst @@ -2,7 +2,7 @@ std_vector_InstrumentInfo =========================== -This a python binding to the C++ class Mantid::Kernel::std_vector_InstrumentInfo. +This is a Python binding to the C++ class Mantid::Kernel::std_vector_InstrumentInfo. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/std_vector_bool.rst b/docs/source/api/python/mantid/kernel/std_vector_bool.rst index 0d73bc46a1674cf08008b4dc2a4e890a91093504..615d03321eb5ed1f0ab1ce30313a5b06942a3614 100644 --- a/docs/source/api/python/mantid/kernel/std_vector_bool.rst +++ b/docs/source/api/python/mantid/kernel/std_vector_bool.rst @@ -2,7 +2,7 @@ std_vector_bool ================= -This a python binding to the C++ class Mantid::Kernel::std_vector_bool. +This is a Python binding to the C++ class Mantid::Kernel::std_vector_bool. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/std_vector_dateandtime.rst b/docs/source/api/python/mantid/kernel/std_vector_dateandtime.rst index f9dbfe5195fa74ab2027058d9771dbd902cc9d3c..b6364ecd0a65adba6e33833ebfe022ba9bbfeae1 100644 --- a/docs/source/api/python/mantid/kernel/std_vector_dateandtime.rst +++ b/docs/source/api/python/mantid/kernel/std_vector_dateandtime.rst @@ -2,7 +2,7 @@ std_vector_dateandtime ======================== -This a python binding to the C++ class Mantid::Kernel::std_vector_dateandtime. +This is a Python binding to the C++ class Mantid::Kernel::std_vector_dateandtime. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/std_vector_dbl.rst b/docs/source/api/python/mantid/kernel/std_vector_dbl.rst index 0e03ad5e9177c182e7312566335f4a82cabc3eff..74fb1f2ec0e1771fce0cfc28082d83b0275be7cb 100644 --- a/docs/source/api/python/mantid/kernel/std_vector_dbl.rst +++ b/docs/source/api/python/mantid/kernel/std_vector_dbl.rst @@ -2,7 +2,7 @@ std_vector_dbl ================ -This a python binding to the C++ class Mantid::Kernel::std_vector_dbl. +This is a Python binding to the C++ class Mantid::Kernel::std_vector_dbl. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/std_vector_facilityinfo.rst b/docs/source/api/python/mantid/kernel/std_vector_facilityinfo.rst index 008213df2a01c515a40275eb6cf04dfcd2e4d9b3..bb4bc018cdda39cd496b9b1cecc97837bfee7de7 100644 --- a/docs/source/api/python/mantid/kernel/std_vector_facilityinfo.rst +++ b/docs/source/api/python/mantid/kernel/std_vector_facilityinfo.rst @@ -2,7 +2,7 @@ std_vector_facilityinfo ========================= -This a python binding to the C++ class Mantid::Kernel::std_vector_facilityinfo. +This is a Python binding to the C++ class Mantid::Kernel::std_vector_facilityinfo. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/std_vector_int.rst b/docs/source/api/python/mantid/kernel/std_vector_int.rst index bb254dd593cd9d6dbd471b64cb2fccdf44550679..a42b3ec3c0799b586392a09cbd2a77d39a781fc5 100644 --- a/docs/source/api/python/mantid/kernel/std_vector_int.rst +++ b/docs/source/api/python/mantid/kernel/std_vector_int.rst @@ -2,7 +2,7 @@ std_vector_int ================ -This a python binding to the C++ class Mantid::Kernel::std_vector_int. +This is a Python binding to the C++ class Mantid::Kernel::std_vector_int. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/std_vector_long.rst b/docs/source/api/python/mantid/kernel/std_vector_long.rst index 974e3ba8b2d072c64f7f9d8466c33af417a42648..dc3fa07d4f6f14c3a612038733e2e79fc5148f92 100644 --- a/docs/source/api/python/mantid/kernel/std_vector_long.rst +++ b/docs/source/api/python/mantid/kernel/std_vector_long.rst @@ -2,7 +2,7 @@ std_vector_long ================= -This a python binding to the C++ class Mantid::Kernel::std_vector_long. +This is a Python binding to the C++ class Mantid::Kernel::std_vector_long. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/std_vector_property.rst b/docs/source/api/python/mantid/kernel/std_vector_property.rst index 38d6a42f70e085648c9a3b1914bececd97762fbf..3ee0f4036a3de6a12dba8e65f94d9304527859f7 100644 --- a/docs/source/api/python/mantid/kernel/std_vector_property.rst +++ b/docs/source/api/python/mantid/kernel/std_vector_property.rst @@ -2,7 +2,7 @@ std_vector_property ===================== -This a python binding to the C++ class Mantid::Kernel::std_vector_property. +This is a Python binding to the C++ class Mantid::Kernel::std_vector_property. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/std_vector_size_t.rst b/docs/source/api/python/mantid/kernel/std_vector_size_t.rst index 1120745b13972bb35ecc3dca7bb70fa656d4ebc1..9a7e74b5d6c8adc0507197bc480047180f306f70 100644 --- a/docs/source/api/python/mantid/kernel/std_vector_size_t.rst +++ b/docs/source/api/python/mantid/kernel/std_vector_size_t.rst @@ -2,7 +2,7 @@ std_vector_size_t =================== -This a python binding to the C++ class Mantid::Kernel::std_vector_size_t. +This is a Python binding to the C++ class Mantid::Kernel::std_vector_size_t. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/std_vector_str.rst b/docs/source/api/python/mantid/kernel/std_vector_str.rst index 2099818f3b815ad726f31fd49f746f34610cc65f..9e29b160fdbbd53a75a980bda8ff99f70a6d4abb 100644 --- a/docs/source/api/python/mantid/kernel/std_vector_str.rst +++ b/docs/source/api/python/mantid/kernel/std_vector_str.rst @@ -2,7 +2,7 @@ std_vector_str ================ -This a python binding to the C++ class Mantid::Kernel::std_vector_str. +This is a Python binding to the C++ class Mantid::Kernel::std_vector_str. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantid/kernel/time_duration.rst b/docs/source/api/python/mantid/kernel/time_duration.rst index 08259563805c19bf8ac64857ea4308cf696191d7..451a5ee18fb70f308163ba6050268392c530dd19 100644 --- a/docs/source/api/python/mantid/kernel/time_duration.rst +++ b/docs/source/api/python/mantid/kernel/time_duration.rst @@ -2,7 +2,7 @@ time_duration =============== -This a python binding to the C++ class Mantid::Kernel::time_duration. +This is a Python binding to the C++ class Mantid::Kernel::time_duration. .. module:`mantid.kernel` diff --git a/docs/source/api/python/mantidplot/ArrowMarker.rst b/docs/source/api/python/mantidplot/ArrowMarker.rst index 62b001688084fd81d9b3ba2d1b659d6e1c6ad188..634a45fb1b5c2f0d7b08798bfe51fc51e378ba50 100644 --- a/docs/source/api/python/mantidplot/ArrowMarker.rst +++ b/docs/source/api/python/mantidplot/ArrowMarker.rst @@ -2,7 +2,7 @@ ArrowMarker ============= -This a python binding to the C++ class Mantidplot::ArrowMarker. +This is a Python binding to the C++ class Mantidplot::ArrowMarker. .. module:`mantidplot` diff --git a/docs/source/api/python/mantidplot/GraphOptions.rst b/docs/source/api/python/mantidplot/GraphOptions.rst index 88f485d006748caacc057959bb5a917c206a32c5..0f8ae99ce854c66b629f97add32a09932722ab2a 100644 --- a/docs/source/api/python/mantidplot/GraphOptions.rst +++ b/docs/source/api/python/mantidplot/GraphOptions.rst @@ -2,7 +2,7 @@ GraphOptions ============== -This a python binding to the C++ class Mantidplot::GraphOptions. +This is a Python binding to the C++ class Mantidplot::GraphOptions. .. module:`mantidplot` diff --git a/docs/source/api/python/mantidplot/ImageMarker.rst b/docs/source/api/python/mantidplot/ImageMarker.rst index 663549ce86fefe3239e689064ea4b5227668ea5b..21ac3b2e177996a58879a68e69b2fa54edafce85 100644 --- a/docs/source/api/python/mantidplot/ImageMarker.rst +++ b/docs/source/api/python/mantidplot/ImageMarker.rst @@ -2,7 +2,7 @@ ImageMarker ============= -This a python binding to the C++ class Mantidplot::ImageMarker. +This is a Python binding to the C++ class Mantidplot::ImageMarker. .. module:`mantidplot` diff --git a/docs/source/api/python/mantidplot/ImageSymbol.rst b/docs/source/api/python/mantidplot/ImageSymbol.rst index 7cb94e5a03a44e993bb146b1a607a675e725e612..620772c60626d6a1e0452d1edcb52a4707a02c4b 100644 --- a/docs/source/api/python/mantidplot/ImageSymbol.rst +++ b/docs/source/api/python/mantidplot/ImageSymbol.rst @@ -2,7 +2,7 @@ ImageSymbol ============= -This a python binding to the C++ class Mantidplot::ImageSymbol. +This is a Python binding to the C++ class Mantidplot::ImageSymbol. *bases:* :py:obj:`mantidplot.PlotSymbol` diff --git a/docs/source/api/python/mantidplot/InstrumentView.rst b/docs/source/api/python/mantidplot/InstrumentView.rst index 4a6d449c2f2fe13610785e87a408be9c1cc02e79..e850a61fc8d3d10fbd7d9411b426f1114fc1804d 100644 --- a/docs/source/api/python/mantidplot/InstrumentView.rst +++ b/docs/source/api/python/mantidplot/InstrumentView.rst @@ -2,7 +2,7 @@ InstrumentView ================ -This a python binding to the C++ class Mantidplot::InstrumentView. +This is a Python binding to the C++ class Mantidplot::InstrumentView. *bases:* :py:obj:`mantidplot.MDIWindow` diff --git a/docs/source/api/python/mantidplot/InstrumentViewMaskTab.rst b/docs/source/api/python/mantidplot/InstrumentViewMaskTab.rst index 06af5bb4e999e92e4c56c99b228bb8021551246d..978a8abb3b12493f978501f0888ec919c7203f88 100644 --- a/docs/source/api/python/mantidplot/InstrumentViewMaskTab.rst +++ b/docs/source/api/python/mantidplot/InstrumentViewMaskTab.rst @@ -2,7 +2,7 @@ InstrumentViewMaskTab ======================= -This a python binding to the C++ class Mantidplot::InstrumentViewMaskTab. +This is a Python binding to the C++ class Mantidplot::InstrumentViewMaskTab. *bases:* :py:obj:`mantidplot.InstrumentViewTab` diff --git a/docs/source/api/python/mantidplot/InstrumentViewPickTab.rst b/docs/source/api/python/mantidplot/InstrumentViewPickTab.rst index c2cca4c3d65c007eccdd9e00449ad889d32ee423..a96db19e92a5888c3456ba9f809780c90b25dcbb 100644 --- a/docs/source/api/python/mantidplot/InstrumentViewPickTab.rst +++ b/docs/source/api/python/mantidplot/InstrumentViewPickTab.rst @@ -2,7 +2,7 @@ InstrumentViewPickTab ======================= -This a python binding to the C++ class Mantidplot::InstrumentViewPickTab. +This is a Python binding to the C++ class Mantidplot::InstrumentViewPickTab. *bases:* :py:obj:`mantidplot.InstrumentViewTab` diff --git a/docs/source/api/python/mantidplot/Layer.rst b/docs/source/api/python/mantidplot/Layer.rst index 29ed514d3f754ed81407449dcdd3eba042a6a2fe..5e4d67957fcef4e5d2a0e60390b525ae7e5f125b 100644 --- a/docs/source/api/python/mantidplot/Layer.rst +++ b/docs/source/api/python/mantidplot/Layer.rst @@ -2,7 +2,7 @@ Layer ======= -This a python binding to the C++ class Mantidplot::Layer. +This is a Python binding to the C++ class Mantidplot::Layer. .. module:`mantidplot` diff --git a/docs/source/api/python/mantidplot/PlotSymbol.rst b/docs/source/api/python/mantidplot/PlotSymbol.rst index 9b5bd04ab3c593c123326d6a8704610202634118..181d4cd7111ebfb408dc928e738371b249abafaf 100644 --- a/docs/source/api/python/mantidplot/PlotSymbol.rst +++ b/docs/source/api/python/mantidplot/PlotSymbol.rst @@ -2,7 +2,7 @@ PlotSymbol ============ -This a python binding to the C++ class Mantidplot::PlotSymbol. +This is a Python binding to the C++ class Mantidplot::PlotSymbol. .. module:`mantidplot` diff --git a/docs/source/api/python/mantidplot/Qt.rst b/docs/source/api/python/mantidplot/Qt.rst index 9a6b17b6ed1a74c8499c18954742f67c607eed5e..14c7360be560a65679a418fb6bac3da627371691 100644 --- a/docs/source/api/python/mantidplot/Qt.rst +++ b/docs/source/api/python/mantidplot/Qt.rst @@ -2,7 +2,7 @@ Qt ==== -This a python binding to the C++ class PyQt4::QtCore::Qt. +This is a Python binding to the C++ class PyQt4::QtCore::Qt. .. module:`PyQt4.QtCore` diff --git a/docs/source/api/python/mantidplot/Screenshot.rst b/docs/source/api/python/mantidplot/Screenshot.rst index 3863cc4bf7bad6cef64aee6d946ad02cdd051d7b..24a3313b53b3aaf66a386c7d5c0dfcaa91692c32 100644 --- a/docs/source/api/python/mantidplot/Screenshot.rst +++ b/docs/source/api/python/mantidplot/Screenshot.rst @@ -2,7 +2,7 @@ Screenshot ============ -This a python binding to the C++ class Mantidplot::Screenshot. +This is a Python binding to the C++ class Mantidplot::Screenshot. .. module:`mantidplot` diff --git a/docs/source/concepts/AbsorptionAndMultipleScattering.rst b/docs/source/concepts/AbsorptionAndMultipleScattering.rst new file mode 100644 index 0000000000000000000000000000000000000000..bd387431e3440d38f74e1324bd3936cc192bc2bf --- /dev/null +++ b/docs/source/concepts/AbsorptionAndMultipleScattering.rst @@ -0,0 +1,719 @@ +.. _Sample Corrections: + +Absorption and Multiple Scattering Corrections +============================================== + +.. contents:: + + +Introduction +------------- +To discuss sample corrections, specifically the absorption and multiple scattering, we must first define terms pertinent to the calculations of these corrections. + +Total cross section (:math:`\sigma_t`) +####################################### +First, we define the total cross section per atom, which for the majority of elements, is equal to the sum of the element's scattering and absorption cross sections: + +.. math:: + :label: total_xs + + \sigma_t (\lambda) = \sigma_s (\lambda) + \sigma_a (\lambda) + +where :math:`\lambda` is the neutron wavelength, :math:`\sigma_s = \int_{\Omega} \frac{d\sigma}{d\Omega} \left( \lambda, 2\theta \right) d\Omega`, and +:math:`\frac{d\sigma}{d\Omega} \left( \lambda, 2\theta \right)` is the differential cross-section which is the cross-section (:math:`\sigma`) per solid angle (:math:`d\Omega`). + +Scattering cross section (:math:`\sigma_s` ) +############################################# +Typically, the scattering cross section is directly found from tabulated single atom values [1]_ and with good approximation, assumed independent of wavelength. +If we observe the equation above for :math:`\sigma_s`, we have that it is equal to the integration of the differential cross section. Yet, for the typical diffraction experiment, +the differential cross-section is exactly the quantity we wish to measure. Thus, to proceed one could chose from the following: + +1) Measure the sample and integrate over all :math:`d\Omega` (very difficult to do exactly since instruments typically cannot cover all :math:`4 \pi` of :math:`\Omega`). +2) Perform a transmission measurement to directly measure :math:`\sigma_t` and subtract a known value for :math:`\sigma_a` (also not typical). +3) Use tabulated single atom values for :math:`\sigma_s` [1]_ (bingo!). + +For the energies used to measure structure of materials, :math:`\sigma_s` is to +reasonable approximation independent of :math:`\lambda`. However, exceptions include nuclear resonances, strong Bragg scattering as in beryllium, and significant inelastic effects. [2]_ + +Absorption cross section (:math:`\sigma_a`) +########################################### +For the absorption cross section, again typically the cross section is used from the same tabulated single atom values [1]_ but is only valid for the reference wavelength, 1.7982 :math:`\AA`, +at which it was measured at. Thus, we can look up the value of :math:`\sigma_a (1.7982 \AA)`. Yet, the absorption cross section is a linear function of wavelength (away from nuclear resonances). +Thus, we can calculate the absorption cross section at other wavelengths from: + +.. math:: + :label: abs_xs + + \sigma_a (\lambda) = \sigma_a (1.7982 \AA) \left( \frac{\lambda}{1.7982} \right) + +**NOTE:** In Mantid, the reference wavelength is defined as a variable :code:`ReferenceLambda` in :code:`NeutronAtom.cpp` [3]_. This variable should be used for consistency in all sample correction algorithms that +calculate :math:`\sigma_a (\lambda)` from this reference wavelength. + +Attenuation Length (:math:`\mu`) +################################# +The attenuation length is defined as: + +.. math:: + :label: attenuation_length + + \mu = \rho \sigma_t = \rho \left( \sigma_s + \sigma_a \right) + +where :math:`\rho` is the atomic number density of the sample. +Note that :math:`\rho` is not the crystallographic or microscopic density of a unit cell but the macroscopic density of the bulk sample. If one were to measure a powder sample, the powder would not pack +perfectly and thus there would be a given packing fraction, :math:`f`. Then, if the microscopic density is given as :math:`\rho_{micro}`, it is related to :math:`\rho` via: + +.. math:: + :label: packing_fraction + + \rho = f * \rho_{micro} + +The definition of :math:`\rho` is used throughout the rest of this documentation. + +Techniques for Calculating Corrections +###################################### + +Methods for calculating the absorption corrections (and also the multiple scattering) generally fall into these categories: + +1) Analytical solutions, based on the Boltzmann transport equation. Numerical integration is required for most shapes. + +* Generally provides quicker solutions. +* Some assumptions are included: sample geometries and the scattering processes. +* Less flexible than the Monte Carlo integration or ray-tracing for tackling different problems. +* Implemented in Mantid + +2) Monte Carlo integration + +* Generally a more computationally demanding calculation and slower to solution. Monte Carlo is used for the numerical integration technique. +* Relaxation of most assumptions needed by analytical solutions. +* More flexible than the analytical techniques for shapes, beam profiles, and mixed number of scattering processes. +* Implemented in Mantid + +3) Monte Carlo ray tracing + +* Most general solution in that it is a virtual neutron experiment with all neutron histories kept. Slowest to solution. +* Relatively no assumptions needed. Can simulate mixed numbers of scattering, complex scattering processes (ie scattering sample to sample environment back to sample then to detector), moderator and guides included. +* Most flexible but mainly a tool for designing new instruments than for calculating sample corrections. +* Typically calculated in another program specific to ray tracing and then imported into Mantid. + +The analytical method generally provides a quicker solution, but at the expense of having to make assumptions about sample geometries and scattering processes that make them less flexible than the Monte Carlo techniques (integration and ray-tracing). +However, in many cases analytical solutions are satisfactory and allow much more efficient analysis of results. + +.. _Absorption Corrections: + +Absorption +------------ + +Introduction +############### +Determination of the structure and/or dynamics of samples depends on the analysis of single scattering data. +Overall, the absorption correction is a factor, :math:`A`, such that 0 < :math:`A` < 1 . It is a factor that accounts for the loss of intensity from single scattering in the sample (or other component in the instrument). +due to both scattering and capture events in the sample. The factor :math:`A` is divided by the measured intensity. Thus, the absorption correction has an overall multiplicative enhancement of the measured intensity. + +Basic Sample Absorption Theory +############################### +The figure shows how a general single scattering process might occur. The neutron travels +a certain distance :math:`l_1` through the sample before a single scattering event occurs in +the volume element :math:`dV` of the sample. Then, the neutron travels a final length :math:`l_2` before leaving the sample and being picked up by +a detector. + +.. figure:: ../images/AbsorptionVolume.png + :alt: AbsorptionVolume.png + +To formulate the absorption sample correction, first, we assume we have a homogeneous sample of a given shape that is fully illuminated by the incident beam. +Then, the number of neutrons per unit solid angle scattered once by a volume element :math:`dV` of the sample and seen by a detector is given by: + +.. math:: + :label: dI1 + + dI_1(\theta) = J_0 \rho \frac{d\sigma}{d\Omega} \left( \theta \right) exp \left[ -\mu (\lambda_1) l_1 + - \mu (\lambda_2) l_2 \right] dV + +where :math:`J_0` is the incident flux, :math:`\rho` is the atomic number density, :math:`\frac{d\sigma}{d\Omega} \left( \theta \right)` is the differential cross-section, +:math:`l_1` and :math:`l_2` are the path lengths for incident neutrons to :math:`dV` and from :math:`dV` to the detector, respectively, +and :math:`\lambda_1` and :math:`\lambda_2` are the incident and scattered wavelength, respectively. + +Yet, this is only the contribution from a single volume element, or voxel, of the sample volume that contributes to a detector. Thus, if we integrate over the entire sample volume (all the voxels), +we arrive at the total intensity of neutrons scattered once through an angle :math:`\theta` and then leaving the sample without further scattering, given as: + +.. math:: + :label: I1 + + I_1(\theta) &= \int_V dI_1 \\ + &= J_0 \rho \frac{d\sigma}{d\Omega} \left( \theta \right) \int_{V} exp \left[ -\mu (\lambda_1) l_1 + -\mu (\lambda_2) l_2 \right] dV \\ + &= J_0 \rho \frac{d\sigma}{d\Omega} \left( \theta \right) A V + +In the last part of the equation for :math:`I_1(\theta)`, we have introduced the term :math:`A`, given as: + +.. math:: + :label: absorption_factor + + A = \frac{1}{V} \int_{V} exp \left[ -\mu (\lambda_1) l_1 + -\mu (\lambda_2) l_2 \right] dV + +This is the basic absorption correction for a single sample volume (i.e. no container included, no partial correction factors, no partial illumination, etc.). +NOTE: In some references, this term :math:`A` is instead defined as the inverse :math:`A^{-1}`. Some references use the notation here: [2]_ [4]_ [5]_ [7]_ [8]_ and [10]_, while others use the inverse notation: [6]_. + +For elastic scattering, :math:`\lambda_1` = :math:`\lambda_2` = :math:`\lambda` and we can simplify to: + +.. math:: + :label: absorption_factor_elastic + + A_{elastic} = \frac{1}{V} \int_{V} exp \left[ -\mu (\lambda) \left( l_1 + l_2 \right) \right] dV + +Partial Absorption Correction Factors: Paalman and Pings Formalism +################################################################### + +When the scattering of a sample (liquid, powder, gas, etc.) is measured, the sample is often held in a thick sample container. This container contributes significantly to the measured neutron beam. Often the empty container is measured and the signal from the container (:math:`C`) subtracted from the signal of sample plus container (:math:`S+C`). + +The Paalman-Pings formalism (PPF) [4]_ provides a framework for correcting for individual component absorption contributions, or the partial absorption correction factors, when a sample is measured in a container and, possibly, one or more sample environments. PPF builds on the earlier work of Ritter [5]_, who described a graphical approach of accounting for partial absorption correction factors. The PPF goes beyond the work of Ritter in two important ways + +* The graphical approach is formulated instead using numerical integration. +* The contribution from the sample/container correlation region, or the interface, where the density of each is affected due to their inter-material interactions. + +In their analysis, Paalman and Pings show that the latter point is not generally of consequence since this region only exists for a few angstroms in most materials, but the ability to account for it is re-assuring. The sample/container interaction could be of significant importance in cases where the container and sample are single crystal or poly-crystalline. + +Generally, the container measurement (neglecting multiple scattering and inelastic effects) is written: + +.. math:: + :label: ppf_container + + I^E_{C} = I_C A_{C,C} + +* :math:`I^E_C` experimental intensity from the isolated container. +* :math:`I_C` theoretical intensity from the isolated container. +* :math:`A_{C,C}` is the absorption factor for scattering in the container region and absorption in the container. + +The full PPF for the sample and container measurement (neglecting multiple scattering and inelastic effects) is written: + +.. math:: + :label: ppf_sample_container + + I^E_{S+C} = I_SA_{S,SC} + I_CA_{C,SC} + I_{SC}A_{SC,SC} + +* :math:`I^E_{S+C}` experimentally measured intensity from :math:`S+C`. +* :math:`I_S` theoretical intensity from the isolated sample. +* :math:`I_C` theoretical intensity from the isolated container. +* :math:`I_{SC}` theoretical intensity from the correlated sample and container interface. +* :math:`A_{S,SC}` is the absorption factor for scattering in the sample region and absorption by the sample and container. +* :math:`A_{C,SC}` is the absorption factor for scattering in the container region and absorption by the sample and container. +* :math:`A_{SC,SC}` is the absorption factor for scattering in the correlated sample and container interface and absorption by the sample and container. + +As discussed above, the final term in this expression is generally neglected. + +General Notes +############## + +Analytical Methods +^^^^^^^^^^^^^^^^^^ + +The analytically approach has been further extended in a number of ways: + +1. The beam profile (and similarly the detector visibility of the sample) can also be included to accommodate partial illumination of the sample by the beam by means of a convolution function for the shape of the profile. [10]_ The beam profile and detector profile can be defined as a function of the volume element :math:`dV` as :math:`P(dV)` and :math:`D(dV)`, respectively. These can then be included into Eq. :eq:`absorption_factor` as: + +.. math:: + :label: absorption_factor_partial_illumination + + A = \frac{1}{V'} \int_{V} exp \left[ -\mu (\lambda_1) l_1 + -\mu (\lambda_2) l_2 \right] P(dV) D(dV) dV + +where :math:`V = \int_V P(dV) D(dV) dV` is the effective volume of the cylinder in the beam. + +2. Building on (1), the PPF can also be generalized to include the beam and detector profiles. [10]_ + + +Legends for Absorption Correction Algorithms in Mantid Table +############################################################# + +Indicates the energy modes that the algorithm can accommodate: + ++-------------+-----------+ +| Legend for Energy Mode | ++=============+===========+ +| E | Elastic | ++-------------+-----------+ +| D | Direct | ++-------------+-----------+ +| I | Indirect | ++-------------+-----------+ + +Indicates the technique used for calculating the absorption correction: + ++------------+-------------------------+ +| Legend for Technique | ++============+=========================+ +| NI | Numerical Integration | ++------------+-------------------------+ +| MC | Monte Carlo Integration | ++------------+-------------------------+ + +Options that describe what functions the algorithm is capable of and the output types: + ++-----------+------------------------------------------------------------------------------------------------------------------------------+ +| Legend for Functions | ++===========+==============================================================================================================================+ +| L | Loads correction from file | ++-----------+------------------------------------------------------------------------------------------------------------------------------+ +| MS | Multiple scattering correction calculated | ++-----------+------------------------------------------------------------------------------------------------------------------------------+ +| FI | Full illumination of sample by beam | ++-----------+------------------------------------------------------------------------------------------------------------------------------+ +| PI | Full or partial illumination of sample by beam | ++-----------+------------------------------------------------------------------------------------------------------------------------------+ +| W | Outputs a corrected sample workspace | ++-----------+------------------------------------------------------------------------------------------------------------------------------+ +| A | Absorption correction calculated | ++-----------+------------------------------------------------------------------------------------------------------------------------------+ +| A\+ | Calculates both sample and container absorption corrections (:math:`A_{s,s}`, :math:`A_{c,c}`) | ++-----------+------------------------------------------------------------------------------------------------------------------------------+ +| A\++ | Calculates full set of partial absorption corrections (:math:`A_{s,s}`, :math:`A_{s,sc}`, :math:`A_{c,c}`, :math:`A_{c,sc}`) | ++-----------+------------------------------------------------------------------------------------------------------------------------------+ + + + +Absorption Correction Algorithms in Mantid Table +################################################# + ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| Algorithm | Energy Mode | Technique | Geometry | Input Units | Functions | Notes | ++=====================================================================================+=============+============+=================================+====================+=====================+=======================================================================================+ +| :ref:`AbsorptionCorrection <algm-AbsorptionCorrection>` | E,D,I | NI | Any Shape | Wavelength | A,PI || Approximates sample shape using cuboid mesh of given element size | +| | | | | | || Base class: AbsorptionCorrection | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`AnnularRingAbsorption <algm-AnnularRingAbsorption>` | E,D,I | MC | Annular / Hollow Cylinder | Wavelength | A,PI | | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`AnvredCorrection <algm-AnvredCorrection>` | E | NI | Sphere | Wavelength or TOF | A,FI,W || Absorption for spheres with additional corrections in ANVRED program from ISAW: | +| | | | | | || - weight factors for pixels of instrument | +| | | | | | || - correct for the slant path through the scintillator glass and scale factors | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`ApplyPaalmanPingsCorrection <algm-ApplyPaalmanPingsCorrection>` | E,D,I | NI | Cylinder or Flat Plate / Slab | Wavelength | W || Simply applies the correction workspaces from other Paalman-Pings-style algorithms | +| | | | | | || Can also apply shift and scale factors to container workspaces | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`CalculateCarpenterSampleCorrection <algm-CalculateCarpenterSampleCorrection>` | E | NI | Cylinder | Wavelength | A,MS,FI || Only applicable to Vanadium | +| | | | | | || In-plane only | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`CalculateMonteCarloAbsorption <algm-CalculateMonteCarloAbsorption>` | E,D,I | MC || Cylinder or | Wavelength | A\+,PI || Uses multiple calls to SimpleShapeMonteCarloAbsorption to calculate | +| | | || Flat Plate / Slab or | | || sample and container correction workspaces | +| | | || Annular / Hollow Cylinder | | || | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`CarpenterSampleCorrection <algm-CarpenterSampleCorrection>` | E | NI | Cylinder | Wavelength | A,MS,FI,W || Only applicable to Vanadium | +| | | | | | || In-plane only | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`CuboidGaugeVolumeAbsorption <algm-CuboidGaugeVolumeAbsorption>` | E,D,I | NI | Cuboid section | Wavelength | A,PI | Base class: AbsorptionCorrection via wrapping | +| | | | in Any Shape sample | | | via wrapping :ref:`FlatPlateAbsorption <algm-FlatPlateAbsorption>` | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`CylinderAbsorption <algm-CylinderAbsorption>` | E,D,I | NI | Cylinder | Wavelength | A,FI | Base class: AbsorptionCorrection | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`CylinderPaalmanPingsCorrection <algm-CylinderPaalmanPingsCorrection>` | E,D,I | NI | Cylinder | Wavelength | A\++,FI | | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`FlatPlateAbsorption <algm-FlatPlateAbsorption>` | E,D,I | NI | Flat Plate / Slab | Wavelength | A,FI | Base class: AbsorptionCorrection | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`FlatPlatePaalmanPingsCorrection <algm-FlatPlatePaalmanPingsCorrection>` | E,D,I | NI | Flat Plate / Slab | Wavelength | A\++,FI | | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`HRPDSlabCanAbsorption <algm-HRPDSlabCanAbsorption>` | E | NI || HRPD aluminium flat plate only | Wavelength | A\+*,FI || Only for HRPD via hard-coded dimensions. | +| | | || | | || Uses :ref:`FlatPlateAbsorption <algm-FlatPlateAbsorption>` for sample. | +| | | || with HRPD vanadium windows | | || Uses slightly different analytical formula for aluminium holder and vanadium windows | +| | | || with HRPD vanadium windows | | || for the HRPD instrument. | +| | | || | | || \*Outputs a single correction workspace with both sample and container corrections | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`IndirectAnnulusAbsorption <algm-IndirectAnnulusAbsorption>` | I | MC || Annular / Hollow Cylinder for | Wavelength | A\+,W || Workflow algorithm specific to Indirect geometry spectrometers. | +| | | || both sample and container | | || Uses MonteCarloAbsorption for sample and container. | +| | | || | | || Will apply calculated absorption corrections and subtract container from sample. | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`IndirectCylinderAbsorption <algm-IndirectCylinderAbsorption>` | I | MC || Cylinder for sample and | Wavelength | A\+,W || Workflow algorithm specific to Indirect geometry spectrometers. | +| | | || Annular / Hollow Cylinder | | || Uses MonteCarloAbsorption for sample and container. | +| | | || for container | | || Will apply calculated absorption corrections and subtract container from sample. | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`IndirectFlatPlateAbsorption <algm-IndirectFlatPlateAbsorption>` | I | MC || Flat Plate / Slab for both | Wavelength | A\+,W || Workflow algorithm specific to Indirect geometry spectrometers. | +| | | || sample and container | | || Uses MonteCarloAbsorption for sample and container. | +| | | || | | || Will apply calculated absorption corrections and subtract container from sample. | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`MayersSampleCorrection <algm-MayersSampleCorrection>` | E | NI | Cylinder | TOF | A,MS,FI,W | | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`MonteCarloAbsorption <algm-MonteCarloAbsorption>` | E,D,I | MC | Any Shape | Wavelength | A\+*,PI || "Workhorse" of the MC-based algorithms | +| | | | | | || \*Outputs a single correction workspace with both sample and container corrections | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`PearlMCAbsorption <algm-PearlMCAbsorption>` | E | MC | Any Shape | N/A | L || Simply reads in pre-computed :math:`\mu` values for PEARL instrument from an | +| | | | | | || external Monte Carlo program. Uses :ref:`LoadAscii <algm-LoadAscii>` | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`SimpleShapeMonteCarloAbsorption <algm-SimpleShapeMonteCarloAbsorption>` | E,D,I | MC || Cylinder or | Wavelength | A,PI || Wrapper for MonteCarloAbsorption for 3 shape types | +| | | || Flat Plate / Slab or | | || | +| | | || Annular / Hollow Cylinder | | || | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`SphericalAbsorption <algm-SphericalAbsorption>` | E | NI | Sphere | Wavelength | A,FI,W | Wrapper around :ref:`AvredCorrection <algm-AnvredCorrection>` | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+--------------------+---------------------+---------------------------------------------------------------------------------------+ + + + +.. _Multiple Scattering Corrections: + +Multiple Scattering +------------------- + +Introduction +############ + +Determination of the structure and/or dynamics of samples depends on the analysis of single scattering data. +Small but unwanted higher-order scattering is always present although in many typical +experiments multiple scattering effects are negligible. However, in some cases the data may +contain a significant contribution from multiple scattering. In neutron scattering, the absorption cross-section is often much smaller than the scattering cross-section. +For this reason it is necessary to account for multiple scattering events. Using the PPF notation from previously, the measured beam from :math:`S+C` +(neglecting multiple scattering and inelastic effects) is given as: + +.. math:: + :label: ppf_sample_container_ms + + I^E_{S+C} = [I_SA_{S,SC} + I_CA_{C,SC} + I_{m,S+C}] + +Thus, the multiple scattering is a parasitic signal that needs to be subtracted from the experimentally measured :math:`I^E` intensity. +To get an idea of when and why multiple scattering +corrections are needed, let us define :math:`\sigma_n` as the likelihood of a neutron being scattered :math:`n` times. +Then it is possible to show [6]_ that: + +.. math:: + :label: sigma_m + + \sigma_m \sim (\frac{\sigma_s}{\sigma_t})^m + +Where practical, the shape and thickness of a sample are carefully chosen to minimize as much +unwanted multiple. This may be achieved by using a sample that is either [7]_ + +* Small in comparison with its mean free path. +* Strongly absorbing (the absorption cross section is much greater than the scattering cross section. Usually this means the dimensions of a sample are chosen to ensure that between 10% and 20% of incident neutrons end up being scattered [8]_ ). + +Increasing the absorption cross section is not always attainable - due to the type of material in question - or desirable, due to +the accompanying intensity losses becoming overly prohibitive. + +Theory +############ +The figure shows how a general double scattering process might occur. The neutron travels +a certain distance :math:`l_1` through the sample before the first scattering event in the volume +element :math:`dV_1`. The second scattering occurs in another volume element :math:`dV_2` after a distance +:math:`l_{12}` has been traversed following which the neutron travels a final length :math:`l_2` before +leaving the sample and being picked up by a detector. + +.. figure:: ../images/MultipleScatteringVolume.png + :alt: MultipleScatteringVolume.png + +We define the multiple scattering intensity, :math:`I_m`, in terms of the total scattering intensity, :math:`I_{total}`, from :math:`n` number of orders of scattering intensity, :math:`I_n`, as: + +.. math:: + :label: Itotal + + I_{total} &= I_1 + I_2 + I_3 + ... + I_n \\ + &= I_1 + \sum_{i=2}^{n} I_i \\ + &= I_1 + I_m + +Then, we see that to compute the multiple scattering, we must compute :math:`n-1` scattering intensity terms to subtract from the total, :math:`I_{total}`. + +Let us first just consider the secondary scattering term, :math:`I_2`. We again assume we have a homogeneous sample of a given shape that is fully illuminated by the incident beam. +Then, extending from Eq. :eq:`dI1`, we have that the number of neutrons per unit solid angle scattered once by a volume element :math:`dV_1` and then a second time by +a volume element :math:`dV_2` of the sample and seen by a detector is given by: + +.. math:: + :label: dI2 + + dI_2(\theta_s) = J_0 \rho^2 \frac{d\sigma}{d\Omega} \left( \theta_1 \right) \frac{d\sigma}{d\Omega} \left( \theta_2 \right) \frac{exp \left[ -\mu (\lambda_1) l_1 + - \mu (\lambda_{12}) l_{12} + - \mu (\lambda_2) l_2 \right]}{l_{12}^2} dV dV + +where :math:`\theta_1` is the angle between the incident path and scatter path from :math:`dV_1`, +:math:`\theta_2` is the angle between scatter path from :math:`dV_1` and scatter path from `dV_2`, +:math:`\theta_s` is the angle between the incident path and scatter path from :math:`dV_2` to the detector, +:math:`\mu_{12}` is the scattered wavelength from volume element :math:`dV_1`, +and the :math:`l_12` term is due to the inverse square law (that as the distance increases between :math:`dV_1` and +:math:`dV_2`, the solid angle subtended by :math:`dV_2` at :math:`dV_1` decreases as the inverse square of :math:`l_{12}`). + +And the total secondary scattering intensity seen by a detector is: + +.. math:: + :label: I2 + + I_2(\theta_s) &= \int_{V} \int_{V} dI_2 \\ + &= J_0 \rho^2 \frac{d\sigma}{d\Omega} \left( \theta_1 \right) \frac{d\sigma}{d\Omega} \left( \theta_2 \right) \int_{V} \int_{V} \frac{exp \left[ -\mu (\lambda_1) l_1 + - \mu (\lambda_{12}) l_{12} + - \mu (\lambda_2) l_2 \right]}{l_{12}^2} dV dV + +We can generalize this for :math:`i^{th}` order of scatter terms as: + +.. math:: + :label: dIi + + dI_i(\theta_s) = J_0 \rho^n \prod_{j=1}^{i} \frac{d\sigma}{d\Omega} \left( \theta_j \right) \frac{exp \left[ -\mu (\lambda_1) l_1 + - \sum_{j=1}^{i-1} \mu (\lambda_{j,j+1}) l_{j,j+1} + - \mu (\lambda_i) l_i \right]}{ \prod_{j=1}^{i-1} l_{j,j+1}^2} dV^{i} + +and + +.. math:: + :label: Ii + + I_i(\theta_s) &= \int_V ... \int_V dI_i \\ + &= J_0 \rho^n \prod_{j=1}^{i} \frac{d\sigma}{d\Omega} \left( \theta_j \right) \int_V ... \int_V \frac{exp \left[ -\mu (\lambda_1) l_1 + - \sum_{j=1}^{i-1} \mu (\lambda_{j,j+1}) l_{j,j+1} + - \mu (\lambda_i) l_i \right]}{ \prod_{j=1}^{i-1} l_{j,j+1}^2} dV^{i} + +Which then the multiple scattering up to the :math:`n` order scattering term is given as: + +.. math:: + :label: Im + + I_m &= \sum_{i=2}^n I_i \\ + &= \sum_{i=2}^{n} \int_V ... \int_V dI_i \\ + &= \sum_{i=2}^{n} J_0 \rho^i \prod_{j=1}^{i} \frac{d\sigma}{d\Omega} \left( \theta_j \right) \int_V ... \int_V \frac{exp \left[ -\mu (\lambda_1) l_1 + - \sum_{j=1}^{i-1} \mu (\lambda_{j,j+1}) l_{j,j+1} + - \mu (\lambda_i) l_i \right]}{ \prod_{j=1}^{i-1} l_{j,j+1}^2} dV^{i} + + +Thus, some of the difficulties in correcting multiple scattering arises from: + +1. For each :math:`i^{th}` order of scattering we must perform :math:`i` volume integrals :math:`dV^{i}` over the sample (although these terms tend to zero as explained in the introduction). +2. Without the elastic scattering assumption, we need to know each emerging :math:`\lambda_i` due to energy transfer of the :math:`i^{th}` scattering event. +3. Without the isotropic approximation, we need to know each :math:`\frac{d\sigma}{d\Omega} \left( \theta_i \right)` of the :math:`i^{th}` scattering event. This requires knowledge of :math:`\frac{d\sigma}{d\Omega}`, which is exactly what we are usually trying to measure (for diffraction)! +4. It is arbitrary at which :math:`n^{th}` order of scatter should the correction be cutoff. +5. This kind of calculation is difficult for all but the simplest of geometries (i.e. cylindrical, planar and spherical) although Monte Carlo integration methods may be utilized for the multiple scattering calculations of more general shapes. + +Assumptions +############ + +Elastic Scattering +^^^^^^^^^^^^^^^^^^ +To address (2) above, we can assume elastic scattering and then Eq. :eq:`Im` becomes: + +.. math:: + :label: Im_elastic + + I_{m,elastic} &= \sum_{i=2}^{n} J_0 \rho^i \prod_{j=1}^{i} \frac{d\sigma}{d\Omega} \left( \theta_j \right) \int_V ... \int_V \frac{exp \left[ -\mu (\lambda) \left( l_1 + \sum_{j=1}^{i-1} l_{j,j+1} + l_i \right) \right]}{ \prod_{j=1}^{i-1} l_{j,j+1}^2} dV^{i} + +Isotropic Scattering +^^^^^^^^^^^^^^^^^^^^^ +To address (3) above, we can assume isotropic scattering and then Eq. :eq:`Im` becomes: + +.. math:: + :label: Im_isotropic + + I_{m,isotropic} &= \sum_{i=2}^{n} J_0 \rho^i \left( \frac{\sigma}{4\pi} \right)^i \int_V ... \int_V \frac{exp \left[ -\mu (\lambda_1) l_1 + - \sum_{j=1}^{i-1} \mu (\lambda_{j,j+1}) l_{j,j+1} + - \mu (\lambda_i) l_i \right]}{ \prod_{j=1}^{i-1} l_{j,j+1}^2} dV^{i} + +Elastic + Isotropic Scattering +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Combining both the elastic and isotropic assumptions, we have: + +.. math:: + :label: Im_elastic_isotropic + + I_{m,elastic+isotropic} &= \sum_{i=2}^{n} J_0 \rho^i \left( \frac{\sigma}{4\pi} \right)^i \int_V ... \int_V \frac{exp \left[ -\mu (\lambda) \left( l_1 + \sum_{j=1}^{i-1} l_{j,j+1} + l_i \right) \right]}{ \prod_{j=1}^{i-1} l_{j,j+1}^2} dV^{i} + +Constant Ratio for :math:`\frac{I_n}{I_{n-1}} = \Delta` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +To address (4), a typical assumption in the analytical method [6]_ [9]_ approaches is to assume that the ratio :math:`\frac{I_n}{I_{n-1}}` is a constant, :math:`\Delta`, thus: + +.. math:: + :label: delta + + \frac{I_n}{I_{n-1}} = \frac{I_2}{I_1} = \Delta + +Then, with the assumption that :math:`\Delta < 1`, Eq. :eq:`Im` can be manipulated into a geometric series: + +.. math:: + :label: Im_proof + + I_m &= \sum_{i=2}^n I_i = \sum_{i=2}^n I_i \prod_{j=1}^{i-1} \frac{I_{j}}{I_{j}} = \sum_{i=2}^n I_i \frac{I_1}{I_{i-1}} \prod_{j=2}^{i-1} \frac{I_{j}}{I_{j-1}} \\ + &= \sum_{i=2}^n I_1 \prod_{j=2}^{i} \frac{I_{j}}{I_{j-1}} = \sum_{i=2}^n I_1 \prod_{j=2}^{i} \Delta \\ + &= I_1 \sum_{i=2}^n \Delta^{i-1} + +Similarly: + +.. math:: + :label: Im_delta_proof + + I_m \Delta &= I_1 \Delta \sum_{i=2}^n \Delta^{i-1} = I_1 \sum_{i=2}^n \Delta^{i} + +Subtracting Eq. :eq:`Im_delta_proof` from Eq. :eq:`Im_proof`, we have: + +.. math:: + :label: Im_delta_difference + + I_m - I_m \Delta &= I_1 \sum_{i=2}^n \Delta^{i-1} - I_1 \sum_{i=2}^n \Delta^{i} \\ + &= I_1 (\Delta - \Delta^{n}) + +Which, solving for :math:`I_m` and based on the assumption :math:`\Delta < 1`, implying :math:`\Delta^n << \Delta`, we arrive at: + +.. math:: + :label: Im_geometric + + I_m &= I_1 \frac{\Delta - \Delta^{n}}{1 - \Delta} \\ + &\approx I_1 \frac{\Delta }{1 - \Delta} + +NOTE: Sears arrived at a separate equation for :math:`I_m` based on flat plate samples but supposedly general enough for any shape sample: :math:`I_{m,Sears} = I_1 \left( \frac{exp(2\delta)-1}{2\delta} - 1 \right)`. +However, comparisons of both equations for cylinders show that Eq. :eq:`Im_geometric` is more accurate solution. [10]_ + +From Eq. :eq:`Im_geometric`, we are left with calculating :math:`\Delta`: + +.. math:: + :label: delta_equation + + \Delta &= \frac{I_n}{I_{n-1}} = \frac{I_2}{I_1} \\ + &= \frac{ J_0 \rho^2 \frac{d\sigma}{d\Omega} \left( \theta_1 \right) \frac{d\sigma}{d\Omega} \left( \theta_2 \right) \int_{V} \int_{V} \frac{exp \left[ -\mu (\lambda_1) l_1 + - \mu (\lambda_{12}) l_{12} + - \mu (\lambda_2) l_2 \right]}{l_{12}^2} dV dV } + { J_0 \rho \frac{d\sigma}{d\Omega} \left( \theta_s \right) \int_{V} exp \left[ -\mu (\lambda_1) l_1 + -\mu (\lambda_2) l_2 \right] dV } \\ + &= \frac{ \rho \frac{d\sigma}{d\Omega} \left( \theta_1 \right) \frac{d\sigma}{d\Omega} \left( \theta_2 \right) \int_{V} \int_{V} \frac{exp \left[ -\mu (\lambda_1) l_1 + - \mu (\lambda_{12}) l_{12} + - \mu (\lambda_2) l_2 \right]}{l_{12}^2} dV dV } + { \frac{d\sigma}{d\Omega} \left( \theta_s \right) \int_{V} exp \left[ -\mu (\lambda_1) l_1 + -\mu (\lambda_2) l_2 \right] dV } + +Using the isotropic approximation, we arrive at: + +.. math:: + :label: delta_equation_elastic + + \Delta_{elastic} &= \frac{ \rho \left( \frac{\sigma_s}{4 \pi} \right)^2 \int_{V} \int_{V} \frac{exp \left[ -\mu (\lambda_1) l_1 + - \mu (\lambda_{12}) l_{12} + - \mu (\lambda_2) l_2 \right]}{l_{12}^2} dV dV } + { \frac{\sigma_s}{4 \pi} \int_{V} exp \left[ -\mu (\lambda_1) l_1 + -\mu (\lambda_2) l_2 \right] dV } \\ + &= \frac{ \rho \sigma_s A_2 V^2 }{ 4 \pi A_1 V } = \frac{ \rho V \sigma_s A_2 }{ 4 \pi A_1 } + +where :math:`A_2` is the secondary scattering absorption factor and :math:`A_1` is the single scattering absorption factor, equivalent to :math:`A` in Eq. :eq:`absorption_factor`. +The absorption factors can be further simplified by using the elastic scattering assumption from Eq. :eq:`absorption_factor_elastic`. + +We can now begin to solve for :math:`I_m` by taking Eq. :eq:`Im_geometric` and substituting this into Eq. :eq:`Itotal`: + +.. math:: + :label: ms_derivation_part1 + + I_{total} &= I_1 + I_m \\ + &= I_1 + I_1 \frac{\Delta }{1 - \Delta} \\ + &= I_1 \left( 1 + \frac{\Delta }{1 - \Delta} \right) \\ + &= I_1 \left( \frac{1}{1 - \Delta} \right) \\ + +Solving this for :math:`I_1`, we see that: + +.. math:: + :label: ms_derivation_part2 + + I_1 &= I_{total} \left( 1 - \Delta \right) \\ + &= I_{total} - I_{total} \Delta + +Thus, comparing Eq. :eq:`ms_derivation_part2` and Eq. :eq:`delta_equation_elastic` with Eq. :eq:`Itotal`, we see that: + +.. math:: + :label: Im_equation + + I_m &= I_{total} \Delta \\ + &= I_{total} \frac{ \rho V \sigma_s A_2 }{ 4 \pi A_1 } + +General Notes +############## + +Analytical Methods +^^^^^^^^^^^^^^^^^^^^^ + +The analytical approach has been further extended in a number of ways: + +1. The beam profile (and similarly the detector visibility of the sample) can also be included to accommodate partial illumination of the sample by the beam by means of a convolution function for the shape of the profile. [10]_ +2. The isotropic approximation can be relaxed, giving anisotropic scattering for the single scattering term :math:`I_1` in Eq. :eq:`Im_isotropic`. This can be realized by either producing a solvable equation for :math:`\frac{d\sigma}{d\Omega}` [6]_ or by using a model equation for :math:`\frac{d\sigma}{d\Omega}`. [11]_ +3. The constant ratio assumption can be tested by computing the higher orders of scattering terms with comparison to Monte Carlo (for flat plate and cylinder comparisons to Monte Carlo, see [12]_). + +Monte Carlo Methods +^^^^^^^^^^^^^^^^^^^^ + +Monte Carlo approaches are a "brute force" technique that does require more computational time than the analytical approaches yet does not suffer many of the drawbacks +to the analytical approach. Such drawbacks included assumptions of isotropic scattering required to formulate the solvable equations, not being able to include +the intermediate energy transfers for scattering, and easier flexibility to handle complicated shapes for sample, container, and/or sample environments. + +In the Monte Carlo ray tracing technique, a virtual experiment is performed such that individual neutrons are put on a trajectory through a model of the +instrument with scattering kernels defined for samples, containers, and other components of the instrument. +Neutron histories are recorded so there is a clear distinction between single and multiple scattered neutrons and the multiple scattering correction +is easily obtained from the result. + + +Small Angle Scattering +^^^^^^^^^^^^^^^^^^^^^^ +In some areas, such as small angle scattering, there may be useful approximations that can be +applied that are not present for the more general wide angle scattering case. +Again matters may become complicated, as for example small angle scatter followed by incoherent +scatter from hydrogen can be more significant in blurring sharp features than double small angle scatter. +For early considerations of multiple small angle scattering see for example [13]_ [14]_. + + +Legends for Multiple Scattering Correction Algorithms in Mantid Table +###################################################################### +Indicates the energy modes that the algorithm can accommodate: + ++-------------+-----------+ +| Legend for Energy Mode | ++=============+===========+ +| E | Elastic | ++-------------+-----------+ +| D | Direct | ++-------------+-----------+ +| I | Indirect | ++-------------+-----------+ + +Indicates the technique used for calculating the absorption correction: + ++------------+-------------------------+ +| Legend for Technique | ++============+=========================+ +| NI | Numerical Integration | ++------------+-------------------------+ +| MC | Monte Carlo Integration | ++------------+-------------------------+ + +Options that describe what functions the algorithm is capable of, assumptions, and the output types: + ++-----------+------------------------------------------------------------------------------------------------------------------------------+ +| Functions | ++===========+==============================================================================================================================+ +| L | Loads correction from file | ++-----------+------------------------------------------------------------------------------------------------------------------------------+ +| FI | Full illumination of sample by beam | ++-----------+------------------------------------------------------------------------------------------------------------------------------+ +| PI | Full or partial illumination of sample by beam | ++-----------+------------------------------------------------------------------------------------------------------------------------------+ +| W | Outputs a corrected sample workspace | ++-----------+------------------------------------------------------------------------------------------------------------------------------+ +| IA | Isotropic assumption is used for all orders of scattering | ++-----------+------------------------------------------------------------------------------------------------------------------------------+ +| EA | Elastic scattering assumption is used | ++-----------+------------------------------------------------------------------------------------------------------------------------------+ + + + + +Multiple Scattering Correction Algorithms in Mantid Table +########################################################## + ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+----------------------+---------------------+---------------------------------------------------------------------------------------+ +| Algorithm | Energy Mode | Technique | Geometry | Input Units | Functions | Notes | ++=====================================================================================+=============+============+=================================+======================+=====================+=======================================================================================+ +| :ref:`CalculateCarpenterSampleCorrection <algm-CalculateCarpenterSampleCorrection>` | E | NI | Cylinder | Wavelength | IA,EA,FI || Only applicable to Vanadium | +| | | | | | || In-plane only | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+----------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`CarpenterSampleCorrection <algm-CarpenterSampleCorrection>` | E | NI | Cylinder | Wavelength | IA,EA,FI,W || Only applicable to Vanadium | +| | | | | | || In-plane only | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+----------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`LoadMcStas <algm-LoadMcStas>` | E,D,I | MC | Any Shape | - | L || Loads McStas [15]_ v2.1 histogram or event data files. | +| | | | | | || Can extract multiple scattering and subtract from scattering in Mantid | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+----------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`LoadMcStasNexus <algm-LoadMcStasNexus>` | E,D,I | MC | Any Shape | - | L || Loads McStas [15]_ v2.0 histogram data files. | +| | | | | | || Can extract multiple scattering and subtract from scattering in Mantid | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+----------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`MayersSampleCorrection <algm-MayersSampleCorrection>` | E | NI+MC | Cylinder | TOF | IA,EA,FI,W | Uses Monte Carlo integration to evaluate the analytical integral. | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+----------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`MuscatData <algm-MuscatData>` | I | MC | Cylinder or Flat Plate / Slab | :math:`S(Q,\omega)` | FI || Uses an input :math:`S(Q,\omega)` workspace created by :ref:`SofQW <algm-SofQW>` | +| | | | | | || Based on Monte Carlo program DISCUS [16]_ (or MINUS) written in FORTRAN. [17]_ | +| | | | | | || Pre-compiled and imported into Mantid via f2py (only for Windows, NumPy=1.9.3) | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+----------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`MuscatFunc <algm-MuscatFunc>` | I | MC | Cylinder or Flat Plate / Slab | :math:`S(Q,\omega)` | FI || Uses special functions to represent :math:`S(Q,\omega)` | +| | | | | | || Based on Monte Carlo program DISCUS [16]_ (or MINUS) written in FORTRAN. [17]_ | +| | | | | | || Pre-compiled and imported into Mantid via f2py (only for Windows, NumPy=1.9.3) | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+----------------------+---------------------+---------------------------------------------------------------------------------------+ +| :ref:`VesuvioCalculateMS <algm-VesuvioCalculateMS>` | I | MC | Flat Plate / Slab | TOF | FI || Monte Carlo ray tracing algorithm for deep inelastic neutron scattering. | +| | | | | | || Calculates both total and multiple scattering output workspaces. Specific to | +| | | | | | || `Vesuvio <https://www.isis.stfc.ac.uk/Pages/Vesuvio.aspx>`__ but possibly general. | ++-------------------------------------------------------------------------------------+-------------+------------+---------------------------------+----------------------+---------------------+---------------------------------------------------------------------------------------+ + + +References +------------ + +.. [1] NIST Center for Neutron Research tabulated neutron scattering lengths and cross sections. - https://www.ncnr.nist.gov/resources/n-lengths/list.html +.. [2] A.K. Soper (2012). GudrunN and GudrunX manual. - https://www.isis.stfc.ac.uk/OtherFiles/Disordered%20Materials/Gudrun-Manual-2017-10.pdf +.. [3] Mantid source code for `NeutronAtom.cpp to define ReferenceLambda variable. <https://github.com/mantidproject/mantid/blob/master/Framework/Kernel/src/NeutronAtom.cpp#L23>`__ +.. [4] H. H. Paalman, and C. J. Pings. (1962) *Numerical Evaluation of Xâ€Ray Absorption Factors for Cylindrical Samples and Annular Sample Cells*, Journal of Applied Physics 33:8 2635–2639 `doi: 10.1063/1.1729034 <http://dx.doi.org/10.1063/1.1729034>`__ +.. [5] H. L. Ritter, R. L. Harris, & R. E. Wood (1951) *On the X-Ray Absorption Correction for Encased Diffracters in the Debye-Sherrer Technique*, Journal of Applied Physics 22:2 169-176 `doi: 10.1063/1.699919 <https://doi.org/10.1063/1.1699919>`__ +.. [6] E. J. Lindley & J. Mayers (Ed.). (1988). *Chapter 10: Experimental method and corrections to data*. United Kingdom: Adam Hilger. - https://inis.iaea.org/search/search.aspx?orig_q=RN:20000574 +.. [7] V.F. Sears (1975): *Slow-neutron multiple scattering*, `Advances in Physics <http://dx.doi.org/10.1080/00018737500101361>`__, 24:1, 1-45 +.. [8] A.K.Soper, W.S.Howells and A.C.Hannon *ATLAS - Analysis of Time-of-Flight Diffraction Data from Liquid and Amorphous Samples* Rutherford Appleton Laboratory Report (1989): `RAL-89-046 <http://wwwisis2.isis.rl.ac.uk/disordered/Manuals/ATLAS/ATLAS%20manual%20v1.0.pdf>`__ +.. [9] I. A. Blech,& B. L. Averbach (Ed.). (1965). *Multiple Scattering of Neutrons in Vanadium and Copper*. Physical Review 137:4A A1113–A1116 `doi: 10.1103/PhysRev.137.A1113 <https://doi.org/10.1103/PhysRev.137.A1113>`__ +.. [10] A. K. Soper & P. A. Egelstaff (1980). *Multiple Scattering and Attenuation of Neutrons in Concentric Cylinders: I. Isotropic First Scattering*. Nuclear Instruments and Methods 178 415–425 `doi: 10.1016/0029-554X(80)90820-4 <https://doi.org/10.1016/0029-554X(80)90820-4>`__ +.. [11] S. J. Cocking & C. R. T. Heard (1965). *Multiple Scattering in Plane Samples: Application to Scattering of Thermal Neutrons*. Report AERE - R5016, Harwell, Berkshire. +.. [12] J. Mayers & R. Cywinski (1985). *A Monte Carlo Evaluation of Analytical Multiple Scattering Corrections for Unpolarised Neutron Scatting and Polarization Analysis Data*. Nuclear Instruments and Methods in Physics Research Section A: Accelerators, Spectrometers, Detectors, and Associated Equipment 241, 519-531 `doi: 10.1016/0168-9002(85)90607-2 <https://doi.org/10.1016/0168-9002(85)90607-2>`__ +.. [13] J.Schelten & W.Schmatz, J.Appl.Cryst. 13(1980)385-390 +.. [14] J.R.D.Copley J.Appl.Cryst 21(1988)639-644 +.. [15] McStas: A neutron ray-trace simulation package `website <http://mcstas.org/>`__ +.. [16] M. W. Johnson, (1974). *Discus: A computer program for calculation of multiple scattering effects in inelastic neutron scattering experiments*. Report AERE-R7682 UKAEA AERE Harwell, Oxfordshire. `Report <https://www.isis.stfc.ac.uk/Pages/discus-manual6827.pdf>`__ +.. [17] `FORTRAN source code for MUSCAT as 3rd party software in Mantid. <https://github.com/mantidproject/3rdpartysources/tree/master/Fortran/Indirect/AbsCorrection>`__ + +.. categories:: Concepts diff --git a/docs/source/concepts/FitConstraint.rst b/docs/source/concepts/FitConstraint.rst index ba86cfce553e7c93b5616141d9f3bfda47cf3e91..a55daed86baa704cf3b564190a0db894bd01938a 100644 --- a/docs/source/concepts/FitConstraint.rst +++ b/docs/source/concepts/FitConstraint.rst @@ -45,6 +45,25 @@ If more than one constraint is defined, then for each violated constraint a penalty of the type defined above is added to the calculated fitting function. - +If the penalty C is not the default value of 1000, then the +constraint penalty value will be included whenever the function +is converted to a string. For example: + +.. code-block:: python + + from mantid.simpleapi import * + myFunction = Gaussian(Height=1.0, PeakCentre=3.0, Sigma=1.0) + myFunction.constrain("PeakCentre < 6") + print(myFunction) + myFunction.setConstraintPenaltyFactor("PeakCentre", 10.0) + print(myFunction) + myFunction.constrain('Sigma > 0') + print(myFunction) + +will output:: + + name=Gaussian,Height=1,PeakCentre=3,Sigma=1,constraints=(PeakCentre<6) + name=Gaussian,Height=1,PeakCentre=3,Sigma=1,constraints=(PeakCentre<6,penalty=10) + name=Gaussian,Height=1,PeakCentre=3,Sigma=1,constraints=(PeakCentre<6,penalty=10,0<Sigma)` .. categories:: Concepts diff --git a/docs/source/concepts/MultipleScattering.rst b/docs/source/concepts/MultipleScattering.rst deleted file mode 100644 index f2457d79523158a0dacb5b6186742b638ef2dc43..0000000000000000000000000000000000000000 --- a/docs/source/concepts/MultipleScattering.rst +++ /dev/null @@ -1,86 +0,0 @@ -.. _Multiple Scattering: - -Multiple Scattering -=================== - -Introduction -~~~~~~~~~~~~ -Determination of the structure of samples depends on the analysis of single scattering data. -Small but unwanted higher-order scattering is always present although in many typical -experiments multiple scattering effects are negligible. However, in some cases the data may -contain a significant contribution from multiple scattering. To get an idea of when and why multiple scattering -corrections are needed let us first define the total cross section per atom as the sum of its -scattering and absorption cross sections: - -.. math:: - - \sigma_t = \sigma_s + \sigma_a - -If :math:`\sigma_m` is the likelihood of a neutron being scattered m times then it is possible -to show [1]_ that: - -.. math:: - - \sigma_m \sim (\frac{\sigma_s}{\sigma_t})^m - -Where practical, the shape and thickness of a sample are carefully chosen to minimise as much -unwanted multiple. This may be achieved by using a sample that is either [2]_ - -* Small in comparison with its mean free path. -* Strongly absorbing (the absorption cross section is much greater than the scattering cross section. Usually this means the dimensions of a sample are chosen to ensure that between 10% and 20% of incident neutrons end up being scattered [3]_ ). - -Increasing the absorption cross section is not always attainable - due to the type of material in question - or desirable, due to -the accompanying intensity losses becoming overly prohibitive. - -The rest of this document shall explain the theory behind multiple scattering and then outline -and compare the techniques currently available in Mantid to perform these corrections. - -Theory -~~~~~~ -The figure shows how a general double scattering process might occur. The neutron travels -a certain distance :math:`l_1` through the sample before the first scattering event. The second -scattering occurs after a distance :math:`l_{12}` has been traversed following which the -neutron travels a final length :math:`l_2` before leaving the sample and being picked up by -a detector. - -.. figure:: ../images/MultipleScatteringVolume.png - :alt: MultipleScatteringVolume.png - -The difficulty in correcting multiple scattering arises from the fact that for each order of scattering -we must perform m volume integrals :math:`dV_1dV_2...dV_m` over the sample to compute the contribution term -for that order (although these terms tend to zero as explained in the introduction). -This kind of calculation is incredibly difficult for all but the simplest of geometries -(i.e. cylindrical, planar and spherical) although Monte Carlo integration -methods may be utilised for the multiple scattering calculations of more general shapes. - -In some areas, such as small angle scattering, there may be useful approximations that can be -applied that are not present for the more general wide angle scattering case. -Again matters may become complicated, as for example small angle scatter followed by incoherent -scatter from hydrogen can be more significant in blurring sharp features than double small angle scatter. -For early considerations of multiple small angle scattering see for example [4]_ [5]_. - -Some simplifying approximations can make the calculations somewhat more tractable and the currently -supported Mantid solutions assume that scattering is isotropic and elastic (for something like vanadium this is usually -reasonable). - -Mantid solutions -~~~~~~~~~~~~~~~~ - -Mayers Sample Correction ------------------------- -Documentation for this algorithm can found :ref:`here <algm-MayersSampleCorrection>`. - -Carpenter Sample Correction ---------------------------------------- -This is a Carpenter style correction. For more details see :ref:`here <algm-CarpenterSampleCorrection>`. - -References -~~~~~~~~~~ - -.. [1] Lindley, E.J., & Mayers, J. Cywinski, R. (Ed.). (1988). Experimental method and corrections to data. United Kingdom: Adam Hilger. - https://inis.iaea.org/search/search.aspx?orig_q=RN:20000574 -.. [2] V.F. Sears (1975): Slow-neutron multiple scattering, `Advances in Physics <http://dx.doi.org/10.1080/00018737500101361>`__, 24:1, 1-45 -.. [3] A.K.Soper, W.S.Howells and A.C.Hannon *ATLAS - Analysis of Time-of-Flight Diffraction Data from Liquid and Amorphous Samples* Rutherford Appleton Laboratory Report (1989): `RAL-89-046 <http://wwwisis2.isis.rl.ac.uk/disordered/Manuals/ATLAS/ATLAS%20manual%20v1.0.pdf>`__ -.. [4] J.Schelten & W.Schmatz, J.Appl.Cryst. 13(1980)385-390 -.. [5] J.R.D.Copley J.Appl.Cryst 21(1988)639-644 - -.. categories:: Concepts diff --git a/docs/source/concepts/ProjectRecovery.rst b/docs/source/concepts/ProjectRecovery.rst index 8142b360cc8e2ce13603e005145428aa07fe92ad..4d56b42af95eff3d91d2ff0ac650d299f3a7b1d3 100644 --- a/docs/source/concepts/ProjectRecovery.rst +++ b/docs/source/concepts/ProjectRecovery.rst @@ -29,6 +29,13 @@ You can choose to attempt a full recovery, to open a recovery script or not to a If full project recovery runs successfully the scripting window will remain open in MantidPlot. It is safe to close this after a recovery. +If Project Recovery fails to run successfully it will open a GUI that will give users the option to select a specific checkpoint, try the last checkpoint again, open a specific checkpoint in a script window or start mantid normally without any project recovery occuring. (See image) + +.. image:: ../images/ProjectRecoveryFailureDialog.png + :width: 400px + :align: center + :alt: alternate text + **NB** This is an early version of project recovery. We think that it is a lot better than nothing, but we know it won't always work. Known caveats are listed below. Moreover, we would sincerely appreciate feedback and input from users. Contact us at `mantid-help@mantidproject.org <mailto:mantid-help@mantidproject.org>`__. The settings for project recovery, including switiching the feature on/off, and how to set them, are listed at the bottom of this page. @@ -45,10 +52,6 @@ Caveats * Affects: SANS interface, SetSample algorithm * Cause: Currently the history writer does not serialise Python dictionaries correctly. -* If full project recovery does not work: - * If the project recovery process has managed to create a script of the ordered workspace histories, this will appear in MantidPlot, it will have a red arrow beside the line where the process failed. In many cases it is possible to edit the script by hand to get it to run. - * If project recovery did not manage to generate the ordered history script then it will return to MantidPlot as normal, with an error message. - Settings -------- diff --git a/docs/source/concepts/TableWorkspaces.rst b/docs/source/concepts/TableWorkspaces.rst index 925bda03de49c2421c808684c57c233180965f52..08cb5b0619124368500c81f1ecbc555563873136 100644 --- a/docs/source/concepts/TableWorkspaces.rst +++ b/docs/source/concepts/TableWorkspaces.rst @@ -169,11 +169,11 @@ Working with Table Workspaces in C++ Table workspaces can be created using the workspace factory: -``ITableWorkspace_sptr table = WorkspaceFactory::Instance().createTable("TableWorkspace");`` +``ITableWorkspace_sptr table = WorkspaceFactory::Instance().createTable("TableWorkspace");`` Columns are added using the addColumn method: -| ``table->addColumn("str","Parameter Name");`` +| ``table->addColumn("str","Parameter Name");`` | ``table->addColumn("double","Value");`` | ``table->addColumn("double","Error");`` | ``table->addColumn("int","Index");`` @@ -224,26 +224,26 @@ Table rows Cells with the same index form a row. TableRow class represents a row. Use getRow(int) or getFirstRow() to access existing rows. For example: -.. code-block:: c +.. code-block:: c++ - std::string key; - double value; - TableRow row = table->getFirstRow(); + std::string key; + double value; + TableRow row = table->getFirstRow(); do { -   row >> key >> value; -   std::cout << "key=" << key << " value=" << value << std::endl; + row >> key >> value; + std::cout << "key=" << key << " value=" << value << std::endl; } while(row.next()); TableRow can also be use for writing into a table: -.. code-block:: c +.. code-block:: c++ - for(int i=0; i < n; ++i) + for(int i=0; i < n; ++i) { - TableRow row = table->appendRow(); - row << keys[i] << values[i]; + TableRow row = table->appendRow(); + row << keys[i] << values[i]; } Defining new column types diff --git a/docs/source/concepts/calibration/CalFile.rst b/docs/source/concepts/calibration/CalFile.rst index e590c38f48b44124c9d5d121bfc12fdbb5bb673b..503a59383ba54dd4172cf6c8ce7ac6d28317bc12 100644 --- a/docs/source/concepts/calibration/CalFile.rst +++ b/docs/source/concepts/calibration/CalFile.rst @@ -33,30 +33,26 @@ The file is a simple text file with the following format  351     201003  0.0535027  1    3             ... -The first column is simply an index, the second is a UDET identifier for -the detector, the third column corresponds to an offset in -:math:`\delta d/d` (this is usually applied in Mantid using the -:ref:`AlignDetectors <algm-AlignDetectors>` algorithm). The fourth column is a -flag to indicate whether the detector is selected and should be used in -:ref:`DiffractionFocussing <algm-DiffractionFocussing>`. The fifth column -indicates the group this detector belongs to (number >=1), zero is not -considered as a group. - -Masking -------- - -The most important columns for masking are the **UDET** and the -**select** column. The **number**, **offset** and **group** entries are -ignored. - -Algorithms that work with CalFiles ----------------------------------- - -Using CalFiles -############## - -* :ref:`AlignDetectors <algm-AlignDetectors>` will apply the offset from a CalFile while converting the data from time of flight to wavelength. -* :ref:`DiffractionFocussing <algm-DiffractionFocussing>` uses the selection and grouping columns of a CalFile to focus the diffraction data. +- ``number``: ignored +- ``UDET``: detector ID +- ``offset``: calibration offset used in :ref:`AlignDetectors + <algm-AlignDetectors>`. Comes from the ``OffsetsWorkspace``, or 0.0 + if none is given. +- ``select``: 1 if selected (use the pixel). Comes from the ``MaskWorkspace``, + or 1 if none is given. +- ``group``: what group to focus to in + :ref:`DiffractionFocussing <algm-DiffractionFocussing>`. Comes from the + ``GroupingWorkspace``, or 1 if none is given. Setting the group + as 0 specifies the detector as not to be grouped, effectively masking it. + +See :ref:`here <Powder Diffraction Calibration>` for information on +CalFile in the greater context of time-of-flight powder diffraction +calibration. + +Related algorithms +------------------ + +In addition to algorithms mentioned in :ref:`powder diffraction calibration <Powder Diffraction Calibration>` there are some algorithms specifically for CalFiles. Loading and saving CalFiles ########################### @@ -66,12 +62,9 @@ Loading and saving CalFiles * :ref:`MaskWorkspaceToCalFile<algm-MaskWorkspaceToCalFile>` will save a mask workspace as a CalFile. * :ref:`ReadGroupsFromFile<algm-ReadGroupsFromFile>` Reads the groups from a CalFile, and output a 2D workspace containing on the Y-axis the values of the Group each detector belongs to. This is used to visualize the grouping scheme for powder diffractometers. - Creating CalFiles ################# -* :ref:`CalibrateRectangularDetectors<algm-CalibrateRectangularDetectors>` will output a CalFile if the SaveAs property is set to "Calibration". -* :ref:`GetDetectorOffsets<algm-GetDetectorOffsets>` will output a CalFile if the GroupingFilename is set. * :ref:`CreateCalFileByNames<algm-CreateCalFileByNames>` will create a CalFile with the grouping column set according to a list of bank names. * :ref:`CreateDummyCalFile<algm-CreateDummyCalFile>` creates a CalFile from a workspace. All of the offsets will be zero, and the pixels will be all grouped into one group. diff --git a/docs/source/concepts/calibration/PowderDiffractionCalibration.rst b/docs/source/concepts/calibration/PowderDiffractionCalibration.rst index d8b7917cac7b43f6966c449167e936fd5b7e3a80..b8926f6dc0651004f78fd72c16cc7d016f79995b 100644 --- a/docs/source/concepts/calibration/PowderDiffractionCalibration.rst +++ b/docs/source/concepts/calibration/PowderDiffractionCalibration.rst @@ -1,50 +1,127 @@ .. _Powder Diffraction Calibration: -Powder Diffraction Calibration -============================== - +Time-of-Flight Powder Diffraction Calibration +============================================= + .. contents:: :local: -Calculating Calibration ------------------------ - Data Required -############# - -You will need to run a sample which will create good clean peaks at known reference dSpacing positions. To get a good calibration you will want good statistics on this calibration data. - -Steps for rectangular detector based instruments -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -If you are trying to calibrate an instrument that uses rectangular detectors the SNS have created a single algorithm to do all of this for you, :ref:`CalibrateRectangularDetectors <algm-CalibrateRectangularDetectors>`. At the time of writing this only supports SNS instruments, but look at the algorithm link for an up to date list. - -Steps for other instruments -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -1. Load the calibration data using :ref:`Load <algm-Load>` -2. Convert the X-Units to d-spacing using :ref:`ConvertUnits <algm-ConvertUnits>` -3. Run :ref:`Rebin <algm-Rebin>` to set a common d-spacing bin structure across all of the spectra, you will need fine enough bins to allow fitting of your peak. Perhaps 0.001 as a rebin step, whatever you choose, make a note of it you may need it later. -4. Take a look at the data using the SpectrumVeiwer or a color map plot, find the workspace index of a spectrum that has the peak close to the known reference position. This will be the reference spectra and will be used in the next step. -5. Here the steps can split depending on whether you are calibrating on a single peak, or a range of peaks - +------------- - * **Single Peak calibration** (this requires the same peak in all detectors) +To get a good calibration you will want good statistics with +calibration data. For most of the calibration algorithms, this means +having enough statistics in a single pixel to fit individual peaks or +to cross-correlate data. Some of the calibration algorithms also +require knowing the ideal positions of peaks in d-spacing. For these +algorithms, it is not uncommen to calibrate the instrument, refine the +results using a Rietveld program, then using the updated peak +positions to calibrate again. Often the sample selected will be diamond. - 6. Cross correlate the spectrum with :ref:`CrossCorrelate <algm-CrossCorrelate>`, enter the workspace index as the ReferenceSpectra you found in the last step. - 7. Run :ref:`GetDetectorOffsets <algm-GetDetectorOffsets>`, the InputWorkspace if the output from :ref:`CrossCorrelate <algm-CrossCorrelate>`. Use the rebinning step you made a note of in step 3 as the step parameter, and DReference as the expected value of the reference peak that you are fitting to. XMax and XMin define the window around the reference peak to search for the peak in each spectra, if you find that some spectra do not find the peak try increasing those values. - 8. The output is an OffsetsWorspace, that can be used directly in :ref:`DiffractionFocussing <algm-DiffractionFocussing>`, or saved using :ref:`SaveCalFile <algm-SaveCalFile>`. You can also save it as a :ref:`CalFile` from :ref:`GetDetectorOffsets <algm-GetDetectorOffsets>`, by defining the GroupingFileName parameter. - - * **Multi Peak Calibration** (*This is less well tested*) - If you do not have a single peak in all detectors, but a range of known peaks across detectors you can try this approach. Another possible approach is to perform the single peak calibration across sections of the instrument with different reference peaks and combine the output calibration. - - 6. Run :ref:`GetDetOffsetsMultiPeaks <algm-GetDetOffsetsMultiPeaks>`, the Input workspace is the one from step 3 earlier. For DReference you can enter a comma separated list of the d-spacing values of the known peaks. - 7. The output is an OffsetsWorspace, and a workspace with the number of peaks found in each spectra, The output offsets workspace that can be used directly in :ref:`DiffractionFocussing <algm-DiffractionFocussing>`, or saved using :ref:`SaveCalFile <algm-SaveCalFile>`. You can also save it as a :ref:`CalFile` from :ref:`GetDetOffsetsMultiPeaks <algm-GetDetOffsetsMultiPeaks>`, by defining the GroupingFileName parameter. +Calculating Calibration +----------------------- +Relative to a specific spectrum +############################### + +This technique of calibration uses a reference spectrum to calibrate +the rest of the instrument to. The main algorithm that does this is +:ref:`GetDetectorOffsets <algm-GetDetectorOffsets>` whose +``InputWorkspace`` is the ``OutputWorkspace`` of :ref:`CrossCorrelate +<algm-CrossCorrelate>`. Generically the workflow is + +1. :ref:`Load <algm-Load>` the calibration data +2. Convert the X-Units to d-spacing using :ref:`ConvertUnits + <algm-ConvertUnits>`. The "offsets" calculated are relative + reference spectrum's geometry so using :ref:`AlignDetectors + <algm-AlignDetectors>` will violate the assumptions for other + algorithms used with time-of-flight powder diffraction and give the + wrong results for focused data. +3. Run :ref:`Rebin <algm-Rebin>` to set a common d-spacing bin + structure across all of the spectra, you will need fine enough bins + to allow fitting of your peak. Whatever you choose, make a note of + it you may need it later. +4. Take a look at the data using the SpectrumViewer or a color map + plot, find the workspace index of a spectrum that has the peak + close to the known reference position. This will be the reference + spectra and will be used in the next step. +5. Cross correlate the spectrum with :ref:`CrossCorrelate + <algm-CrossCorrelate>`, enter the workspace index as the + ``ReferenceSpectra`` you found in the last step. +6. Run :ref:`GetDetectorOffsets <algm-GetDetectorOffsets>`, the + InputWorkspace if the output from :ref:`CrossCorrelate + <algm-CrossCorrelate>`. Use the rebinning step you made a note of + in step 3 as the step parameter, and DReference as the expected + value of the reference peak that you are fitting to. XMax and XMin + define the window around the reference peak to search for the peak + in each spectra, if you find that some spectra do not find the peak + try increasing those values. +7. The output is an ``OffsetsWorspace``. See below for how to save, + load, and use the workspace. + +Using known peak positions +########################## + +These techniques require knowing the precise location, in d-space, of +diffraction peaks and benefit from knowing +more. :ref:`GetDetOffsetsMultiPeaks <algm-GetDetOffsetsMultiPeaks>` +and :ref:`PDCalibration <algm-PDCalibration>` are the main choices for +this. Both algorithms fit individual peak positions and use those fits +to generate the calibration information. + +The workflow for :ref:`GetDetOffsetsMultiPeaks +<algm-GetDetOffsetsMultiPeaks>` is identical to that of +:ref:`GetDetectorOffsets <algm-GetDetectorOffsets>` without the +cross-correlation step (5). The main difference in the operation of +the algorithm is that it essentially calculates an offset from each +peak then calculates a weighted average of those offsets for the +individual spectrum. + +The workflow for :ref:`PDCalibration <algm-PDCalibration>` differs +significantly from that of the other calibration techniques. It +requires the data to be in time-of-flight, then uses either the +instrument geometry, or a previous calibration, to convert the peak +positions to time-of-flight. The individual peaks fits are then used +to calculate :math:`DIFC` values directly. The benefit of this method, is +that it allows for calibrating starting from a "good" calibration, +rather than returning back to the instrument geometry. The steps for +using this are + +1. :ref:`Load <algm-Load>` the calibration data +2. Run :ref:`PDCalibration <algm-PDCalibration>` with appropriate + properties +3. The ``OutputCalibrationTable`` is a :ref:`TableWorkspace <Table Workspaces>`. See + below for how to save, load, and use the workspace. + + +Workflow algorithms +################### + +:ref:`CalibrateRectangularDetectors <algm-CalibrateRectangularDetectors>` +will do most of the workflow for you, including applying the +calibration to the data. While its name suggests it is only for a +particular subset of detector types, it is not. It has many options +for selecting between :ref:`GetDetectorOffsets +<algm-GetDetectorOffsets>` and :ref:`GetDetOffsetsMultiPeaks +<algm-GetDetOffsetsMultiPeaks>`. + +Saving and Loading Calibration +############################## + +There are two basic formats for the calibration information. The +legacy ascii format is described in :ref:`CalFile`. The newer HDF5 +version is described alongside the description of :ref:`calibration +table <DiffractionCalibrationWorkspace>`. + +Saving and loading the HDF5 format is done with :ref:`SaveDiffCal +<algm-SaveDiffCal>` and :ref:`LoadDiffCal <algm-LoadDiffCal>`. + +Saving and loading the legacy format is done with :ref:`SaveCalFile +<algm-SaveCalFile>` and :ref:`LoadCalFile <algm-LoadCalFile>`. This +can be converted from an ``OffsetsWorkspace`` to a calibration table +using :ref:`ConvertDiffCal <algm-ConvertDiffCal>`. -Additionally :ref:`PDCalibration <algm-PDCalibration>` can be used to fit peaks directly in TOF instead of converting to d-spacing. This algorithm produces a calibration table which can be passed to :ref:`AlignDetectors <algm-AlignDetectors>`. The algorithms :ref:`LoadDiffCal <algm-LoadDiffCal>` and :ref:`SaveDiffCal <algm-SaveDiffCal>` can be used to read and write the calibration table to file. - .. figure:: /images/PG3_Calibrate.png :width: 400px :align: right @@ -56,36 +133,63 @@ Testing your Calibration :width: 400px :align: right -You will need to test that the calibration managed to find a reasonable offset for each of the spectra in your data. -The easiest way to do this is to apply the calibration to your calibration data and check that the bragg peaks align as expected. +The first thing that should be done is to convert the calibration +workspace (either table or ``OffsetsWorkspace`` to a workspace of +:math:`DIFC` values to inspect using the :py:obj:`instrument view +<mantidplot.InstrumentView>`. This can be done using +:ref:`CalculateDIFC <algm-CalculateDIFC>`. The values of :math:`DIFC` +should vary continuously across the detectors that are close to each +other (e.g. neighboring pixels in an LPSD). + +You will need to test that the calibration managed to find a +reasonable calibration constant for each of the spectra in your data. +The easiest way to do this is to apply the calibration to your +calibration data and check that the bragg peaks align as expected. + +1. Load the calibration data using :ref:`Load <algm-Load>` +2. Run :ref:`AlignDetectors <algm-AlignDetectors>`, this will convert the data to d-spacing and apply the calibration. You can provide the calibration using the ``CalibrationFile``, the ``CalibrationWorkspace``, or ``OffsetsWorkspace``. +3. Plot the workspace as a Color Fill plot, in the spectrum view, or a few spectra in a line plot. -1. Load the calibration data using :ref:`Load <algm-Load>` -2. Run :ref:`AlignDetectors <algm-AlignDetectors>`, this will convert the data to d-spacing and apply the calibration. You can provide the calibration either by defining the OffsetsWrokspace, or by providing the path to the saved :ref:`CalFile`. -3. Plot the workspace as a Color Fill plot, or a few spectra as a line plot. +Further insight can be gained by comparing the grouped (after aligning +and focussing the data) spectra from a previous calibration or convert +units to the newly calibrated version. This can be done using +:ref:`AlignAndFocusPowder <algm-AlignAndFocusPowder>` with and without +calibration information. In the end, a Rietveld refinement is the best +test of the calibration. -Applying your calibration -------------------------- +Expanding on detector masking +----------------------------- -During Focussing -################ +While many of the calibration methods will generate a mask based on the detectors calibrated, sometimes additional metrics for masking are desired. One way is to use :ref:`DetectorDiagnostic <algm-DetectorDiagnostic>`. The result can be combined with an existing mask using -The calibration can be applied as part of the reduction and processing workflow using the two algorithms +.. code:: + + BinaryOperateMasks(InputWorkspace1='mask_from_cal', InputWorkspace2='mask_detdiag', + OperationType='OR', OutputWorkspace='mask_final') + +Creating detector grouping +-------------------------- + +To create a grouping workspace for :ref:`SaveDiffCal +<algm-SaveDiffCal>` you need to specify which detector pixels to +combine to make an output spectrum. This is done using +:ref:`CreateGroupingWorkspace <algm-CreateGroupingWorkspace>`. An +alternative is to generate a grouping file to load with +:ref:`LoadDetectorsGroupingFile <algm-LoadDetectorsGroupingFile>`. -1. Load the experimental data using :ref:`Load <algm-Load>` -2. Run :ref:`AlignDetectors <algm-AlignDetectors>`, this will convert the data to d-spacing and apply the calibration. You can provide the calibration either by defining the OffsetsWrokspace, or by providing the path to the saved :ref:`CalFile`. -3. Run :ref:`DiffractionFocussing <algm-DiffractionFocussing>` with the output from AlignDetectors as the input. This will group the detectors according to the GroupingWorkspace or CalFile. Adjusting the Instrument Definition -################################### +----------------------------------- This approach attempts to correct the instrument component positions based on the calibration data. It can be more involved than applying the correction during focussing. 1. Perform a calibration using :ref:`CalibrateRectangularDetectors <algm-CalibrateRectangularDetectors>` or :ref:`GetDetOffsetsMultiPeaks <algm-GetDetOffsetsMultiPeaks>`. Only these algorithms can export the :ref:`Diffraction Calibration Workspace <DiffractionCalibrationWorkspace>` required. 2. Run :ref:`AlignComponents <algm-AlignComponents>` this will move aspects of the instrument to optimize the offsets. It can move any named aspect of the instrument including the sample and source positions. You will likely need to run this several times, perhaps focussing on a single bank at a time, and then the source and sample positions in order to get a good alignment. 3. Then either: - * :ref:`ExportGeometry <algm-ExportGeometry>` will export the resulting geometry into a format that can be used to create a new XML instrument definition. The Mantid team at ORNL have tools to automate this for common instruments at the SNS. + + * :ref:`ExportGeometry <algm-ExportGeometry>` will export the resulting geometry into a format that can be used to create a new XML instrument definition. The Mantid team at ORNL have tools to automate this for some instruments at the SNS. * At ISIS enter the resulting workspace as the calibration workspace into the DAE software when recording new runs. The calibrated workspace will be copied into the resulting NeXuS file of the run. - + .. categories:: Calibration diff --git a/docs/source/diagrams/ILLSANS-v1_absorber_wkflw.dot b/docs/source/diagrams/ILLSANS-v1_absorber_wkflw.dot new file mode 100644 index 0000000000000000000000000000000000000000..36642ff42c0985f185fe6397ad1fe98208595996 --- /dev/null +++ b/docs/source/diagrams/ILLSANS-v1_absorber_wkflw.dot @@ -0,0 +1,29 @@ +digraph Absorber { + $global_style + + subgraph decisions { + $decision_style + NormaliseBy + } + + subgraph params { + $param_style + Run + } + + subgraph values { + $value_style + OutputWorkspace + } + + subgraph algorithms { + $algorithm_style + LoadAndMerge + Divide + } + + Run -> LoadAndMerge + LoadAndMerge -> NormaliseBy + NormaliseBy -> Divide + Divide -> OutputWorkspace +} diff --git a/docs/source/diagrams/ILLSANS-v1_beam_wkflw.dot b/docs/source/diagrams/ILLSANS-v1_beam_wkflw.dot new file mode 100644 index 0000000000000000000000000000000000000000..aa7108fe6a41c7ef994142e05d876f396968e3ef --- /dev/null +++ b/docs/source/diagrams/ILLSANS-v1_beam_wkflw.dot @@ -0,0 +1,58 @@ +digraph Beam { + $global_style + + subgraph decisions { + $decision_style + NormaliseBy + DirectBeam + } + + subgraph params { + $param_style + Run + BeamRadius + AbsorberInputWorkspace + } + + subgraph values { + $value_style + BeamCenterX + BeamCenterY + BeamFluxValue + BeamFluxError + OutputWorkspace + } + + subgraph algorithms { + $algorithm_style + LoadAndMerge + Divide + Minus + FindCenterOfMassPosition + MoveInstrumentComponent + } + + subgraph process { + $process_style + IntegrateInRadius + } + + Run -> LoadAndMerge + LoadAndMerge -> NormaliseBy + NormaliseBy -> Divide + Divide -> Minus + AbsorberInputWorkspace -> Minus + Minus -> FindCenterOfMassPosition + BeamRadius -> FindCenterOfMassPosition + FindCenterOfMassPosition -> DirectBeam + DirectBeam -> BeamCenterX + DirectBeam -> BeamCenterY + BeamCenterX -> MoveInstrumentComponent + BeamCenterY -> MoveInstrumentComponent + BeamRadius -> IntegrateInRadius + MoveInstrumentComponent -> IntegrateInRadius + IntegrateInRadius -> BeamFluxValue + IntegrateInRadius -> BeamFluxError + BeamFluxValue -> OutputWorkspace + BeamFluxError -> OutputWorkspace +} diff --git a/docs/source/diagrams/ILLSANS-v1_container_wkflw.dot b/docs/source/diagrams/ILLSANS-v1_container_wkflw.dot new file mode 100644 index 0000000000000000000000000000000000000000..5e309ee97c75d298200523692e6f704890e5b2cc --- /dev/null +++ b/docs/source/diagrams/ILLSANS-v1_container_wkflw.dot @@ -0,0 +1,43 @@ +digraph Container { + $global_style + + subgraph decisions { + $decision_style + NormaliseBy + } + + subgraph params { + $param_style + Run + TransmissionInputWorkspace + BeamInputWorkspace + AbsorberInputWorkspace + } + + subgraph values { + $value_style + OutputWorkspace + } + + subgraph algorithms { + $algorithm_style + LoadAndMerge + Divide + Minus + MoveInstrumentComponent + ApplyTransmissionCorrection + SolidAngle + } + + Run -> LoadAndMerge + LoadAndMerge -> NormaliseBy + NormaliseBy -> Divide + Divide -> Minus + AbsorberInputWorkspace -> Minus + Minus -> MoveInstrumentComponent + BeamInputWorkspace -> MoveInstrumentComponent + MoveInstrumentComponent -> SolidAngle [label="Divide"] + SolidAngle -> ApplyTransmissionCorrection + TransmissionInputWorkspace -> ApplyTransmissionCorrection + ApplyTransmissionCorrection -> OutputWorkspace +} diff --git a/docs/source/diagrams/ILLSANS-v1_reference_wkflw.dot b/docs/source/diagrams/ILLSANS-v1_reference_wkflw.dot new file mode 100644 index 0000000000000000000000000000000000000000..4ad46ef82db51c757e5e90921525fddf90cf569f --- /dev/null +++ b/docs/source/diagrams/ILLSANS-v1_reference_wkflw.dot @@ -0,0 +1,59 @@ +digraph Reference { + $global_style + + subgraph decisions { + $decision_style + NormaliseBy + } + + subgraph params { + $param_style + Run + AbsorberInputWorkspace + ContainerInputWorkspace + MaskedInputWorkspace + BeamInputWorkspace + TransmissionInputWorkspace + SampleThickness + } + + subgraph values { + $value_style + OutputWorkspace + SensitivityOutputWorkspace + } + + subgraph algorithms { + $algorithm_style + LoadAndMerge + Divide + Minus + MoveInstrumentComponent + ApplyTransmissionCorrection + SolidAngle + Minus2 [label="Minus"] + CalculateEfficiency + MaskDetectors + NormaliseByThickness + } + + Run -> LoadAndMerge + LoadAndMerge -> NormaliseBy + NormaliseBy -> Divide + Divide -> Minus + AbsorberInputWorkspace -> Minus + Minus -> MoveInstrumentComponent + BeamInputWorkspace -> MoveInstrumentComponent + MoveInstrumentComponent -> SolidAngle [label="Divide"] + SolidAngle -> ApplyTransmissionCorrection + TransmissionInputWorkspace -> ApplyTransmissionCorrection + ApplyTransmissionCorrection -> Minus2 + ContainerInputWorkspace -> Minus2 + Minus2 -> MaskDetectors + MaskedInputWorkspace -> MaskDetectors + MaskDetectors -> NormaliseByThickness + SampleThickness -> NormaliseByThickness + NormaliseByThickness -> CalculateEfficiency + CalculateEfficiency -> SensitivityOutputWorkspace + NormaliseByThickness -> OutputWorkspace +} diff --git a/docs/source/diagrams/ILLSANS-v1_sample_wkflw.dot b/docs/source/diagrams/ILLSANS-v1_sample_wkflw.dot new file mode 100644 index 0000000000000000000000000000000000000000..190b5ecf925305109f8778179715c6174afa1aa5 --- /dev/null +++ b/docs/source/diagrams/ILLSANS-v1_sample_wkflw.dot @@ -0,0 +1,75 @@ +digraph Sample { + $global_style + + subgraph decisions { + $decision_style + NormaliseBy + AbsoluteScaleBy + } + + subgraph params { + $param_style + Run + BeamInputWorkspace + TransmissionInputWorkspace + AbsorberInputWorkspace + ContainerInputWorkspace + SensitivityInputWorkspace + ReferenceInputWorkspace + MaskedInputWorkspace + SampleThickness + } + + subgraph values { + $value_style + OutputWorkspace + } + + subgraph algorithms { + $algorithm_style + LoadAndMerge + Divide + Minus + MoveInstrumentComponent + ApplyTransmissionCorrection + SolidAngle + Minus2 [label="Minus"] + MaskDetectors + NormaliseByThickness + Divide2 [label="Divide"] + Divide3 [label="Divide"] + Divide4 [label="Divide"] + } + + subgraph process { + $process_style + ScaleByFluxFactor + } + + Run -> LoadAndMerge + LoadAndMerge -> NormaliseBy + NormaliseBy -> Divide + Divide -> Minus + AbsorberInputWorkspace -> Minus + Minus -> MoveInstrumentComponent + BeamInputWorkspace -> MoveInstrumentComponent + MoveInstrumentComponent -> SolidAngle [label="Divide"] + SolidAngle -> ApplyTransmissionCorrection + TransmissionInputWorkspace -> ApplyTransmissionCorrection + ApplyTransmissionCorrection -> Minus2 + ContainerInputWorkspace -> Minus2 + Minus2 -> MaskDetectors + MaskedInputWorkspace -> MaskDetectors + MaskDetectors -> NormaliseByThickness + SampleThickness -> NormaliseByThickness + NormaliseByThickness -> AbsoluteScaleBy + AbsoluteScaleBy -> Divide2 [label="Beam Flux"] + BeamInputWorkspace -> Divide2 + Divide2 -> Divide3 + SensitivityInputWorkspace -> Divide3 + Divide3 -> ScaleByFluxFactor + AbsoluteScaleBy -> Divide4 [label="Standard Sample"] + ReferenceInputWorkspace -> Divide4 + Divide4 -> ScaleByFluxFactor + ScaleByFluxFactor -> OutputWorkspace +} diff --git a/docs/source/diagrams/ILLSANS-v1_transmission_wkflw.dot b/docs/source/diagrams/ILLSANS-v1_transmission_wkflw.dot new file mode 100644 index 0000000000000000000000000000000000000000..284309a90a527357ec916008d01c04ea7009e893 --- /dev/null +++ b/docs/source/diagrams/ILLSANS-v1_transmission_wkflw.dot @@ -0,0 +1,47 @@ +digraph Transmission { + $global_style + + subgraph decisions { + $decision_style + NormaliseBy + } + + subgraph params { + $param_style + Run + BeamRadius + AbsorberInputWorkspace + BeamInputWorkspace + } + + subgraph values { + $value_style + TransmissionValue + TransmissionError + OutputWorkspace + } + + subgraph algorithms { + $algorithm_style + LoadAndMerge + Divide + Minus + CalculateTransmission + MoveInstrumentComponent + } + + Run -> LoadAndMerge + LoadAndMerge -> NormaliseBy + NormaliseBy -> Divide + Divide -> Minus + AbsorberInputWorkspace -> Minus + Minus -> MoveInstrumentComponent + BeamInputWorkspace -> MoveInstrumentComponent + MoveInstrumentComponent -> CalculateTransmission + BeamRadius -> CalculateTransmission + BeamInputWorkspace -> CalculateTransmission + CalculateTransmission -> TransmissionValue + CalculateTransmission -> TransmissionError + TransmissionValue -> OutputWorkspace + TransmissionError -> OutputWorkspace +} diff --git a/docs/source/diagrams/ReflectometryILLConvertToQ-v1_wkflw.dot b/docs/source/diagrams/ReflectometryILLConvertToQ-v1_wkflw.dot index 739abe9d913df91ab3827fa469c7fc86c135bdd7..8116537fa2ebac73b08371c49c901ffeb6e91d57 100644 --- a/docs/source/diagrams/ReflectometryILLConvertToQ-v1_wkflw.dot +++ b/docs/source/diagrams/ReflectometryILLConvertToQ-v1_wkflw.dot @@ -6,10 +6,7 @@ digraph ReflectometryILLConvertToQ { $param_style input [label="InputWorkspace"] output [label="OutputWorkspace"] - direct [label="DirectBeamWorkspace"] direct_foreground [label="DirectForegroundWorkspace"] - reflected [label="ReflectedBeamWorkspace"] - polarized [label="Polarized"] q_grouping [label="GroupingQFraction"] } @@ -30,9 +27,6 @@ digraph ReflectometryILLConvertToQ { } input -> refl_to_q - reflected -> refl_to_q - direct -> refl_to_q - polarized -> refl_to_q refl_to_q -> refl_to_point_data refl_to_point_data -> refl_group_points refl_group_points -> if_direct_foreground diff --git a/docs/source/diagrams/ReflectometryILLSumForeground-v1_wkflw.dot b/docs/source/diagrams/ReflectometryILLSumForeground-v1_wkflw.dot index 54d7b1ad03b318215dcaaf2bf7de59ed124bd7ce..5cea57ee0413b3c08cbf1f08f669b823c3cc851f 100644 --- a/docs/source/diagrams/ReflectometryILLSumForeground-v1_wkflw.dot +++ b/docs/source/diagrams/ReflectometryILLSumForeground-v1_wkflw.dot @@ -6,13 +6,13 @@ digraph ReflectometryILLSumForeground { $param_style input [label="InputWorkspace"] output [label="OutputWorkspace"] + direct [label="DirectBeamWorkspace"] l_direct_foreground [label="DirectForegroundWorkspace"] q_direct_foreground [label="DirectForegroundWorkspace"] l_foreground_centre [label="Foreground\ncentre"] q_foreground_centre [label="Foreground\ncentre"] l_foreground_widths [label="Foreground\nwidths"] q_foreground_widths [label="Foreground\nwidths"] - flat_sample [label="FlatSample"] wavelength_range [label="WavelengthRange"] } @@ -24,18 +24,26 @@ digraph ReflectometryILLSumForeground { sum_foreground_wavelength [label="Sum foreground spectra\nin wavelength"] l_rebin [label="Rebin reflected to direct"] q_rebin [label="Rebin reflected to direct"] - divide_after [label="Divide"] divide_before [label="Divide"] + statistics [label="ReflectometryBeamStatistics"] } subgraph decisions { $decision_style - if_direct_foreground [label="Is DirectForeground\navailable?"] - which_sum_type [label="SummationType?"] + is_rebin_required [label="Summing direct\nor reflected?"] + is_statistics_required [label="Summing direct\nor reflected?"] + which_sum_type [label="SummationType?"] } - input -> which_sum_type + input -> is_statistics_required + + is_statistics_required -> statistics [label="Reflected"] + + direct -> statistics + statistics -> which_sum_type + + is_statistics_required -> extract_spectrum [label="Direct"] which_sum_type -> q_rebin [label="SumInQ"] q_direct_foreground -> q_rebin @@ -44,18 +52,17 @@ digraph ReflectometryILLSumForeground { divide_before -> sum_foreground_q q_foreground_centre -> sum_foreground_q q_foreground_widths -> sum_foreground_q - flat_sample -> sum_foreground_q sum_foreground_q -> crop which_sum_type -> extract_spectrum [label="SumInLambda"] l_foreground_centre -> extract_spectrum extract_spectrum -> sum_foreground_wavelength l_foreground_widths -> sum_foreground_wavelength - sum_foreground_wavelength -> if_direct_foreground + sum_foreground_wavelength -> is_rebin_required - if_direct_foreground -> crop [label="No"] + is_rebin_required -> crop [label="Direct"] - if_direct_foreground -> l_rebin [label="Yes"] + is_rebin_required -> l_rebin [label="Reflected"] l_direct_foreground -> l_rebin l_rebin -> crop diff --git a/docs/source/images/AbsorptionVolume.png b/docs/source/images/AbsorptionVolume.png new file mode 100644 index 0000000000000000000000000000000000000000..2f19e5ab56f60c90dbe8b4ec18af823299481859 Binary files /dev/null and b/docs/source/images/AbsorptionVolume.png differ diff --git a/docs/source/images/BASISPowderDiffraction_2.png b/docs/source/images/BASISPowderDiffraction_2.png new file mode 100644 index 0000000000000000000000000000000000000000..94b736895255d0faf930d369ae7ec1e9b4d64d7d Binary files /dev/null and b/docs/source/images/BASISPowderDiffraction_2.png differ diff --git a/docs/source/images/BASISPowderDiffraction_3.png b/docs/source/images/BASISPowderDiffraction_3.png new file mode 100644 index 0000000000000000000000000000000000000000..015af51bbb360d88f48c0d9a421cf1f367ba19b5 Binary files /dev/null and b/docs/source/images/BASISPowderDiffraction_3.png differ diff --git a/docs/source/images/ProjectRecoveryDialog.png b/docs/source/images/ProjectRecoveryDialog.png index f21af61f7e938786301e92de7e97f516987efddb..faa599400d7dd9976a2f976326c32c10ab2f268f 100644 Binary files a/docs/source/images/ProjectRecoveryDialog.png and b/docs/source/images/ProjectRecoveryDialog.png differ diff --git a/docs/source/images/ProjectRecoveryFailureDialog.png b/docs/source/images/ProjectRecoveryFailureDialog.png new file mode 100644 index 0000000000000000000000000000000000000000..beafb8ed955c67d52e8a92a665c992ee5726d071 Binary files /dev/null and b/docs/source/images/ProjectRecoveryFailureDialog.png differ diff --git a/docs/source/images/SpecularReflectionCorrection_figure1.png b/docs/source/images/SpecularReflectionCorrection_figure1.png new file mode 100644 index 0000000000000000000000000000000000000000..aecabc082a3ed9dce5e548bb84f6d802e993f996 Binary files /dev/null and b/docs/source/images/SpecularReflectionCorrection_figure1.png differ diff --git a/docs/source/images/SpecularReflectionCorrection_figure2.png b/docs/source/images/SpecularReflectionCorrection_figure2.png new file mode 100644 index 0000000000000000000000000000000000000000..1327e3f9f2664505e4d8f3adeb73bb13f7be105f Binary files /dev/null and b/docs/source/images/SpecularReflectionCorrection_figure2.png differ diff --git a/docs/source/images/SpecularReflectionCorrection_figure3.png b/docs/source/images/SpecularReflectionCorrection_figure3.png new file mode 100644 index 0000000000000000000000000000000000000000..8156be43ce129c93d9e36c9acb5ec8800af6e5d1 Binary files /dev/null and b/docs/source/images/SpecularReflectionCorrection_figure3.png differ diff --git a/docs/source/interfaces/HFIR Single Crystal Reduction.rst b/docs/source/interfaces/HFIR Single Crystal Reduction.rst index cccd52f341aaaf33e46d0e638300907ad60a04b0..7bb829e97a5fffed81215c720f0f4c4b6e4a767f 100644 --- a/docs/source/interfaces/HFIR Single Crystal Reduction.rst +++ b/docs/source/interfaces/HFIR Single Crystal Reduction.rst @@ -140,6 +140,36 @@ Here is a typical use case to merge all the measuring points (Pt.) in a scan 9. User goes to MantidPlot to view the merged scan by SliceView or Vates. +Workflow to calculate peak intensity of a single measurement scan ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +It is a common practice to have one measurement in a scan for a peak. The only reason to do so is that the signal is too weak for users to have enough beam time to have multiple measurements in a single peak's scan. Therefore, it is not straight forward to calculate peak intensity from this type of measurement. + +1. A user specifies the range of scan numbers that include all the neutron peaks regardless whether they are single-point measurement or multiple-points measurement. + +2. The user pushes button *Survey*. Mantid will load all the SPICE files of the scans. + +3. The users selects *All* single-point-measurement scans by checking *Single Pt Scans*. + +4. The user launches single-point peak integration window from menu *Peak Integraton* and sub menu *Single Pt Integration*. Mantid will add all the single-point scans to the popped out window. + +5. The user will be prompted with the information about how to map single-point scan to a complete nuclear peak scan with same 2theta. + +6. The user may load a CSV file to which integrated peak table was exported previously. + +7. The user pushes button *Retrieve FWHM*. Mantid then will gather FWHM value from integrated peak. + +8. The user add mapped scans for each single-pt scan to peak processint tab, and integrate peaks. + +9. The user pushes *Retrieve FHWHM* again to make sure every scan to have an FWHM. + +10. The user pushes *Integrate* to integrate peaks. + +11. The user pushes *Export to Table* to add the integrated peaks to peak processing table for final output. + + + + Peak Integration with automatic background subtraction by approximation +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/docs/source/interfaces/ISIS Reflectometry.rst b/docs/source/interfaces/ISIS Reflectometry.rst index cc19a4e12687ad9876a8abb0ddbdcc7549208bda..b20f2fab93624916583a67a5741bca4a33b2cfb2 100644 --- a/docs/source/interfaces/ISIS Reflectometry.rst +++ b/docs/source/interfaces/ISIS Reflectometry.rst @@ -336,8 +336,7 @@ Columns | | | column will prevail over options specified | | | | in the **Settings** tab. | | | | | -| | | Example: ``StrictSpectrumChecking=0,`` | -| | | ``RegionOfDirectBeam="0,2", Params="1,2,3"`` | +| | | Example: ``RegionOfDirectBeam="0,2", Params="1,2,3"`` | +---------------------+-----------+---------------------------------------------------------------------------------+ Search Interface diff --git a/docs/source/release/v3.14.0/diffraction.rst b/docs/source/release/v3.14.0/diffraction.rst index ee0d0173a635f9627c376a4419843884f63327c8..3925285e2a3656c5b28440e8219ec15e5949809a 100644 --- a/docs/source/release/v3.14.0/diffraction.rst +++ b/docs/source/release/v3.14.0/diffraction.rst @@ -22,6 +22,8 @@ Improvements - :ref:`LoadWAND <algm-LoadWAND>` has grouping option added and loads faster - Mask workspace option added to :ref:`WANDPowderReduction <algm-WANDPowderReduction>` - :ref:`Le Bail concept page <Le Bail Fit>` moved from mediawiki +- Rework of :ref:`powder diffraction calibration <Powder Diffraction Calibration>` documentation + Single Crystal Diffraction -------------------------- @@ -29,11 +31,12 @@ Single Crystal Diffraction Improvements ############ -- :ref:`IntegratePeaksProfileFitting <algm-IntegratePeaksProfileFitting>` now supports MaNDi, TOPAZ, and CORELLI. Other instruments can easily be added as well. In addition, the algorithm can now automatically generate a strong peaks library is one is not provided. +- :ref:`IntegratePeaksProfileFitting <algm-IntegratePeaksProfileFitting>` now supports MaNDi, TOPAZ, and CORELLI. Other instruments can easily be added as well. In addition, the algorithm can now automatically generate a strong peaks library is one is not provided. Peakshapes will be learned to improve initial guesses as the strong peak library is generated. - :ref:`MDNormSCD <algm-MDNormSCD>` now can handle merged MD workspaces. - :ref:`StartLiveData <algm-StartLiveData>` will load "live" data streaming from TOPAZ new Adara data server. - :ref:`IntegratePeaksMD <algm-IntegratePeaksMD>` with Cylinder=True now has improved fits using BackToBackExponential and IkedaCarpenterPV functions. +- :ref:`SaveIsawPeaks <algm-SaveIsawPeaks>` now has option to renumber peaks sequentially. Bugfixes ######## @@ -53,13 +56,16 @@ Improvements - Focusing on Gem now crops values that would be divided by very small or zero vanadium values - Removed save_angles flag for Gem , as it was set by the texture mode. - Added save_all flag to Gem that is set to true by default, setting it to false disables the saving of .NXS files. +- Added subtract_empty_instrument flag to Gem that is true by default, setting it to false disables subrtracting the empty. - Changed spline coefficient so that the default for long_mode on and long_mode off can be set separately. +- Focus on Pearl now saves out xye_tof files. Bugfixes ######## - multiple_scattering flag is now optional for Polaris focus when absorb_correction is true. - Normalisation is fixed in :ref:`SumOverlappingTubes <algm-SumOverlappingTubes>`, which was causing very low peak to background ratio for reduced D2B data. +- sudden drops at either end of spectra in Pearl caused by partial bins are now cropped. New ### diff --git a/docs/source/release/v3.14.0/direct_inelastic.rst b/docs/source/release/v3.14.0/direct_inelastic.rst index 114e886692a50709adcf093e28a9addf9763a3c6..c4f908a56f392d16d5f5ea95aaee28d3a280b4f6 100644 --- a/docs/source/release/v3.14.0/direct_inelastic.rst +++ b/docs/source/release/v3.14.0/direct_inelastic.rst @@ -47,5 +47,14 @@ Improvements - :ref:`LoadDNSSCD <algm-LoadDNSSCD>` has been improved to be able to load TOF data. - :ref:`MDNormDirectSC <algm-MDNormDirectSC>` now can handle merged MD workspaces. +Python +------ + + +Improved +######## + +- The ``directtools`` plotting and utility module has been updated with improved automatic E ranges, cut labels and other visuals. All functions now should also be applicable to non-ILL data as well. + :ref:`Release 3.14.0 <v3.14.0>` diff --git a/docs/source/release/v3.14.0/framework.rst b/docs/source/release/v3.14.0/framework.rst index 33c4989a7cdc89e3ef5187de7ec9233f804ade2d..2e38b727d875632f8c257548cad24e3a01786d9e 100644 --- a/docs/source/release/v3.14.0/framework.rst +++ b/docs/source/release/v3.14.0/framework.rst @@ -39,7 +39,9 @@ Algorithms New Algorithms ############## +- :ref:`CalculateDynamicRange <algm-CalculateDynamicRange>` will calculate the Q range of a SANS workspace. - :ref:`MatchSpectra <algm-MatchSpectra>` is an algorithm that calculates factors to match all spectra to a reference spectrum. +- :ref:`MaskBinsIf <algm-MaskBinsIf>` is an algorithm to mask bins according to criteria specified as a muparser expression. Improvements ############ @@ -49,16 +51,17 @@ Improvements - :ref:`SumOverlappingTubes <algm-SumOverlappingTubes>` will produce histogram data, and will not split the counts between bins by default. - :ref:`SumSpectra <algm-SumSpectra>` has an additional option, ``MultiplyBySpectra``, which controls whether or not the output spectra are multiplied by the number of bins. This property should be set to ``False`` for summing spectra as PDFgetN does. - :ref:`Live Data <algm-StartLiveData>` for events with ``PreserveEvents=True`` now produces workspaces that have bin boundaries which encompass the total x-range (TOF) for all events across all spectra if the data was not binned during the process step. -- Bugfix in :ref:`ConvertToMatrixWorkspace <algm-ConvertToMatrixWorkspace>` with ``Workspace2D`` as the ``InputWorkspace`` not being cloned to the ``OutputWorkspace``. Added support for ragged workspaces. - :ref:`RebinToWorkspace <algm-RebinToWorkspace>` now checks if the ``WorkspaceToRebin`` and ``WorkspaceToMatch`` already have the same binning. Added support for ragged workspaces. - :ref:`GroupWorkspaces <algm-GroupWorkspaces>` supports glob patterns for matching workspaces in the ADS. - :ref:`LoadSampleShape <algm-LoadSampleShape-v1>` now supports loading from binary .stl files. - :ref:`MaskDetectorsIf <algm-MaskDetectorsIf>` now supports masking a workspace in addition to writing the masking information to a calfile. +- :ref:`LoadSampleShape <algm-LoadSampleShape-v1>` now supports loading from binary .stl files. Bugfixes ######## - :ref:`SaveGDA <algm-SaveGDA>` Now takes a parameter of OutputFilename instead of Filename to better match with similar algorithms. +- Bugfix in :ref:`ConvertToMatrixWorkspace <algm-ConvertToMatrixWorkspace>` with ``Workspace2D`` as the ``InputWorkspace`` not being cloned to the ``OutputWorkspace``. Added support for ragged workspaces. - :ref:`SolidAngle <algm-SolidAngle-v1>` Now properly accounts for a given StartWorkspaceIndex. - :ref:`FilterEvents <algm-FilterEvents-v1>` output workspaces now contain the goniometer. - Fixed an issue where if a workspace's history wouldn't update for some algorithms @@ -92,6 +95,9 @@ Improvements ############ - :ref:`ChudleyElliot <func-ChudleyElliot>` includes hbar in the definition +- :ref:`Functions <FitFunctionsInPython>` may now have their constraint penalties for fitting set in python using ``function.setConstraintPenaltyFactor("parameterName", double)``. +- :py:obj:`mantid.kernel.Logger` now handles unicode in python2 + Bugfixes ######## diff --git a/docs/source/release/v3.14.0/indirect_inelastic.rst b/docs/source/release/v3.14.0/indirect_inelastic.rst index ca75179f5671a264e951c52d99542598ee32c2a9..12ed7eec892a94b0dbb580cbb559f6db2f6f9057 100644 --- a/docs/source/release/v3.14.0/indirect_inelastic.rst +++ b/docs/source/release/v3.14.0/indirect_inelastic.rst @@ -16,7 +16,9 @@ New Algorithms ############## - :ref:`BASISCrystalDiffraction <algm-BASISCrystalDiffraction>` replaces :ref:`BASISDiffraction <algm-BASISDiffraction>`, now deprecated. - :ref:`BASISPowderDiffraction <algm-BASISPowderDiffraction>` obtains scattered elastic intensity versus momentum transfer and versus scattering angle. +- TOF correction for neutrons incorrectly interpreted as slow neutrons in :ref:`BASISPowderDiffraction <algm-BASISPowderDiffraction>` - Deprecated algorithm BASISReduction311 has been removed. +- :ref:`LoadEMU <algm-LoadEMU>` loader for an ANSTO EMU backscattering event file. :ref:`Release 3.14.0 <v3.14.0>` @@ -45,6 +47,9 @@ Bugfixes option in ConvFit. - An unexpected crash is prevented when Plot Current Preview is clicked when no data is loaded. A meaningful error message is now displayed. +- The Probability Density Functions (PDF) workspaces for the FABADA minimiser in ConvFit no longer overwrite each other. + Various other improvements in the display of the FABADA PDF's have also been finished. +- The expression for the Fit type Yi in MSDFit was incorrect and has now been corrected. Data Corrections Interface @@ -70,3 +75,16 @@ Improvements - Added 'Default' detector grouping option in ISISEnergyTransfer for TOSCA, to allow a default grouping using the grouping specified in the Instrument Parameter File. - ISISEnergyTransfer now allows overlapping detector grouping. +- The Run button has been moved to be above the output options. The run button, save button and plotting options + are now disabled while a tab is running or plotting. +- It is now possible to choose which spectrum to Plot Output for in the S(Q,w) Tab. + + +Bayes Interface +--------------- + +Improvements +############ + +- The Run button is now above the output options. +- The Run, Plot and Save buttons are now disabled while running and plotting is taking place. diff --git a/docs/source/release/v3.14.0/reflectometry.rst b/docs/source/release/v3.14.0/reflectometry.rst index dafa700c7abd9ee2ee66f6464f2ae7645e27e040..ba3612c69c3f58db844b244b5a96a8b2eadadb48 100644 --- a/docs/source/release/v3.14.0/reflectometry.rst +++ b/docs/source/release/v3.14.0/reflectometry.rst @@ -40,6 +40,7 @@ Bug fixes ######### - Fixed the error propagation in :math:`Q` grouping in :ref:`ReflectometryILLConvertToQ <algm-ReflectometryILLConvertToQ>` +- Handling of group workspaces containing single workspaces when scaling by period and using :literal:`ScaleFactorFromPeriod`, i.e. :literal:`UseManualScaleFactors` is true, :literal:`ManualScaleFactors` remains empty. Liquids Reflectometer --------------------- @@ -67,7 +68,7 @@ Improved Bug fixes ######### - +- The SaveASCII tab from the interface was unable to save in some places on Windows and that has now been fixed. Algorithms ---------- @@ -76,26 +77,22 @@ Algorithms New ### - +- Some computations from :ref:`algm-ReflectometryMomentumTransfer` were extracted to a new algorithm, :ref:`algm-ReflectometryBeamStatistics`. Improved ######## - :ref:`algm-ReflectometryReductionOneAuto` now supports the Wildes method for polarization corrections as well as Fredrikze when configured in the parameters file. +- :ref:`algm-ReflectometryReductionOne`, :ref:`algm-ReflectometryReductionOneAuto`, :ref:`algm-CreateTransmissionWorkspace` and :ref:`algm-CreateTransmissionWorkspaceAuto` now use spectrum numbers for their processing instructions instead of workspace indcies +- :ref:`algm-ReflectometryReductionOne` and :ref:`algm-ReflectometryReductionOneAuto` Now take a parameter to pass processing instructions to the transmission workspace algorithms and no longer accept strict spectrum checking - Common naming of slit component name and size properties across algorithms. +- :ref:`algm-SpecularReflectionPositionCorrect` is now compatible with the reflectometers at ILL. Bug fixes ######### - A bug has been fixed on the Settings tab where the IncludePartialBins check box had been hidden by a misplaced text entry box. - :ref:`algm-ReflectometryReductionOneAuto` No longer sums all of a transmission run's workspaces and instead will use the first run only - -Algorithms ----------- - -Bug fixes -######### - - In :ref:`algm-ReflectometryReductionOneAuto` an issue where if you gave only one of either MomentumTransferMax or MomentumTransferMin were specified it would be ignored, this has been fixed. :ref:`Release 3.14.0 <v3.14.0>` diff --git a/docs/source/release/v3.14.0/sans.rst b/docs/source/release/v3.14.0/sans.rst index ab3adf9d92ae946f36038e034f588a74494ecbc7..156c0f64fabe75f5dfe29a67e92d0e589193a762 100644 --- a/docs/source/release/v3.14.0/sans.rst +++ b/docs/source/release/v3.14.0/sans.rst @@ -5,11 +5,14 @@ SANS Changes .. contents:: Table of Contents :local: -ISIS SANS Interface -------------------- New ### +- :ref:`SANSILLReduction <algm-SANSILLReduction>` performs common SANS reduction for ILL instruments. + +ISIS SANS Interface +------------------- + Improved ######## * Updated workspace naming scheme for new backend. @@ -25,6 +28,7 @@ Bug fixes * Fixed an issue where the run tab was difficult to select. * Changed the geometry names from CylinderAxisUP, Cuboid and CylinderAxisAlong to Cylinder, FlatPlate and Disc * The GUI now correctly resets to default values when a new user file is loaded. +* The GUI no longer hangs whilst searching the archive for files. * Updated the options and units displayed in wavelength and momentum range combo boxes. * Fixed a bug which crashed the beam centre finder if a phi mask was set. diff --git a/docs/source/release/v3.14.0/ui.rst b/docs/source/release/v3.14.0/ui.rst index 8f7fd9e7eccb3c083e9ef7e87a8dd40e1b62d21b..8d205869b012af2dadba9183f56146469a702a43 100644 --- a/docs/source/release/v3.14.0/ui.rst +++ b/docs/source/release/v3.14.0/ui.rst @@ -24,7 +24,17 @@ Project Recovery ---------------- New ### --Project recovery can now make a recovery checkpoint on command using mantidplot.app.saveRecoveryCheckpoint() in either the interpreter or script windows in python +- Project recovery can now make a recovery checkpoint on command using mantidplot.app.saveRecoveryCheckpoint() in either the interpreter or script windows in python +- If project recovery fails when attempting to recover a checkpoint it will open a new GUI offering multiple checkpoints to the user and the ability to open them in a script window. (See image below) + +.. figure:: ../../images/ProjectRecoveryFailureDialog.png + :class: screenshot + :align: right + :figwidth: 70% + +- Project Recovery can now make a recovery checkpoint on command using mantidplot.app.saveRecoveryCheckpoint() in either the interpreter or script windows in python +- Project Recovery now adds a lock file at the start of saving so if MantidPlot crashes when saving it will no longer use that checkpoint as it is incomplete. + Changes ####### @@ -32,6 +42,15 @@ Changes - MantidPlot no longer checks for the existence of files in the "Recent Files" menu. Fixes case where files on slow mounted network drives can cause a lag on MantidPlot startup. - Workspaces now save locally as a number of how many workspaces have already been saved instead of workspace names - Project Recovery will now attempt to recover multiple instances of mantid that are ran at the same time. +- The project recovery prompt on mantid restart is improved and shows which checkpoint you will be getting. (See image below) + +.. figure:: ../../images/ProjectRecoveryDialog.png + :class: screenshot + :align: right + :figwidth: 70% + +- Project Recovery will now output less unhelpful logging information into the results log + Bugfixes ######## @@ -39,6 +58,8 @@ Bugfixes - Project Recovery will actually recover fully cases where multiple workspaces were passed as a list to an algorithm (Fixes a known bug with GroupWorkspaces as well) - Project Recovery will now run normally when you select no or the recovery fails when recovering from a ungraceful exit. - When autosaving or saving a recovery checkpoint with the Instrument View open the results log would be filled with excess logging and no longer does this. +- Fixed an issue where Project Recovery would start regardless of the config options +- If an empty group workspace is present in the ADS it will no longer crash the save thread of project recovery and instead will delete it from the ADS and ignore it. MantidPlot ---------- diff --git a/docs/source/release/v3.9.0/framework.rst b/docs/source/release/v3.9.0/framework.rst index c769e352c7be287cc6e14814316b815ada6858c9..5e3c01f3eb64f20b67e7c59001fc7716b1ef9658 100644 --- a/docs/source/release/v3.9.0/framework.rst +++ b/docs/source/release/v3.9.0/framework.rst @@ -76,7 +76,6 @@ Deprecated - :ref:`OneStepMDEW <algm-OneStepMDEW>`. - :ref:`QueryAllRemoteJobs <algm-QueryAllRemoteJobs>` is deprecated in favour of v2. - :ref:`RefinePowderInstrumentParameters <algm-RefinePowderInstrumentParameters>` is deprecated in favour of v2. -- :ref:`SetupILLD33Reduction <algm-SetupILLD33Reduction>`. - :ref:`StartRemoteTransaction <algm-StartRemoteTransaction>` is deprecated in favour of v2. - :ref:`LoadILLAscii <algm-LoadILLDiffraction>`. - :ref:`StopRemoteTransaction <algm-StopRemoteTransaction>` is deprecated in favour of v2. @@ -107,7 +106,7 @@ CurveFitting - `Exclude` is new property of :ref:`Fit <algm-Fit>`, which allows for a user defined range to be excluded from a fit. - System tests and Fitting Benchmarks have been added for testing the minimizer, the scripts generate the tables displayed on :ref:`FittingMinimzers page <FittingMinimizers>`. This System tests also demo how these tables can be created as a standard Mantid script. -The work on benchmarking fitting has received funding from the Horizon 2020 Framework +The work on benchmarking fitting has received funding from the Horizon 2020 Framework Programme of the European Union under the SINE2020 project Grant No 654000 Improved diff --git a/docs/source/techniques/ISISPowder-Tutorials.rst b/docs/source/techniques/ISISPowder-Tutorials.rst index 1a64317e62998f910a91434428d20419a7306101..8639e872ab0d07c24a7c18e0e30ce9951d78bbb1 100644 --- a/docs/source/techniques/ISISPowder-Tutorials.rst +++ b/docs/source/techniques/ISISPowder-Tutorials.rst @@ -44,7 +44,12 @@ no prior knowledge or usage of the scripts and a fresh install of Mantid. Copying instrument example files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Open your Mantid install location, by default this -will be `C:\\MantidInstall` on Windows and `/opt/Mantid` on Linux. +will be `C:\\MantidInstall` on Windows, `/opt/Mantid` on Linux, and `/Applications/MantidPlot.app/` on OSX. + +.. note:: In Finder on OSX to see files inside the MantidPlot application you + need to right click on the MantidPlot application then choose "Show Package + Contents" + Open *scripts* > *Diffraction* > *isis_powder*. In these tutorials we will be using Polaris examples and data however you may set-up a different instrument. diff --git a/images/MantidWidgets.qrc b/images/instrumentview.qrc similarity index 100% rename from images/MantidWidgets.qrc rename to images/instrumentview.qrc diff --git a/instrument/CORELLI_Parameters.xml b/instrument/CORELLI_Parameters.xml index 1fde9c8970ce8946ff84970c19103b0c7095d366..ff949ed18cf63be09a2a51107416aec142da0d56 100644 --- a/instrument/CORELLI_Parameters.xml +++ b/instrument/CORELLI_Parameters.xml @@ -20,16 +20,6 @@ <value val="true"/> </parameter> - <!-- Multiplier for profile fitting for BVG polar angle --> - <parameter name="sigX0Scale"> - <value val="2." /> - </parameter> - - <!-- Multiplier for profile fitting for BVG azimuthal angle --> - <parameter name="sigY0Scale"> - <value val="2." /> - </parameter> - <!-- Number of rows between detector gaps for profile fitting --> <parameter name="numDetRows" type="int"> <value val="255" /> @@ -52,7 +42,7 @@ <!-- Fraction along (h,k,l) to use for profile fitting. 0.5 is the next peak. --> <parameter name="fracHKL"> - <value val="0.4" /> + <value val="0.25" /> </parameter> <!-- Side length of each voxel for fitting in units of angstrom^-1 --> @@ -75,25 +65,19 @@ <value val="10" /> </parameter> - <!-- Constraints for ICC fitting. Valid names are iccA, iccB, iccR, iccT0, iccScale0 - iccHatWidth and iccKConv. Inputs are strings with values separated by - spaces which are prased by the IntegratePeaksProfileFitting algorithm. - If two values are given they are treated as the lower and upper bounds. If - three are given they are the lower bound, upper bound, and initial guess.--> - <parameter name="iccA" type="string"> - <value val="0.25 0.75 0.5" /> - </parameter> - - <parameter name="iccB" type="string"> - <value val="0.001 0.3 0.005" /> + <!-- Initial guess parameters for coshPeakWidthModel --> + <parameter name="sigSC0Params" type="string"> + <value val="0.00413132 1.54103839 1.0 -0.00266634" /> </parameter> - <parameter name="iccR" type="string"> - <value val="0.05 0.15 0.1" /> + <!-- Initial guess for sigma along the azimuthal direction (rad) --> + <parameter name="sigAZ0"> + <value val="0.0025" /> </parameter> - <parameter name="iccKConv" type="string"> - <value val="10.0 800.0 100.0" /> + <!-- Initial guess parameters for fsigP (BVG covariance) --> + <parameter name="sigP0Params" type="string"> + <value val="0.1460775 1.85816592 0.26850086 -0.00725352" /> </parameter> </component-link> diff --git a/instrument/EMUau_Definition.xml b/instrument/EMUau_Definition.xml new file mode 100644 index 0000000000000000000000000000000000000000..2ab511131e2eb302b1b241659a6b2e375ac95b06 --- /dev/null +++ b/instrument/EMUau_Definition.xml @@ -0,0 +1,607 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- WARNING: this file was automatically generated and any changes will be lost when it is regenerated. --> +<!-- see http://www.mantidproject.org/IDF --> +<instrument + xmlns="http://www.mantidproject.org/IDF/1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.mantidproject.org/IDF/1.0 http://schema.mantidproject.org/IDF/1.0/IDFSchema.xsd" + name="EMUau" + valid-from="1901-01-01 00:00:00"> + <!-- DEFAULTS --> + <defaults> + <length unit="meter"/> + <angle unit="degree"/> + <indirect-neutronic-positions/> + <reference-frame> + <along-beam axis="z"/> + <pointing-up axis="y"/> + <handedness val="right"/> + </reference-frame> + <default-view axis-view="z-"/> + </defaults> + <!-- SOURCE --> + <type name="DopplerDrive" is="Source"/> + <component type="DopplerDrive"> + <location x="0.0" y="0.0" z="-2.035"/> + </component> + <!-- SAMPLE --> + <type name="SampleHolder" is="SamplePos"/> + <component type="SampleHolder"> + <location x="0.0" y="0.0" z="0.0"/> + </component> + <!-- DIRECT VERTICAL DETECTORS --> + <type name="VerticalPixel" is="Detector"> + <cylinder id="cyl"> + <centre-of-bottom-base x="0.0" y="-0.00195312" z="0.0"/> + <axis x="0.0" y="1.0" z="0.0"/> + <radius val="0.00635"/> + <height val="0.00390625"/> + </cylinder> + <algebra val="cyl"/> + </type> + <type name="VerticalTube" outline="no"> + <component type="VerticalPixel"> + <location y="-0.125000000"> + <neutronic y="-0.125000000"/> + </location> + <location y="-0.121093750"> + <neutronic y="-0.121093750"/> + </location> + <location y="-0.117187500"> + <neutronic y="-0.117187500"/> + </location> + <location y="-0.113281250"> + <neutronic y="-0.113281250"/> + </location> + <location y="-0.109375000"> + <neutronic y="-0.109375000"/> + </location> + <location y="-0.105468750"> + <neutronic y="-0.105468750"/> + </location> + <location y="-0.101562500"> + <neutronic y="-0.101562500"/> + </location> + <location y="-0.097656250"> + <neutronic y="-0.097656250"/> + </location> + <location y="-0.093750000"> + <neutronic y="-0.093750000"/> + </location> + <location y="-0.089843750"> + <neutronic y="-0.089843750"/> + </location> + <location y="-0.085937500"> + <neutronic y="-0.085937500"/> + </location> + <location y="-0.082031250"> + <neutronic y="-0.082031250"/> + </location> + <location y="-0.078125000"> + <neutronic y="-0.078125000"/> + </location> + <location y="-0.074218750"> + <neutronic y="-0.074218750"/> + </location> + <location y="-0.070312500"> + <neutronic y="-0.070312500"/> + </location> + <location y="-0.066406250"> + <neutronic y="-0.066406250"/> + </location> + <location y="-0.062500000"> + <neutronic y="-0.062500000"/> + </location> + <location y="-0.058593750"> + <neutronic y="-0.058593750"/> + </location> + <location y="-0.054687500"> + <neutronic y="-0.054687500"/> + </location> + <location y="-0.050781250"> + <neutronic y="-0.050781250"/> + </location> + <location y="-0.046875000"> + <neutronic y="-0.046875000"/> + </location> + <location y="-0.042968750"> + <neutronic y="-0.042968750"/> + </location> + <location y="-0.039062500"> + <neutronic y="-0.039062500"/> + </location> + <location y="-0.035156250"> + <neutronic y="-0.035156250"/> + </location> + <location y="-0.031250000"> + <neutronic y="-0.031250000"/> + </location> + <location y="-0.027343750"> + <neutronic y="-0.027343750"/> + </location> + <location y="-0.023437500"> + <neutronic y="-0.023437500"/> + </location> + <location y="-0.019531250"> + <neutronic y="-0.019531250"/> + </location> + <location y="-0.015625000"> + <neutronic y="-0.015625000"/> + </location> + <location y="-0.011718750"> + <neutronic y="-0.011718750"/> + </location> + <location y="-0.007812500"> + <neutronic y="-0.007812500"/> + </location> + <location y="-0.003906250"> + <neutronic y="-0.003906250"/> + </location> + <location y="+0.000000000"> + <neutronic y="+0.000000000"/> + </location> + <location y="+0.003906250"> + <neutronic y="+0.003906250"/> + </location> + <location y="+0.007812500"> + <neutronic y="+0.007812500"/> + </location> + <location y="+0.011718750"> + <neutronic y="+0.011718750"/> + </location> + <location y="+0.015625000"> + <neutronic y="+0.015625000"/> + </location> + <location y="+0.019531250"> + <neutronic y="+0.019531250"/> + </location> + <location y="+0.023437500"> + <neutronic y="+0.023437500"/> + </location> + <location y="+0.027343750"> + <neutronic y="+0.027343750"/> + </location> + <location y="+0.031250000"> + <neutronic y="+0.031250000"/> + </location> + <location y="+0.035156250"> + <neutronic y="+0.035156250"/> + </location> + <location y="+0.039062500"> + <neutronic y="+0.039062500"/> + </location> + <location y="+0.042968750"> + <neutronic y="+0.042968750"/> + </location> + <location y="+0.046875000"> + <neutronic y="+0.046875000"/> + </location> + <location y="+0.050781250"> + <neutronic y="+0.050781250"/> + </location> + <location y="+0.054687500"> + <neutronic y="+0.054687500"/> + </location> + <location y="+0.058593750"> + <neutronic y="+0.058593750"/> + </location> + <location y="+0.062500000"> + <neutronic y="+0.062500000"/> + </location> + <location y="+0.066406250"> + <neutronic y="+0.066406250"/> + </location> + <location y="+0.070312500"> + <neutronic y="+0.070312500"/> + </location> + <location y="+0.074218750"> + <neutronic y="+0.074218750"/> + </location> + <location y="+0.078125000"> + <neutronic y="+0.078125000"/> + </location> + <location y="+0.082031250"> + <neutronic y="+0.082031250"/> + </location> + <location y="+0.085937500"> + <neutronic y="+0.085937500"/> + </location> + <location y="+0.089843750"> + <neutronic y="+0.089843750"/> + </location> + <location y="+0.093750000"> + <neutronic y="+0.093750000"/> + </location> + <location y="+0.097656250"> + <neutronic y="+0.097656250"/> + </location> + <location y="+0.101562500"> + <neutronic y="+0.101562500"/> + </location> + <location y="+0.105468750"> + <neutronic y="+0.105468750"/> + </location> + <location y="+0.109375000"> + <neutronic y="+0.109375000"/> + </location> + <location y="+0.113281250"> + <neutronic y="+0.113281250"/> + </location> + <location y="+0.117187500"> + <neutronic y="+0.117187500"/> + </location> + <location y="+0.121093750"> + <neutronic y="+0.121093750"/> + </location> + </component> + </type> + <type name="VerticalPanel"> + <component type="VerticalTube"> + <location r="+0.150000" t="+164.450000" name="vt_16"/> + <location r="+0.167600" t="+160.210000" name="vt_17"/> + <location r="+0.150000" t="+155.960000" name="vt_18"/> + <location r="+0.167600" t="+151.720000" name="vt_19"/> + <location r="+0.150000" t="+147.470000" name="vt_20"/> + <location r="+0.167600" t="+143.230000" name="vt_21"/> + <location r="+0.150000" t="+138.980000" name="vt_22"/> + <location r="+0.167600" t="+134.740000" name="vt_23"/> + <location r="+0.150000" t="+130.490000" name="vt_24"/> + <location r="+0.167600" t="+126.250000" name="vt_25"/> + <location r="+0.150000" t="+122.000000" name="vt_26"/> + <location r="+0.167600" t="+117.760000" name="vt_27"/> + <location r="+0.150000" t="+113.510000" name="vt_28"/> + <location r="+0.167600" t="+109.270000" name="vt_29"/> + <location r="+0.150000" t="+105.020000" name="vt_30"/> + <location r="+0.167600" t="+100.780000" name="vt_31"/> + <location r="+0.150000" t="+96.530000" name="vt_32"/> + <location r="+0.167600" t="+92.290000" name="vt_33"/> + <location r="+0.150000" t="+88.040000" name="vt_34"/> + <location r="+0.167600" t="+83.800000" name="vt_35"/> + <location r="+0.150000" t="+79.550000" name="vt_36"/> + <location r="+0.167600" t="+75.310000" name="vt_37"/> + <location r="+0.150000" t="+71.060000" name="vt_38"/> + <location r="+0.167600" t="+66.820000" name="vt_39"/> + <location r="+0.150000" t="+62.570000" name="vt_40"/> + <location r="+0.167600" t="+58.330000" name="vt_41"/> + <location r="+0.150000" t="+54.080000" name="vt_42"/> + <location r="+0.167600" t="+49.840000" name="vt_43"/> + <location r="+0.150000" t="+45.590000" name="vt_44"/> + <location r="+0.167600" t="+41.350000" name="vt_45"/> + <location r="+0.150000" t="+37.100000" name="vt_46"/> + <location r="+0.167600" t="+32.860000" name="vt_47"/> + <location r="+0.150000" t="+28.610000" name="vt_48"/> + <location r="+0.167600" t="+24.370000" name="vt_49"/> + <location r="+0.150000" t="+20.120000" name="vt_50"/> + </component> + </type> + <component name="DVDetectors" type="VerticalPanel" idlist="direct_vids"> + <location/> + </component> + <idlist idname="direct_vids"> + <id start="4288" end="6527"/> + </idlist> + <component name="IDVDetectors" type="VerticalPanel" idlist="indirect_vids"> + <location/> + </component> + <idlist idname="indirect_vids"> + <id start="1024" end="3263"/> + </idlist> + <!-- DIRECT HORIZONTAL DETECTORS --> + <type name="HorizontalPixel" is="Detector"> + <cylinder id="cyl-hz"> + <centre-of-bottom-base x="0.0" y="-0.00117187" z="0.0"/> + <axis x="0.0" y="1.0" z="0.0"/> + <radius val="0.00635"/> + <height val="0.00234375"/> + </cylinder> + <algebra val="cyl-hz"/> + </type> + <type name="HorizontalTube" outline="no"> + <component type="HorizontalPixel"> + <location y="-0.075000000"> + <neutronic y="-0.075000000"/> + </location> + <location y="-0.072656250"> + <neutronic y="-0.072656250"/> + </location> + <location y="-0.070312500"> + <neutronic y="-0.070312500"/> + </location> + <location y="-0.067968750"> + <neutronic y="-0.067968750"/> + </location> + <location y="-0.065625000"> + <neutronic y="-0.065625000"/> + </location> + <location y="-0.063281250"> + <neutronic y="-0.063281250"/> + </location> + <location y="-0.060937500"> + <neutronic y="-0.060937500"/> + </location> + <location y="-0.058593750"> + <neutronic y="-0.058593750"/> + </location> + <location y="-0.056250000"> + <neutronic y="-0.056250000"/> + </location> + <location y="-0.053906250"> + <neutronic y="-0.053906250"/> + </location> + <location y="-0.051562500"> + <neutronic y="-0.051562500"/> + </location> + <location y="-0.049218750"> + <neutronic y="-0.049218750"/> + </location> + <location y="-0.046875000"> + <neutronic y="-0.046875000"/> + </location> + <location y="-0.044531250"> + <neutronic y="-0.044531250"/> + </location> + <location y="-0.042187500"> + <neutronic y="-0.042187500"/> + </location> + <location y="-0.039843750"> + <neutronic y="-0.039843750"/> + </location> + <location y="-0.037500000"> + <neutronic y="-0.037500000"/> + </location> + <location y="-0.035156250"> + <neutronic y="-0.035156250"/> + </location> + <location y="-0.032812500"> + <neutronic y="-0.032812500"/> + </location> + <location y="-0.030468750"> + <neutronic y="-0.030468750"/> + </location> + <location y="-0.028125000"> + <neutronic y="-0.028125000"/> + </location> + <location y="-0.025781250"> + <neutronic y="-0.025781250"/> + </location> + <location y="-0.023437500"> + <neutronic y="-0.023437500"/> + </location> + <location y="-0.021093750"> + <neutronic y="-0.021093750"/> + </location> + <location y="-0.018750000"> + <neutronic y="-0.018750000"/> + </location> + <location y="-0.016406250"> + <neutronic y="-0.016406250"/> + </location> + <location y="-0.014062500"> + <neutronic y="-0.014062500"/> + </location> + <location y="-0.011718750"> + <neutronic y="-0.011718750"/> + </location> + <location y="-0.009375000"> + <neutronic y="-0.009375000"/> + </location> + <location y="-0.007031250"> + <neutronic y="-0.007031250"/> + </location> + <location y="-0.004687500"> + <neutronic y="-0.004687500"/> + </location> + <location y="-0.002343750"> + <neutronic y="-0.002343750"/> + </location> + <location y="+0.000000000"> + <neutronic y="+0.000000000"/> + </location> + <location y="+0.002343750"> + <neutronic y="+0.002343750"/> + </location> + <location y="+0.004687500"> + <neutronic y="+0.004687500"/> + </location> + <location y="+0.007031250"> + <neutronic y="+0.007031250"/> + </location> + <location y="+0.009375000"> + <neutronic y="+0.009375000"/> + </location> + <location y="+0.011718750"> + <neutronic y="+0.011718750"/> + </location> + <location y="+0.014062500"> + <neutronic y="+0.014062500"/> + </location> + <location y="+0.016406250"> + <neutronic y="+0.016406250"/> + </location> + <location y="+0.018750000"> + <neutronic y="+0.018750000"/> + </location> + <location y="+0.021093750"> + <neutronic y="+0.021093750"/> + </location> + <location y="+0.023437500"> + <neutronic y="+0.023437500"/> + </location> + <location y="+0.025781250"> + <neutronic y="+0.025781250"/> + </location> + <location y="+0.028125000"> + <neutronic y="+0.028125000"/> + </location> + <location y="+0.030468750"> + <neutronic y="+0.030468750"/> + </location> + <location y="+0.032812500"> + <neutronic y="+0.032812500"/> + </location> + <location y="+0.035156250"> + <neutronic y="+0.035156250"/> + </location> + <location y="+0.037500000"> + <neutronic y="+0.037500000"/> + </location> + <location y="+0.039843750"> + <neutronic y="+0.039843750"/> + </location> + <location y="+0.042187500"> + <neutronic y="+0.042187500"/> + </location> + <location y="+0.044531250"> + <neutronic y="+0.044531250"/> + </location> + <location y="+0.046875000"> + <neutronic y="+0.046875000"/> + </location> + <location y="+0.049218750"> + <neutronic y="+0.049218750"/> + </location> + <location y="+0.051562500"> + <neutronic y="+0.051562500"/> + </location> + <location y="+0.053906250"> + <neutronic y="+0.053906250"/> + </location> + <location y="+0.056250000"> + <neutronic y="+0.056250000"/> + </location> + <location y="+0.058593750"> + <neutronic y="+0.058593750"/> + </location> + <location y="+0.060937500"> + <neutronic y="+0.060937500"/> + </location> + <location y="+0.063281250"> + <neutronic y="+0.063281250"/> + </location> + <location y="+0.065625000"> + <neutronic y="+0.065625000"/> + </location> + <location y="+0.067968750"> + <neutronic y="+0.067968750"/> + </location> + <location y="+0.070312500"> + <neutronic y="+0.070312500"/> + </location> + <location y="+0.072656250"> + <neutronic y="+0.072656250"/> + </location> + </component> + </type> + <type name="HorizontalPanel"> + <component type="HorizontalTube"> + <location x="-0.006000" y="+0.011400" z="-0.199600" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_00"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="+0.5*value+0.011400"/> + </parameter> + </location> + <location x="-0.006000" y="-0.011400" z="-0.199600" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_01"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="-0.5*value-0.011400"/> + </parameter> + </location> + <location x="-0.003200" y="+0.026300" z="-0.214300" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_02"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="+0.5*value+0.026300"/> + </parameter> + </location> + <location x="-0.003200" y="-0.026300" z="-0.214300" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_03"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="-0.5*value-0.026300"/> + </parameter> + </location> + <location x="-0.006000" y="+0.034320" z="-0.199600" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_04"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="+0.5*value+0.034320"/> + </parameter> + </location> + <location x="-0.006000" y="-0.034320" z="-0.199600" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_05"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="-0.5*value-0.034320"/> + </parameter> + </location> + <location x="-0.003200" y="+0.049970" z="-0.214300" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_06"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="+0.5*value+0.049970"/> + </parameter> + </location> + <location x="-0.003200" y="-0.049970" z="-0.214300" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_07"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="-0.5*value-0.049970"/> + </parameter> + </location> + <location x="-0.006000" y="+0.057240" z="-0.199600" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_08"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="+0.5*value+0.057240"/> + </parameter> + </location> + <location x="-0.006000" y="-0.057240" z="-0.199600" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_09"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="-0.5*value-0.057240"/> + </parameter> + </location> + <location x="-0.003200" y="+0.073640" z="-0.214300" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_10"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="+0.5*value+0.073640"/> + </parameter> + </location> + <location x="-0.003200" y="-0.073640" z="-0.214300" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_11"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="-0.5*value-0.073640"/> + </parameter> + </location> + <location x="-0.006000" y="+0.080160" z="-0.199600" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_12"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="+0.5*value+0.080160"/> + </parameter> + </location> + <location x="-0.006000" y="-0.080160" z="-0.199600" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_13"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="-0.5*value-0.080160"/> + </parameter> + </location> + <location x="-0.003200" y="+0.097310" z="-0.214300" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_14"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="+0.5*value+0.097310"/> + </parameter> + </location> + <location x="-0.003200" y="-0.097310" z="-0.214300" rot="90.0" axis-x="0.0" axis-y="0.0" axis-z="1.0" name="ht_15"> + <rot val="-10.800000" axis-x="1" axis-y="0" axis-z="0"/> + <parameter name="y"> + <logfile id="horizontal_tubes_gap" eq="-0.5*value-0.097310"/> + </parameter> + </location> + </component> + </type> + <component name="DHDetectors" type="HorizontalPanel" idlist="direct_hids"> + <location/> + </component> + <idlist idname="direct_hids"> + <id start="3264" end="4287"/> + </idlist> + <component name="IDHDetectors" type="HorizontalPanel" idlist="indirect_hids"> + <location/> + </component> + <idlist idname="indirect_hids"> + <id start="0" end="1023"/> + </idlist> +</instrument> \ No newline at end of file diff --git a/instrument/EMUau_Parameters.xml b/instrument/EMUau_Parameters.xml new file mode 100644 index 0000000000000000000000000000000000000000..5ef52a69d378fad15845245abf6763ccda8cc73d --- /dev/null +++ b/instrument/EMUau_Parameters.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<parameter-file instrument = "EMUau" valid-from = "1901-01-01T00:00:00"> + + <component-link name = "EMUau"> + + <parameter name="deltaE-mode" type="string"> + <value val="indirect"/> + </parameter> + + <parameter name="Efixed"> + <value val="2.08032" /> + </parameter> + + <parameter name="SourceSample"> + <value val="2.035" /> + </parameter> + + <parameter name="ChopperSource"> + <value val="1.715" /> + </parameter> + + <parameter name="SampleAnalyser"> + <value val="1.8" /> + </parameter> + + <parameter name="SampleVDetectorInner"> + <value val="0.15" /> + </parameter> + + <parameter name="SampleVDetectorOuter"> + <value val="0.1676" /> + </parameter> + + <parameter name="AnalysedV2"> + <value val="630.866" /> + </parameter> + + <!-- time limits in msec --> + <parameter name="DirectTauxMin"> + <value val="1.0" /> + </parameter> + + <parameter name="DirectTauxMax"> + <value val="4.45" /> + </parameter> + + <parameter name="AnalysedTauxMin"> + <value val="6.45" /> + </parameter> + + <parameter name="AnalysedTauxMax"> + <value val="10.9" /> + </parameter> + + <parameter name="DetectorMap" type="string"> + <value val="0,8,1,9,2,10,3,11,4,12,5,13,6,14,7,15,50-16" /> + </parameter> + + <parameter name="DopplerReferenceThreshold"> + <value val="-18.479" /> + </parameter> + + <parameter name="DopplerReferenceDelay"> + <value val="0.0538" /> + </parameter> + + </component-link> + +</parameter-file> \ No newline at end of file diff --git a/instrument/Facilities.xml b/instrument/Facilities.xml index 5807e78e65ce16e40ffb012bea442f8d31dca0a5..e86e44fcd124fa24b11473af7be59ce2facc2601 100644 --- a/instrument/Facilities.xml +++ b/instrument/Facilities.xml @@ -857,6 +857,12 @@ <instrument name="Bilby"> <technique>Small Angle Scattering</technique> </instrument> + + <instrument name="EMUau"> + <technique>Neutron Spectroscopy</technique> + <technique>Reactor Indirect Geometry Spectroscopy</technique> + </instrument> + </facility> <!-- HZB --> diff --git a/instrument/MANDI_Parameters.xml b/instrument/MANDI_Parameters.xml index c397a537c34e9dc03d5d4b110a4e359ed0d36d87..7bc7b52fb131504b93d40d570c9a110222cbcd49 100644 --- a/instrument/MANDI_Parameters.xml +++ b/instrument/MANDI_Parameters.xml @@ -45,7 +45,7 @@ <!-- Fraction along (h,k,l) to use for profile fitting. 0.5 is the next peak. --> <parameter name="fracHKL"> - <value val="0.4" /> + <value val="0.25" /> </parameter> <!-- Side length of each voxel for fitting in units of angstrom^-1 --> @@ -68,6 +68,21 @@ <value val="5" /> </parameter> +<!-- Initial guess parameters for coshPeakWidthModel --> +<parameter name="sigSC0Params" type="string"> + <value val="0.00413132 1.54103839 1.0 -0.00266634" /> +</parameter> + +<!-- Initial guess for sigma along the azimuthal direction (rad) --> +<parameter name="sigAZ0"> + <value val="0.0025" /> +</parameter> + +<!-- Initial guess parameters for fsigP (BVG covariance) --> +<parameter name="sigP0Params" type="string"> + <value val="0.1460775 1.85816592 0.26850086 -0.00725352" /> +</parameter> + </component-link> </parameter-file> diff --git a/instrument/MANDI_Parameters_2015_08_01.xml b/instrument/MANDI_Parameters_2015_08_01.xml index 6dae7f8e7df687429ec8cd6c39fa1844fbf9606e..0a7d157e577b526cd0ca2711ad39c188f2aaccf4 100644 --- a/instrument/MANDI_Parameters_2015_08_01.xml +++ b/instrument/MANDI_Parameters_2015_08_01.xml @@ -44,7 +44,7 @@ <!-- Fraction along (h,k,l) to use for profile fitting. 0.5 is the next peak. --> <parameter name="fracHKL"> - <value val="0.4" /> + <value val="0.25" /> </parameter> <!-- Side length of each voxel for fitting in units of angstrom^-1 --> @@ -67,5 +67,20 @@ <value val="5" /> </parameter> +<!-- Initial guess parameters for coshPeakWidthModel --> +<parameter name="sigSC0Params" type="string"> + <value val="0.00413132 1.54103839 1.0 -0.00266634" /> +</parameter> + +<!-- Initial guess for sigma along the azimuthal direction (rad) --> +<parameter name="sigAZ0"> + <value val="0.0025" /> +</parameter> + +<!-- Initial guess parameters for fsigP (BVG covariance) --> +<parameter name="sigP0Params" type="string"> + <value val="0.1460775 1.85816592 0.26850086 -0.00725352" /> +</parameter> + </component-link> </parameter-file> diff --git a/instrument/MANDI_Parameters_2016_02_01.xml b/instrument/MANDI_Parameters_2016_02_01.xml index a75eee6da310107446c1428e4e6ca0bc96f9f28d..15ca29c7c6ff159659caef0b5f32dd09ea77e254 100644 --- a/instrument/MANDI_Parameters_2016_02_01.xml +++ b/instrument/MANDI_Parameters_2016_02_01.xml @@ -44,7 +44,7 @@ <!-- Fraction along (h,k,l) to use for profile fitting. 0.5 is the next peak. --> <parameter name="fracHKL"> - <value val="0.4" /> + <value val="0.25" /> </parameter> <!-- Side length of each voxel for fitting in units of angstrom^-1 --> @@ -67,5 +67,20 @@ <value val="5" /> </parameter> +<!-- Initial guess parameters for coshPeakWidthModel --> +<parameter name="sigSC0Params" type="string"> + <value val="0.00413132 1.54103839 1.0 -0.00266634" /> +</parameter> + +<!-- Initial guess for sigma along the azimuthal direction (rad) --> +<parameter name="sigAZ0"> + <value val="0.0025" /> +</parameter> + +<!-- Initial guess parameters for fsigP (BVG covariance) --> +<parameter name="sigP0Params" type="string"> + <value val="0.1460775 1.85816592 0.26850086 -0.00725352" /> +</parameter> + </component-link> </parameter-file> diff --git a/instrument/TOPAZ_Parameters.xml b/instrument/TOPAZ_Parameters.xml index d2956eb6df187922bd173b30c6e5b95099a2aa24..1408c260d826e3cf1a62ec314f3fb0a67d6f53d3 100644 --- a/instrument/TOPAZ_Parameters.xml +++ b/instrument/TOPAZ_Parameters.xml @@ -121,12 +121,12 @@ detScale={13:1.046504,14:1.259293,16:1.02449,17:1.18898,18:0.88014,19:0.98665,\ <!-- Fraction along (h,k,l) to use for profile fitting. 0.5 is the next peak. --> <parameter name="fracHKL"> - <value val="0.4" /> + <value val="0.25" /> </parameter> <!-- Side length of each voxel for fitting in units of angstrom^-1 --> <parameter name="dQPixel"> - <value val="0.01" /> + <value val="0.006" /> </parameter> <!-- Minimum spacing for profile fitting the TOF profile. Units of microseconds --> @@ -141,7 +141,22 @@ detScale={13:1.046504,14:1.259293,16:1.02449,17:1.18898,18:0.88014,19:0.98665,\ <!-- Size of peak mask for background calculation in units of dQPixel --> <parameter name="peakMaskSize" type="int"> - <value val="15" /> + <value val="6" /> +</parameter> + +<!-- Initial guess parameters for coshPeakWidthModel --> +<parameter name="sigSC0Params" type="string"> + <value val="0.00413132 1.54103839 1.0 -0.00266634" /> +</parameter> + +<!-- Initial guess for sigma along the azimuthal direction (rad) --> +<parameter name="sigAZ0"> + <value val="0.0025" /> +</parameter> + +<!-- Initial guess parameters for fsigP (BVG covariance) --> +<parameter name="sigP0Params" type="string"> + <value val="0.1460775 1.85816592 0.26850086 -0.00725352" /> </parameter> <!-- Constraints for ICC fitting. Valid names are iccA, iccB, iccR, iccT0, iccScale0 @@ -149,14 +164,12 @@ detScale={13:1.046504,14:1.259293,16:1.02449,17:1.18898,18:0.88014,19:0.98665,\ spaces which are prased by the IntegratePeaksProfileFitting algorithm. If two values are given they are treated as the lower and upper bounds. If three are given they are the lower bound, upper bound, and initial guess.--> -<parameter name="iccB" type="string"> - <value val="0.001 0.3 0.005" /> -</parameter> <parameter name="iccKConv" type="string"> <value val="10.0 800.0 100.0" /> </parameter> </component-link> + </parameter-file> diff --git a/qt/applications/workbench/workbench/app/mainwindow.py b/qt/applications/workbench/workbench/app/mainwindow.py index 66557e19a014f4a0b5130cf145de4eb9a1395279..8a920c0025b94041a835f92341eea615ce533824 100644 --- a/qt/applications/workbench/workbench/app/mainwindow.py +++ b/qt/applications/workbench/workbench/app/mainwindow.py @@ -37,7 +37,7 @@ requirements.check_qt() # ----------------------------------------------------------------------------- # Qt # ----------------------------------------------------------------------------- -from qtpy.QtCore import (QEventLoop, Qt, QCoreApplication, QSettings, QPoint, QSize) # noqa +from qtpy.QtCore import (QEventLoop, Qt, QCoreApplication, QPoint, QSize) # noqa from qtpy.QtGui import (QColor, QPixmap) # noqa from qtpy.QtWidgets import (QApplication, QDesktopWidget, QFileDialog, QMainWindow, QSplashScreen) # noqa @@ -131,6 +131,7 @@ class MainWindow(QMainWindow): self.editor_menu = None self.view_menu = None self.view_menu_actions = None + self.interfaces_menu = None # Allow splash screen text to be overridden in set_splash self.splash = SPLASH @@ -200,6 +201,7 @@ class MainWindow(QMainWindow): self.file_menu = self.menuBar().addMenu("&File") self.editor_menu = self.menuBar().addMenu("&Editor") self.view_menu = self.menuBar().addMenu("&View") + self.interfaces_menu = self.menuBar().addMenu('&Interfaces') def create_actions(self): # --- general application menu options -- @@ -248,6 +250,52 @@ class MainWindow(QMainWindow): add_actions(self.file_menu, self.file_menu_actions) add_actions(self.view_menu, self.view_menu_actions) + def launchCustomGUI(self, script): + exec(open(script).read(), globals()) + + def populateAfterMantidImport(self): + from mantid.kernel import ConfigService, logger + # TODO ConfigService should accept unicode strings https://github.com/mantidproject/mantid/pull/23826 + interface_dir = ConfigService[str('mantidqt.python_interfaces_directory')] + items = ConfigService[str('mantidqt.python_interfaces')].split() + + # list of custom interfaces that are not qt4/qt5 compatible + GUI_BLACKLIST = ['DGS_Reduction.py', + 'MSlice.py', + 'ORNL_SANS.py', + 'ISIS_Reflectometry_Old.py', + 'Powder_Diffraction_Reduction.py', + 'HFIR_4Circle_Reduction.py', + 'ISIS_SANS_v2_experimental.py', + 'Frequency_Domain_Analysis.py', + 'Elemental_Analysis.py'] + + # detect the python interfaces + interfaces = {} + for item in items: + key, scriptname = item.split('/') + if not os.path.exists(os.path.join(interface_dir, scriptname)): + logger.warning('Failed to find script "{}" in "{}"'.format(scriptname, interface_dir)) + continue + if scriptname in GUI_BLACKLIST: + logger.information('Not adding gui "{}"'.format(scriptname)) + continue + temp = interfaces.get(key, []) + temp.append(scriptname) + interfaces[key] = temp + + # add the interfaces to the menu + keys = list(interfaces.keys()) + keys.sort() + for key in keys: + submenu = self.interfaces_menu.addMenu(key) + names = interfaces[key] + names.sort() + for name in names: + action = submenu.addAction(name.replace('.py', '').replace('_', ' ')) + script = os.path.join(interface_dir, name) + action.triggered.connect(lambda checked, script=script: self.launchCustomGUI(script)) + def add_dockwidget(self, plugin): """Create a dockwidget around a plugin and add the dock to window""" dockwidget, location = plugin.create_dockwidget() @@ -335,6 +383,10 @@ class MainWindow(QMainWindow): import matplotlib.pyplot as plt # noqa plt.close('all') + app = QApplication.instance() + if app is not None: + app.closeAllWindows() + event.accept() else: # Cancel was pressed when closing an editor @@ -453,14 +505,16 @@ def start_workbench(app, command_line_options): # start mantid main_window.set_splash('Preloading mantid') importlib.import_module('mantid') + main_window.populateAfterMantidImport() main_window.show() if main_window.splash: main_window.splash.hide() - if command_line_options.exe_script is not None: - main_window.editor.open_file_in_new_tab(command_line_options.exe_script) - main_window.editor.execute_current() # TODO use the result as an exit code + if command_line_options.script is not None: + main_window.editor.open_file_in_new_tab(command_line_options.script) + if command_line_options.execute: + main_window.editor.execute_current() # TODO use the result as an exit code if command_line_options.quit: main_window.close() @@ -485,9 +539,10 @@ def main(): # setup command line arguments parser = argparse.ArgumentParser(description='Mantid Workbench') - parser.add_argument('-x', '--execute', metavar='SCRIPT', dest='exe_script', + parser.add_argument('script', nargs='?') + parser.add_argument('-x', '--execute', action='store_true', help='execute the script file given as argument') - parser.add_argument('-q', '--quit', dest='quit', action='store_true', + parser.add_argument('-q', '--quit', action='store_true', help='execute the script file with \'-x\' given as argument and then exit') # TODO -a or --about: show about dialog and exit # TODO -d or --default-settings: start MantidPlot with the default settings @@ -511,13 +566,13 @@ def main(): # TODO handle options that don't require starting the workbench e.g. --help --version # fix/validate arguments - if options.exe_script is not None: + if options.script is not None: # convert into absolute path - options.exe_script = os.path.abspath(os.path.expanduser(options.exe_script)) - if not os.path.exists(options.exe_script): + options.script = os.path.abspath(os.path.expanduser(options.script)) + if not os.path.exists(options.script): # TODO should be logged - print('script "{}" does not exist'.format(options.exe_script)) - options.exe_script = None + print('script "{}" does not exist'.format(options.script)) + options.script = None app = initialize() # the default sys check interval leads to long lags diff --git a/qt/applications/workbench/workbench/plugins/workspacewidget.py b/qt/applications/workbench/workbench/plugins/workspacewidget.py index 369c661ec160d3fd504c08fc0b2f265658dcd774..61f2c561099f53b0d5b72858132223654f5fc1e7 100644 --- a/qt/applications/workbench/workbench/plugins/workspacewidget.py +++ b/qt/applications/workbench/workbench/plugins/workspacewidget.py @@ -14,13 +14,14 @@ import functools # third-party library imports from mantid.api import AnalysisDataService +from mantidqt.widgets.samplelogs.presenter import SampleLogs +from mantidqt.widgets.instrumentview.presenter import InstrumentViewPresenter from mantidqt.widgets.workspacewidget.workspacetreewidget import WorkspaceTreeWidget from qtpy.QtWidgets import QMessageBox, QVBoxLayout # local package imports from workbench.plugins.base import PluginWidget from workbench.plotting.functions import can_overplot, pcolormesh, plot_from_names -# from mantidqt.utils.qt import toQSettings when readSettings/writeSettings are implemented class WorkspaceWidget(PluginWidget): @@ -47,6 +48,8 @@ class WorkspaceWidget(PluginWidget): self.workspacewidget.overplotSpectrumWithErrorsClicked.connect(functools.partial(self._do_plot_spectrum, errors=True, overplot=True)) self.workspacewidget.plotColorfillClicked.connect(self._do_plot_colorfill) + self.workspacewidget.sampleLogsClicked.connect(self._do_sample_logs) + self.workspacewidget.showInstrumentClicked.connect(self._do_show_instrument) # ----------------- Plugin API -------------------- @@ -92,3 +95,22 @@ class WorkspaceWidget(PluginWidget): except BaseException: import traceback traceback.print_exc() + + def _do_sample_logs(self, names): + """ + Show the sample log window for the given workspaces + + :param names: A list of workspace names + """ + for ws in self._ads.retrieveWorkspaces(names, unrollGroups=True): + SampleLogs(ws=ws, parent=self) + + def _do_show_instrument(self, names): + """ + Show an instrument widget for the given workspaces + + :param names: A list of workspace names + """ + for ws in self._ads.retrieveWorkspaces(names, unrollGroups=True): + presenter = InstrumentViewPresenter(ws, parent=self) + presenter.view.show() diff --git a/qt/applications/workbench/workbench/test/test_import.py b/qt/applications/workbench/workbench/test/test_import.py index 617d94adc8e56c09fc1a493d1a3a50648ce9e584..743b8a0b413dd1410ed8b4262cf4d82ac2c459c4 100644 --- a/qt/applications/workbench/workbench/test/test_import.py +++ b/qt/applications/workbench/workbench/test/test_import.py @@ -18,5 +18,6 @@ class ImportTest(unittest.TestCase): def test_import_workbench(self): import workbench # noqa + if __name__ == "__main__": unittest.main() diff --git a/qt/python/CMakeLists.txt b/qt/python/CMakeLists.txt index 149eb7e845789f3f2a0bc1861172f2e0a4645b4f..911b81e16db472139b03042f4a66702b53c6fdbf 100644 --- a/qt/python/CMakeLists.txt +++ b/qt/python/CMakeLists.txt @@ -39,9 +39,10 @@ if ( ENABLE_WORKBENCH ) # Setup dependency chain add_dependencies ( mantidqt + mantidqt_resources mantidqt_commonqt4 mantidqt_commonqt5 - mantidqt_resources + mantidqt_instrumentviewqt5 ) if ( MSVC ) @@ -83,6 +84,9 @@ endif () mantidqt/widgets/test/test_jupyterconsole.py mantidqt/widgets/test/test_messagedisplay.py + + mantidqt/widgets/samplelogs/test/test_samplelogs_model.py + mantidqt/widgets/samplelogs/test/test_samplelogs_presenter.py ) # Tests diff --git a/qt/python/mantidqt/CMakeLists.txt b/qt/python/mantidqt/CMakeLists.txt index 54590033f2b87e85f963873849ae11e22f593fcb..8b2ac266947ceac944c79338e0460175b5292ff4 100644 --- a/qt/python/mantidqt/CMakeLists.txt +++ b/qt/python/mantidqt/CMakeLists.txt @@ -1,7 +1,7 @@ include ( SipQtTargetFunctions ) set ( COMMON_INC_DIR ../../widgets/common/inc ) -set ( HEADER_DEPENDS +set ( _header_depends ${COMMON_INC_DIR}/MantidQtWidgets/Common/AlgorithmDialog.h ${COMMON_INC_DIR}/MantidQtWidgets/Common/Message.h ${COMMON_INC_DIR}/MantidQtWidgets/Common/MessageDisplay.h @@ -23,15 +23,16 @@ mtd_add_sip_module ( MODULE_NAME _commonqt4 TARGET_NAME mantidqt_commonqt4 SIP_SRCS _common.sip - HEADER_DEPS ${HEADER_DEPENDS} + HEADER_DEPS ${_header_depends} PYQT_VERSION 4 + INCLUDE_DIRS + ${PYTHON_INCLUDE_PATH} LINK_LIBS ${common_link_libs} MantidQtWidgetsCommonQt4 Qt4::QtCore Qt4::QtGui Qt4::Qscintilla - ${PYTHON_LIBRARIES} ${Boost_LIBRARIES} API FOLDER Qt4 @@ -46,17 +47,17 @@ mtd_add_sip_module ( MODULE_NAME _commonqt5 TARGET_NAME mantidqt_commonqt5 SIP_SRCS _common.sip - HEADER_DEPS ${HEADER_DEPENDS} + HEADER_DEPS ${_header_depends} PYQT_VERSION 5 + INCLUDE_DIRS + ${PYTHON_INCLUDE_PATH} LINK_LIBS ${common_link_libs} MantidQtWidgetsCommonQt5 - ${POCO_LIBRARIES} Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Qscintilla - ${PYTHON_LIBRARIES} ${Boost_LIBRARIES} API INSTALL_DIR @@ -65,3 +66,5 @@ mtd_add_sip_module ( "\$ORIGIN/.." FOLDER Qt5 ) + +add_subdirectory ( widgets/instrumentview ) diff --git a/qt/python/mantidqt/_common.sip b/qt/python/mantidqt/_common.sip index e78fc66c6bd175de83b0b2a8d54a1f0e366dec2b..5ea5f58fa7afe506bb2882a832746c1994d152bd 100644 --- a/qt/python/mantidqt/_common.sip +++ b/qt/python/mantidqt/_common.sip @@ -241,6 +241,8 @@ signals: void plotSpectrumWithErrorsClicked(const QStringList & workspaceNames); void overplotSpectrumWithErrorsClicked(const QStringList & workspaceNames); void plotColorfillClicked(const QStringList &workspaceNames); + void sampleLogsClicked(const QStringList &workspaceNames); + void showInstrumentClicked(const QStringList &workspaceNames); }; // --------------------------------- diff --git a/qt/python/mantidqt/test/test_import.py b/qt/python/mantidqt/test/test_import.py index f2c4c63a3e203d5c58759b5cb4d62414e0dddc71..6154c50eea3d24749833dfafa908a6578265d488 100644 --- a/qt/python/mantidqt/test/test_import.py +++ b/qt/python/mantidqt/test/test_import.py @@ -15,5 +15,6 @@ class ImportTest(unittest.TestCase): def test_import(self): import mantidqt # noqa + if __name__ == "__main__": unittest.main() diff --git a/qt/python/mantidqt/utils/qt/__init__.py b/qt/python/mantidqt/utils/qt/__init__.py index a622bdb6e198cbb743d6af2aff49d97fc7e7ce11..dd491474b05b355d6c10ebae4b16cf2abfc6220d 100644 --- a/qt/python/mantidqt/utils/qt/__init__.py +++ b/qt/python/mantidqt/utils/qt/__init__.py @@ -77,6 +77,10 @@ def load_ui(caller_filename, ui_relfilename, baseinstance=None): return the form class """ filepath = osp.join(osp.dirname(caller_filename), ui_relfilename) + if not osp.exists(filepath): + raise ImportError('File "{}" does not exist'.format(filepath)) + if not osp.isfile(filepath): + raise ImportError('File "{}" is not a file'.format(filepath)) if baseinstance is not None: return loadUi(filepath, baseinstance=baseinstance) else: diff --git a/qt/python/mantidqt/widgets/instrumentview/CMakeLists.txt b/qt/python/mantidqt/widgets/instrumentview/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..31f02d565474207434366a88f79f3c02a952f1c2 --- /dev/null +++ b/qt/python/mantidqt/widgets/instrumentview/CMakeLists.txt @@ -0,0 +1,32 @@ +# Defines a Python wrapper module for instrument view access +# This wraps the low-level classes. + +set ( INSTRUMENTVIEW_INC_DIR ../../widgets/instrumentview/inc ) +set ( _header_depends + ${INSTRUMENTVIEW_INC_DIR}/InstrumentWidget.h +) + +# The wrapper is only defined for Qt5 and does not support +# legacy Qt4 mode +mtd_add_sip_module ( + MODULE_NAME _instrumentviewqt5 + TARGET_NAME mantidqt_instrumentviewqt5 + SIP_SRCS _instrumentview.sip + PYQT_VERSION 5 + INCLUDE_DIRS + ${PYTHON_INCLUDE_PATH} + LINK_LIBS + ${common_link_libs} + MantidQtWidgetsInstrumentViewQt5 + MantidQtWidgetsCommonQt5 + MantidQtWidgetsMplCppQt5 + Qt5::Core + Qt5::Gui + Qt5::OpenGL + Qt5::Widgets + INSTALL_DIR + ${LIB_DIR}/mantidqt/widgets/instrumentview + LINUX_INSTALL_RPATH + "\$ORIGIN/../../.." + FOLDER Qt5 +) diff --git a/qt/python/mantidqt/widgets/instrumentview/__init__.py b/qt/python/mantidqt/widgets/instrumentview/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c1a81e28aec885808acaea854ff7f175c593fa4f --- /dev/null +++ b/qt/python/mantidqt/widgets/instrumentview/__init__.py @@ -0,0 +1,30 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2017 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +# This file is part of the mantidqt package +# +# +""" +You can run this widget independently by for example: + + from mantidqt.widgets.instrumentview.presenter import InstrumentView + from mantid.simpleapi import Load + from qtpy.QtWidgets import QApplication + + ws=Load('CNCS_7860') + + app = QApplication([]) + window = InstrumentView(ws) + app.exec_() +""" +from __future__ import (absolute_import, unicode_literals) + +# 3rdparty imports +from qtpy import PYQT4 + + +if PYQT4: + raise ImportError("Instrument view requires Qt >= v5") diff --git a/qt/python/mantidqt/widgets/instrumentview/_instrumentview.sip b/qt/python/mantidqt/widgets/instrumentview/_instrumentview.sip new file mode 100644 index 0000000000000000000000000000000000000000..2826afed57b2dcf26ccd77e9da7b2e75249a9989 --- /dev/null +++ b/qt/python/mantidqt/widgets/instrumentview/_instrumentview.sip @@ -0,0 +1,27 @@ +%ModuleCode +#include "MantidQtWidgets/InstrumentView/InstrumentWidget.h" +// Allows suppression of namespaces within the module +using namespace MantidQt::MantidWidgets; +%End + +%InitialisationCode +qRegisterMetaType<std::string>("StdString"); +qRegisterMetaType<Mantid::API::Workspace_sptr>("Workspace"); +%End + +//---------------------------------------------------------------------------- +// Classes +// ---------------------------------------------------------------------------- + +class InstrumentWidget : QWidget { +%TypeHeaderCode +#include "MantidQtWidgets/InstrumentView/InstrumentWidget.h" +%End +public: + InstrumentWidget(const QString &ws_name, QWidget *parent /TransferThis/ = 0, + bool reset_geometry = true, bool autoscaling = true, + double scale_min = 0.0, double scale_max = 0.0, + bool set_default_view = true); + bool overlay(const QString & ws_name); +}; + diff --git a/qt/python/mantidqt/widgets/instrumentview/presenter.py b/qt/python/mantidqt/widgets/instrumentview/presenter.py new file mode 100644 index 0000000000000000000000000000000000000000..03f44680fbf9ac660f156e610f5be17efb428726 --- /dev/null +++ b/qt/python/mantidqt/widgets/instrumentview/presenter.py @@ -0,0 +1,27 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2017 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +# This file is part of the mantidqt package +# +# +""" +Contains the presenter for displaying the InstrumentWidget +""" +from __future__ import (absolute_import, unicode_literals) + +# local imports +from .view import InstrumentView + + +class InstrumentViewPresenter(object): + """ + Presenter holding the view widget for the InstrumentView. + It has no model as its an old widget written in C++ with out MVP + """ + view = None + + def __init__(self, ws, parent=None): + self.view = InstrumentView(self, ws.name(), parent) diff --git a/qt/python/mantidqt/widgets/instrumentview/view.py b/qt/python/mantidqt/widgets/instrumentview/view.py new file mode 100644 index 0000000000000000000000000000000000000000..7abfce3a63fce6506d2b3109e731516d4f0606ae --- /dev/null +++ b/qt/python/mantidqt/widgets/instrumentview/view.py @@ -0,0 +1,48 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2017 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +# This file is part of the mantidqt package +# +# +""" +Contains the Python wrapper class for the C++ instrument widget +""" +from __future__ import (absolute_import, unicode_literals) + +# 3rdparty imports +from qtpy.QtCore import Qt +from qtpy.QtWidgets import QVBoxLayout, QWidget + +# local imports +from mantidqt.utils.qt import import_qt + + +# import widget class from C++ wrappers +InstrumentWidget = import_qt('._instrumentview', 'mantidqt.widgets.instrumentview', + 'InstrumentWidget') + + +class InstrumentView(QWidget): + """ + Defines a Window wrapper for the instrument widget. Sets + the Qt.Window flag and window title. Holds a reference + to the presenter and keeps it alive for the duration that + the window is open + """ + _presenter = None + _widget = None + + def __init__(self, presenter, name, parent=None): + super(InstrumentView, self).__init__(parent) + self._presenter = presenter + + self.setWindowTitle(name) + self.setWindowFlags(Qt.Window) + + layout = QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(InstrumentWidget(name)) + self.setLayout(layout) diff --git a/qt/python/mantidqt/widgets/instrumentview/widget.py b/qt/python/mantidqt/widgets/instrumentview/widget.py new file mode 100644 index 0000000000000000000000000000000000000000..c52a8bc59005bfc2bf3a1e92fc126c79bcd5dd78 --- /dev/null +++ b/qt/python/mantidqt/widgets/instrumentview/widget.py @@ -0,0 +1,19 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2017 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +# This file is part of the mantidqt package +# +# +from __future__ import (absolute_import, unicode_literals) + +# 3rd party imports + +# local imports +from mantidqt.utils.qt import import_qt + +# Import widget from C++ wrappers +InstrumentWidget = import_qt('._instrumentview', 'mantidqt.widgets.instrumentview', + 'InstrumentWidget') diff --git a/qt/python/mantidqt/widgets/samplelogs/__init__.py b/qt/python/mantidqt/widgets/samplelogs/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..fe8fb0e331a37553eba84ac691d446711e7c0f38 --- /dev/null +++ b/qt/python/mantidqt/widgets/samplelogs/__init__.py @@ -0,0 +1,22 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +# This file is part of the mantid workbench. +# +# +""" +You can run this widget independently by for example: + + from mantidqt.widgets.samplelogs.presenter import SampleLogs + from mantid.simpleapi import Load + from qtpy.QtWidgets import QApplication + + ws=Load('CNCS_7860') + + app = QApplication([]) + window = SampleLogs(ws) + app.exec_() +""" diff --git a/qt/python/mantidqt/widgets/samplelogs/model.py b/qt/python/mantidqt/widgets/samplelogs/model.py new file mode 100644 index 0000000000000000000000000000000000000000..f0138fe18db58d7025ef4c8d75f4d7887bf83393 --- /dev/null +++ b/qt/python/mantidqt/widgets/samplelogs/model.py @@ -0,0 +1,138 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +# This file is part of the mantid workbench. +# +# +from __future__ import (absolute_import, division, print_function) +from mantid.kernel import (BoolTimeSeriesProperty, + FloatTimeSeriesProperty, Int32TimeSeriesProperty, + Int64TimeSeriesProperty, StringTimeSeriesProperty) +from mantid.api import MultipleExperimentInfos +from qtpy.QtGui import QStandardItemModel, QStandardItem + +TimeSeriesProperties = (BoolTimeSeriesProperty, + FloatTimeSeriesProperty, Int32TimeSeriesProperty, + Int64TimeSeriesProperty, StringTimeSeriesProperty) + + +def get_type(log): + """Convert type to something readable""" + dtype_map = {'i': 'int', 'f': 'float', 's': 'string', 'b': 'bool'} + if isinstance(log, TimeSeriesProperties): + return "{} series".format(dtype_map[log.dtype()[0].lower()]) + else: + return log.type + + +def get_value(log): + """Returns the first value if length==1 otherwise returns number of + entries + """ + if isinstance(log, TimeSeriesProperties): + if log.size() > 1: + return "({} entries)".format(log.size()) + else: + return log.firstValue() + else: + return log.value + + +class SampleLogsModel(object): + """This class stores the workspace object and return log values when + requested + """ + def __init__(self, ws): + """Stores three thing:, the workspace, which experiment info number + to use, and the run object. + """ + self._ws = ws + self._exp = 0 + self._set_run() + + def _set_run(self): + """Set run depending on workspace type and experiment info number""" + if self.isMD(): + self.run = self._ws.getExperimentInfo(self._exp).run() + else: + self.run = self._ws.run() + + def set_exp(self, exp): + """Change the experiment info number""" + self._exp = exp + self._set_run() + + def get_exp(self): + """Return the experiment info number""" + return self._exp + + def get_ws(self): + """Return the workspace""" + return self._ws + + def get_name(self): + """Return the workspace name""" + return self._ws.name() + + def getNumExperimentInfo(self): + """Return number of experiment info's in workspace""" + return self._ws.getNumExperimentInfo() if self.isMD() else 0 + + def get_log(self, LogName): + """Return log of given LogName""" + return self.run.getLogData(LogName) + + def get_log_names(self): + """Returns a list of logs in workspace""" + return self.run.keys() + + def get_log_display_values(self, LogName): + """Return a row to display for a log (name, type, value, units)""" + log = self.get_log(LogName) + return log.name, get_type(log), get_value(log), log.units + + def is_log_plottable(self, LogName): + """Checks if logs is plottable. Only Float, Int32 and Int64 + TimeSeriesProperties are plottable at this point. + """ + return isinstance(self.get_log(LogName), (FloatTimeSeriesProperty, + Int32TimeSeriesProperty, + Int64TimeSeriesProperty)) + + def get_statistics(self, LogName): + """Return the statistics of a particular log""" + log = self.get_log(LogName) + if isinstance(log, TimeSeriesProperties): + return log.getStatistics() + + def isMD(self): + """Checks if workspace is a MD Workspace""" + return isinstance(self._ws, MultipleExperimentInfos) + + def getItemModel(self): + """Return a QModel made from the current workspace. This should be set + onto a QTableView + """ + model = QStandardItemModel() + model.setHorizontalHeaderLabels(["Name", "Type", "Value", "Units"]) + model.setColumnCount(4) + for key in self.get_log_names(): + log = self.run.getLogData(key) + name = QStandardItem() + name.setText(log.name) + name.setEditable(False) + log_type = QStandardItem() + log_type.setText(get_type(log)) + log_type.setEditable(False) + value = QStandardItem() + value.setText(str(get_value(log))) + value.setEditable(False) + unit = QStandardItem() + unit.setText(log.units) + unit.setEditable(False) + model.appendRow((name, log_type, value, unit)) + model.sort(0) + return model diff --git a/qt/python/mantidqt/widgets/samplelogs/presenter.py b/qt/python/mantidqt/widgets/samplelogs/presenter.py new file mode 100644 index 0000000000000000000000000000000000000000..285a760f5d62c79f215f11ea44c9222c452cf24a --- /dev/null +++ b/qt/python/mantidqt/widgets/samplelogs/presenter.py @@ -0,0 +1,99 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +# This file is part of the mantid workbench. +# +# +from __future__ import (absolute_import, division, print_function) +from .model import SampleLogsModel +from .view import SampleLogsView + + +class SampleLogs(object): + """ + """ + def __init__(self, ws, parent=None, model=None, view=None): + # Create model and view, or accept mocked versions + self.model = model if model else SampleLogsModel(ws) + self.view = view if view else SampleLogsView(self, + parent, + self.model.get_name(), + self.model.isMD(), + self.model.getNumExperimentInfo()) + self.setup_table() + + def clicked(self): + """When a log is clicked update stats with selected log and replot the + logs + """ + self.update_stats() + self.plot_logs() + + def doubleClicked(self, i): + """When a log is doubleCliked, print the log, later this should + display the data in a table workspace or something + """ + self.print_selected_logs() + + def print_selected_logs(self): + """Print all selected logs""" + for row in self.view.get_selected_row_indexes(): + log = self.model.get_log(self.view.get_row_log_name(row)) + print('# {}'.format(log.name)) + print(log.valueAsPrettyStr()) + + def plot_clicked(self, event): + """Check if figure is doubleClicked, then create new plot""" + if event.dblclick: + self.new_plot_logs() + + def changeExpInfo(self): + """When a different experiment info is selected this updates + everything + """ + # get currently selected rows + selected_rows = self.view.get_selected_row_indexes() + # set experiment info of model to one from view + self.model.set_exp(self.view.get_exp()) + # create a new table as eveything has changed + self.setup_table() + # reselect previously selected rows + self.view.set_selected_rows(selected_rows) + # update stats with different experiment info + self.update_stats() + # replot logs with different experiment info + self.plot_logs() + + def update_stats(self): + """Updates the stats for currently select row. + + If more then one row is selected then that stats are just cleared. + """ + selected_rows = self.view.get_selected_row_indexes() + if len(selected_rows) == 1: + stats = self.model.get_statistics(self.view.get_row_log_name(selected_rows[0])) + if stats: + self.view.set_statistics(stats) + else: + self.view.clear_statistics() + else: + self.view.clear_statistics() + + def plot_logs(self): + """Get all selected rows, check if plottable, then plot the logs""" + to_plot = [row for row in self.view.get_selected_row_indexes() + if self.model.is_log_plottable(self.view.get_row_log_name(row))] + self.view.plot_selected_logs(self.model.get_ws(), self.model.get_exp(), to_plot) + + def new_plot_logs(self): + """Get all selected rows, check if plottable, then plot the logs in new figure""" + to_plot = [row for row in self.view.get_selected_row_indexes() + if self.model.is_log_plottable(self.view.get_row_log_name(row))] + self.view.new_plot_selected_logs(self.model.get_ws(), self.model.get_exp(), to_plot) + + def setup_table(self): + """Set the model in the view to the one create from the model""" + self.view.set_model(self.model.getItemModel()) diff --git a/qt/python/mantidqt/widgets/samplelogs/test/test_samplelogs_model.py b/qt/python/mantidqt/widgets/samplelogs/test/test_samplelogs_model.py new file mode 100644 index 0000000000000000000000000000000000000000..52a8e707d7721e1bb54e17b0a41e1aff0ed6992d --- /dev/null +++ b/qt/python/mantidqt/widgets/samplelogs/test/test_samplelogs_model.py @@ -0,0 +1,90 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +# This file is part of the mantid workbench. +# +# +from __future__ import (absolute_import, division, print_function) + +from mantid.simpleapi import LoadEventNexus, CreateMDWorkspace +from mantidqt.widgets.samplelogs.model import SampleLogsModel + +import unittest + + +class SampleLogsModelTest(unittest.TestCase): + + def test_model(self): + ws = LoadEventNexus('CNCS_7860', MetaDataOnly=True) + model = SampleLogsModel(ws) + + self.assertEqual(model.get_exp(), 0) + self.assertEqual(model.get_name(), 'ws') + self.assertEqual(model.getNumExperimentInfo(), 0) + + log = model.get_log("Speed5") + self.assertEqual(log.name, "Speed5") + self.assertEqual(log.size(), 4) + + log_names = model.get_log_names() + self.assertEqual(len(log_names), 48) + self.assertIn("Speed5", log_names) + + values = model.get_log_display_values("Speed5") + self.assertEqual(values[0], "Speed5") + self.assertEqual(values[1], "float series") + self.assertEqual(values[2], "(4 entries)") + self.assertEqual(values[3], "Hz") + + self.assertTrue(model.is_log_plottable("Speed5")) + self.assertFalse(model.is_log_plottable("duration")) + + stats = model.get_statistics("Speed5") + self.assertEqual(stats.maximum, 300.0) + + self.assertFalse(model.isMD()) + + itemModel = model.getItemModel() + self.assertEqual(itemModel.horizontalHeaderItem(0).text(), "Name") + self.assertEqual(itemModel.horizontalHeaderItem(1).text(), "Type") + self.assertEqual(itemModel.horizontalHeaderItem(2).text(), "Value") + self.assertEqual(itemModel.horizontalHeaderItem(3).text(), "Units") + self.assertEqual(itemModel.rowCount(), 48) + self.assertEqual(itemModel.item(0,0).text(), "ChopperStatus1") + self.assertEqual(itemModel.item(0,1).text(), "float series") + self.assertEqual(itemModel.item(0,2).text(), "4.0") + self.assertEqual(itemModel.item(0,3).text(), "") + + def test_model_MD(self): + ws1 = LoadEventNexus("CNCS_7860", MetaDataOnly=True) + ws2 = LoadEventNexus("VIS_19351", MetaDataOnly=True) + md = CreateMDWorkspace(Dimensions=1, Extents='-1,1', Names='A', Units='U') + md.addExperimentInfo(ws1) + md.addExperimentInfo(ws2) + model = SampleLogsModel(md) + + self.assertEqual(model.get_exp(), 0) + self.assertEqual(model.get_name(), 'md') + self.assertEqual(model.getNumExperimentInfo(), 2) + + values = model.get_log_display_values("duration") + self.assertEqual(values[0], "duration") + self.assertEqual(values[1], "number") + self.assertEqual(values[2], 148.0) + self.assertEqual(values[3], "second") + + # Change exp + model.set_exp(1) + self.assertEqual(model.get_exp(), 1) + values = model.get_log_display_values("duration") + self.assertEqual(values[0], "duration") + self.assertEqual(values[1], "number") + self.assertEqual(values[2], 4.616606712341309) + self.assertEqual(values[3], "second") + + +if __name__ == '__main__': + unittest.main() diff --git a/qt/python/mantidqt/widgets/samplelogs/test/test_samplelogs_presenter.py b/qt/python/mantidqt/widgets/samplelogs/test/test_samplelogs_presenter.py new file mode 100644 index 0000000000000000000000000000000000000000..0a4f9a53db3d39953e07b435bcd84ea1e295c478 --- /dev/null +++ b/qt/python/mantidqt/widgets/samplelogs/test/test_samplelogs_presenter.py @@ -0,0 +1,117 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +# This file is part of the mantid workbench. +# +# +from __future__ import (absolute_import, division, print_function) + +import matplotlib +matplotlib.use('Agg') # noqa: E402 + +from mantidqt.widgets.samplelogs.model import SampleLogsModel +from mantidqt.widgets.samplelogs.presenter import SampleLogs +from mantidqt.widgets.samplelogs.view import SampleLogsView + +import unittest +try: + from unittest import mock +except ImportError: + import mock + + +class SampleLogsTest(unittest.TestCase): + + def setUp(self): + self.view = mock.Mock(spec=SampleLogsView) + self.view.get_row_log_name = mock.Mock(return_value="Speed5") + self.view.get_selected_row_indexes = mock.Mock(return_value=[5]) + self.view.get_exp = mock.Mock(return_value=1) + + self.model = mock.Mock(spec=SampleLogsModel) + self.model.get_ws = mock.Mock(return_value='ws') + self.model.is_log_plottable = mock.Mock(return_value=True) + self.model.get_statistics = mock.Mock(return_value=[1,2,3,4]) + self.model.get_exp = mock.Mock(return_value=0) + + def test_sampleLogs(self): + + presenter = SampleLogs(None, model=self.model, view=self.view) + + # setup calls + self.assertEqual(self.view.set_model.call_count, 1) + self.assertEqual(self.model.getItemModel.call_count, 1) + + # plot_logs + presenter.plot_logs() + self.model.is_log_plottable.assert_called_once_with("Speed5") + self.assertEqual(self.model.get_ws.call_count, 1) + self.view.plot_selected_logs.assert_called_once_with('ws', 0, [5]) + + # update_stats + presenter.update_stats() + self.assertEqual(self.model.get_statistics.call_count, 1) + self.view.get_row_log_name.assert_called_with(5) + self.view.set_statistics.assert_called_once_with([1,2,3,4]) + self.assertEqual(self.view.clear_statistics.call_count, 0) + + self.view.reset_mock() + self.view.get_selected_row_indexes = mock.Mock(return_value=[2,5]) + presenter.update_stats() + self.assertEqual(self.view.set_statistics.call_count, 0) + self.assertEqual(self.view.clear_statistics.call_count, 1) + + # changeExpInfo + self.model.reset_mock() + self.view.reset_mock() + + presenter.changeExpInfo() + self.assertEqual(self.view.get_selected_row_indexes.call_count, 3) + self.assertEqual(self.view.get_exp.call_count, 1) + self.model.set_exp.assert_called_once_with(1) + self.view.set_selected_rows.assert_called_once_with([2,5]) + + # clicked + self.model.reset_mock() + self.view.reset_mock() + + presenter.clicked() + self.assertEqual(self.view.get_selected_row_indexes.call_count, 2) + + # plot clicked + self.model.reset_mock() + self.view.reset_mock() + + event = mock.Mock() + type(event).dblclick = mock.PropertyMock(return_value=False) + presenter.plot_clicked(event) + self.assertEqual(self.view.get_selected_row_indexes.call_count, 0) + self.assertEqual(self.view.get_row_log_name.call_count, 0) + self.assertEqual(self.model.is_log_plottable.call_count, 0) + self.assertEqual(self.view.new_plot_selected_logs.call_count, 0) + + type(event).dblclick = mock.PropertyMock(return_value=True) + presenter.plot_clicked(event) + self.assertEqual(self.view.get_selected_row_indexes.call_count, 1) + self.assertEqual(self.view.get_row_log_name.call_count, 2) + self.assertEqual(self.model.is_log_plottable.call_count, 2) + self.assertEqual(self.view.new_plot_selected_logs.call_count, 1) + + # double clicked + self.model.reset_mock() + self.view.reset_mock() + + index = mock.Mock() + index.row = mock.Mock(return_value=7) + + presenter.doubleClicked(index) + self.assertEqual(self.view.get_row_log_name.call_count, 2) + self.view.get_row_log_name.assert_called_with(5) + self.model.get_log.assert_called_with("Speed5") + + +if __name__ == '__main__': + unittest.main() diff --git a/qt/python/mantidqt/widgets/samplelogs/view.py b/qt/python/mantidqt/widgets/samplelogs/view.py new file mode 100644 index 0000000000000000000000000000000000000000..6227c6cba16860d3760e869800ea98bea181070a --- /dev/null +++ b/qt/python/mantidqt/widgets/samplelogs/view.py @@ -0,0 +1,173 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +# This file is part of the mantid workbench. +# +# +from __future__ import (absolute_import, division, print_function) +from qtpy.QtWidgets import (QTableView, QHBoxLayout, QVBoxLayout, + QAbstractItemView, QFormLayout, QLineEdit, + QHeaderView, QLabel, QCheckBox, QMenu, + QSizePolicy, QSpinBox, QSplitter, QFrame) +from qtpy.QtCore import QItemSelectionModel, Qt +from matplotlib.backends.backend_qt5agg import FigureCanvas +from matplotlib.figure import Figure +import matplotlib.pyplot as plt + + +class SampleLogsView(QSplitter): + """Sample Logs View + + This contains a table of the logs, a plot of the currently + selected logs, and the statistics of the selected log. + """ + def __init__(self, presenter, parent = None, name = '', isMD=False, noExp = 0): + super(SampleLogsView, self).__init__(parent) + + self.presenter = presenter + + self.setWindowTitle("{} sample logs".format(name)) + self.setWindowFlags(Qt.Window) + + # Create sample log table + self.table = QTableView() + self.table.setSelectionBehavior(QAbstractItemView.SelectRows) + self.table.clicked.connect(self.presenter.clicked) + self.table.doubleClicked.connect(self.presenter.doubleClicked) + self.table.contextMenuEvent = self.tableMenu + self.addWidget(self.table) + + frame_right = QFrame() + layout_right = QVBoxLayout() + + #Add full_time and experimentinfo options + layout_options = QHBoxLayout() + + if isMD: + layout_options.addWidget(QLabel("Experiment Info #")) + self.experimentInfo = QSpinBox() + self.experimentInfo.setMaximum(noExp-1) + self.experimentInfo.valueChanged.connect(self.presenter.changeExpInfo) + layout_options.addWidget(self.experimentInfo) + + self.full_time = QCheckBox("Relative Time") + self.full_time.setChecked(True) + self.full_time.stateChanged.connect(self.presenter.plot_logs) + layout_options.addWidget(self.full_time) + layout_right.addLayout(layout_options) + + # Sample log plot + self.fig = Figure() + self.canvas = FigureCanvas(self.fig) + self.canvas.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding) + self.canvas.mpl_connect('button_press_event', self.presenter.plot_clicked) + self.ax = self.fig.add_subplot(111, projection='mantid') + layout_right.addWidget(self.canvas) + + # Sample stats + self.create_stats_widgets() + layout_stats = QFormLayout() + layout_stats.addRow('', QLabel("Log Statistics")) + layout_stats.addRow('Min:', self.stats_widgets["minimum"]) + layout_stats.addRow('Max:', self.stats_widgets["maximum"]) + layout_stats.addRow('Mean:', self.stats_widgets["mean"]) + layout_stats.addRow('Median:', self.stats_widgets["median"]) + layout_stats.addRow('Std Dev:', self.stats_widgets["standard_deviation"]) + layout_stats.addRow('Time Avg:', self.stats_widgets["time_mean"]) + layout_stats.addRow('Time Std Dev:', self.stats_widgets["time_standard_deviation"]) + layout_stats.addRow('Duration:', self.stats_widgets["duration"]) + layout_right.addLayout(layout_stats) + frame_right.setLayout(layout_right) + + self.addWidget(frame_right) + self.setStretchFactor(0,1) + + self.resize(1200,800) + self.show() + + def tableMenu(self, event): + """Right click menu for table, can plot or print selected logs""" + menu = QMenu(self) + plotAction = menu.addAction("Plot selected") + plotAction.triggered.connect(self.presenter.new_plot_logs) + plotAction = menu.addAction("Print selected") + plotAction.triggered.connect(self.presenter.print_selected_logs) + menu.exec_(event.globalPos()) + + def set_model(self, model): + """Set the model onto the table""" + self.model = model + self.table.setModel(self.model) + self.table.resizeColumnsToContents() + self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) + + def plot_selected_logs(self, ws, exp, rows): + """Update the plot with the selected rows""" + self.ax.clear() + self.create_ax_by_rows(self.ax, ws, exp, rows) + self.fig.canvas.draw() + + def new_plot_selected_logs(self, ws, exp, rows): + """Create a new plot, in a separate window for selected rows""" + fig, ax = plt.subplots(subplot_kw={'projection': 'mantid'}) + self.create_ax_by_rows(ax, ws, exp, rows) + fig.show() + + def create_ax_by_rows(self, ax, ws, exp, rows): + """Creates the plots for given rows onto axis ax""" + for row in rows: + log_text = self.get_row_log_name(row) + ax.plot(ws, + LogName=log_text, + label=log_text, + marker='.', + FullTime=not self.full_time.isChecked(), + ExperimentInfo=exp) + + ax.set_ylabel('') + if ax.get_legend_handles_labels()[0]: + ax.legend() + + def get_row_log_name(self, i): + """Returns the log name of particular row""" + return str(self.model.item(i, 0).text()) + + def get_exp(self): + """Get set experiment info number""" + return self.experimentInfo.value() + + def get_selected_row_indexes(self): + """Return a list of selected row from table""" + return [row.row() for row in self.table.selectionModel().selectedRows()] + + def set_selected_rows(self, rows): + """Set seleceted rows in table""" + mode = QItemSelectionModel.Select | QItemSelectionModel.Rows + for row in rows: + self.table.selectionModel().select(self.model.index(row, 0), mode) + + def create_stats_widgets(self): + """Creates the statistics widgets""" + self.stats_widgets = {"minimum": QLineEdit(), + "maximum": QLineEdit(), + "mean": QLineEdit(), + "median": QLineEdit(), + "standard_deviation": QLineEdit(), + "time_mean": QLineEdit(), + "time_standard_deviation": QLineEdit(), + "duration": QLineEdit()} + for widget in self.stats_widgets.values(): + widget.setReadOnly(True) + + def set_statistics(self, stats): + """Updates the statistics widgets from stats dictionary""" + for param in self.stats_widgets.keys(): + self.stats_widgets[param].setText('{:.6}'.format(getattr(stats, param))) + + def clear_statistics(self): + """Clears the values in statistics widgets""" + for widget in self.stats_widgets.values(): + widget.clear() diff --git a/qt/python/mantidqtpython/CMakeLists.txt b/qt/python/mantidqtpython/CMakeLists.txt index 85660f831f3ef74e3de958dbc72ab106a7aea00d..ec8e24440984c922a0c625ce0e2d38233b6459d5 100644 --- a/qt/python/mantidqtpython/CMakeLists.txt +++ b/qt/python/mantidqtpython/CMakeLists.txt @@ -92,6 +92,7 @@ mtd_add_sip_module ( HEADER_DEPS ${HEADER_DEPENDS} INCLUDE_DIRS ${CMAKE_CURRENT_LIST_DIR} + ${PYTHON_INCLUDE_PATH} ../../../Framework/PythonInterface/core/inc PYQT_VERSION 4 LINK_LIBS ${TCMALLOC_LIBRARIES_LINKTIME} diff --git a/qt/python/mantidqtpython/mantidqtpython_def.sip b/qt/python/mantidqtpython/mantidqtpython_def.sip index 020ee77372edabe511d97e084902a62872328aea..e01221bd94d4ffd451015325e695b6beb4115e38 100644 --- a/qt/python/mantidqtpython/mantidqtpython_def.sip +++ b/qt/python/mantidqtpython/mantidqtpython_def.sip @@ -2528,6 +2528,9 @@ public: void setColorMapRange(double, double); void selectComponent(const QString &); void setScaleType(GraphOptions::ScaleType); + %MethodCode + sipCpp->setScaleType(MantidQt::MantidWidgets::ColorMap::ScaleType(a0)); + %End void setViewType(const QString &); private: @@ -2546,6 +2549,9 @@ public: %Docstring Returns the current scale type. 0=Linear, 1=Log10 %End +%MethodCode + sipRes = static_cast<GraphOptions::ScaleType>(sipCpp->getScaleType()); +%End void setScaleType(GraphOptions::ScaleType type); %Docstring @@ -2554,6 +2560,9 @@ public: Args: type The new type Options are: GraphOptions.Linear, GraphOptions.Log10 %End +%MethodCode + sipCpp->setScaleType(MantidQt::MantidWidgets::ColorMap::ScaleType(a0)); +%End void setAxis(const QString& axisName); %Docstring diff --git a/qt/scientific_interfaces/EnggDiffraction/EnggDiffractionViewQtGUI.h b/qt/scientific_interfaces/EnggDiffraction/EnggDiffractionViewQtGUI.h index ade24fd48825ea2253f3b0829142716217422b79..6f249bc41c2bcc17a77c1d20cb1dec05d5172848 100644 --- a/qt/scientific_interfaces/EnggDiffraction/EnggDiffractionViewQtGUI.h +++ b/qt/scientific_interfaces/EnggDiffraction/EnggDiffractionViewQtGUI.h @@ -22,8 +22,6 @@ #include "ui_EnggDiffractionQtTabPreproc.h" #include "ui_EnggDiffractionQtTabSettings.h" -#include <boost/scoped_ptr.hpp> - // Qt classes forward declarations class QMessageBox; class QMutex; diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflAsciiSaver.cpp b/qt/scientific_interfaces/ISISReflectometry/ReflAsciiSaver.cpp index 7efb2651bc7a447037ed7da32b6a17b33b086d53..654d3671e4e9204624a97767ea4c6bb56e66272e 100644 --- a/qt/scientific_interfaces/ISISReflectometry/ReflAsciiSaver.cpp +++ b/qt/scientific_interfaces/ISISReflectometry/ReflAsciiSaver.cpp @@ -53,7 +53,7 @@ bool ReflAsciiSaver::isValidSaveDirectory(std::string const &path) const { try { auto pocoPath = Poco::Path().parseDirectory(path); auto pocoFile = Poco::File(pocoPath); - return pocoFile.exists() && pocoFile.canWrite(); + return pocoFile.exists(); } catch (Poco::PathSyntaxException &) { return false; } diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabPresenter.cpp b/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabPresenter.cpp index a21c30d46699d20ff30350461d3dfec283b0c478..356032b30427e1dd7d86a446d5a8a78d743db69b 100644 --- a/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabPresenter.cpp +++ b/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabPresenter.cpp @@ -29,7 +29,6 @@ #include <QStringList> #include <algorithm> -#include <boost/regex.hpp> #include <boost/tokenizer.hpp> #include <fstream> #include <iterator> diff --git a/qt/scientific_interfaces/Indirect/AbsorptionCorrections.cpp b/qt/scientific_interfaces/Indirect/AbsorptionCorrections.cpp index 55c3da939f26fe990b2b7879982cd8fe3ed68bcd..ca02cde8744d83ded69302e3bac015e11eb30e24 100644 --- a/qt/scientific_interfaces/Indirect/AbsorptionCorrections.cpp +++ b/qt/scientific_interfaces/Indirect/AbsorptionCorrections.cpp @@ -21,8 +21,27 @@ using namespace Mantid::Geometry; namespace { Mantid::Kernel::Logger g_log("AbsorptionCorrections"); +bool isWorkspaceInADS(std::string const &workspaceName) { + return AnalysisDataService::Instance().doesExist(workspaceName); +} + +MatrixWorkspace_sptr getADSMatrixWorkspace(std::string const &workspaceName) { + return AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>( + workspaceName); +} + +WorkspaceGroup_sptr getADSWorkspaceGroup(std::string const &workspaceName) { + return AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>( + workspaceName); +} + +template <typename T> +void addWorkspaceToADS(std::string const &workspaceName, T const &workspace) { + AnalysisDataService::Instance().addOrReplace(workspaceName, workspace); +} + MatrixWorkspace_sptr convertUnits(MatrixWorkspace_sptr workspace, - const std::string &target) { + std::string const &target) { auto convertAlg = AlgorithmManager::Instance().create("ConvertUnits"); convertAlg->initialize(); convertAlg->setChild(true); @@ -38,7 +57,7 @@ MatrixWorkspace_sptr convertUnits(MatrixWorkspace_sptr workspace, } WorkspaceGroup_sptr -groupWorkspaces(const std::vector<std::string> &workspaceNames) { +groupWorkspaces(std::vector<std::string> const &workspaceNames) { auto groupAlg = AlgorithmManager::Instance().create("GroupWorkspaces"); groupAlg->initialize(); groupAlg->setChild(true); @@ -49,15 +68,15 @@ groupWorkspaces(const std::vector<std::string> &workspaceNames) { } WorkspaceGroup_sptr convertUnits(WorkspaceGroup_sptr workspaceGroup, - const std::string &target) { + std::string const &target) { std::vector<std::string> convertedNames; convertedNames.reserve(workspaceGroup->size()); - for (const auto &workspace : *workspaceGroup) { - const auto name = "__" + workspace->getName() + "_" + target; - const auto wavelengthWorkspace = convertUnits( + for (auto const &workspace : *workspaceGroup) { + auto const name = "__" + workspace->getName() + "_" + target; + auto const wavelengthWorkspace = convertUnits( boost::dynamic_pointer_cast<MatrixWorkspace>(workspace), target); - AnalysisDataService::Instance().addOrReplace(name, wavelengthWorkspace); + addWorkspaceToADS(name, wavelengthWorkspace); convertedNames.emplace_back(name); } return groupWorkspaces(convertedNames); @@ -77,17 +96,14 @@ AbsorptionCorrections::AbsorptionCorrections(QWidget *parent) // Change of input connect(m_uiForm.dsSampleInput, SIGNAL(dataReady(const QString &)), this, - SLOT(getBeamDefaults(const QString &))); - connect(m_uiForm.dsSampleInput, SIGNAL(dataReady(const QString &)), this, - SLOT(getMonteCarloDefaults(const QString &))); - + SLOT(getParameterDefaults(const QString &))); // Handle algorithm completion connect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this, SLOT(algorithmComplete(bool))); - // Handle plotting and saving - connect(m_uiForm.pbSave, SIGNAL(clicked()), this, SLOT(saveClicked())); + // Handle running, plotting and saving + connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); connect(m_uiForm.pbPlot, SIGNAL(clicked()), this, SLOT(plotClicked())); - + connect(m_uiForm.pbSave, SIGNAL(clicked()), this, SLOT(saveClicked())); // Handle density units connect(m_uiForm.cbSampleDensity, SIGNAL(currentIndexChanged(int)), this, SLOT(changeSampleDensityUnit(int))); @@ -103,25 +119,22 @@ AbsorptionCorrections::AbsorptionCorrections(QWidget *parent) } AbsorptionCorrections::~AbsorptionCorrections() { - if (AnalysisDataService::Instance().doesExist("__mc_corrections_wavelength")) + if (isWorkspaceInADS("__mc_corrections_wavelength")) AnalysisDataService::Instance().remove("__mc_corrections_wavelength"); } MatrixWorkspace_sptr AbsorptionCorrections::sampleWorkspace() const { - const auto sampleWSName = - m_uiForm.dsSampleInput->getCurrentDataName().toStdString(); - - if (AnalysisDataService::Instance().doesExist(sampleWSName)) - return AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>( - sampleWSName); - return nullptr; + auto const name = m_uiForm.dsSampleInput->getCurrentDataName().toStdString(); + return isWorkspaceInADS(name) ? getADSMatrixWorkspace(name) : nullptr; } void AbsorptionCorrections::setup() { doValidation(); } void AbsorptionCorrections::run() { + setRunIsRunning(true); + // Get correct corrections algorithm - QString sampleShape = m_uiForm.cbShape->currentText().replace(" ", ""); + QString const sampleShape = m_uiForm.cbShape->currentText().replace(" ", ""); IAlgorithm_sptr monteCarloAbsCor = AlgorithmManager::Instance().create("CalculateMonteCarloAbsorption"); @@ -132,7 +145,7 @@ void AbsorptionCorrections::run() { addShapeSpecificSampleOptions(monteCarloAbsCor, sampleShape); // Sample details - QString sampleWsName = m_uiForm.dsSampleInput->getCurrentDataName(); + QString const sampleWsName = m_uiForm.dsSampleInput->getCurrentDataName(); monteCarloAbsCor->setProperty("SampleWorkspace", sampleWsName.toStdString()); monteCarloAbsCor->setProperty( @@ -141,28 +154,29 @@ void AbsorptionCorrections::run() { monteCarloAbsCor->setProperty("SampleDensity", m_uiForm.spSampleDensity->value()); - QString sampleChemicalFormula = m_uiForm.leSampleChemicalFormula->text(); + QString const sampleChemicalFormula = + m_uiForm.leSampleChemicalFormula->text(); monteCarloAbsCor->setProperty("SampleChemicalFormula", sampleChemicalFormula.toStdString()); // General details monteCarloAbsCor->setProperty("BeamHeight", m_uiForm.spBeamHeight->value()); monteCarloAbsCor->setProperty("BeamWidth", m_uiForm.spBeamWidth->value()); - long wave = static_cast<long>(m_uiForm.spNumberWavelengths->value()); + long const wave = static_cast<long>(m_uiForm.spNumberWavelengths->value()); monteCarloAbsCor->setProperty("NumberOfWavelengthPoints", wave); - long events = static_cast<long>(m_uiForm.spNumberEvents->value()); + long const events = static_cast<long>(m_uiForm.spNumberEvents->value()); monteCarloAbsCor->setProperty("EventsPerPoint", events); auto const interpolation = m_uiForm.cbInterpolation->currentText().toStdString(); monteCarloAbsCor->setProperty("Interpolation", interpolation); - long maxAttempts = + long const maxAttempts = static_cast<long>(m_uiForm.spMaxScatterPtAttempts->value()); monteCarloAbsCor->setProperty("MaxScatterPtAttempts", maxAttempts); // Can details - bool useCan = m_uiForm.ckUseCan->isChecked(); + bool const useCan = m_uiForm.ckUseCan->isChecked(); if (useCan) { - std::string canWsName = + std::string const canWsName = m_uiForm.dsCanInput->getCurrentDataName().toStdString(); monteCarloAbsCor->setProperty("ContainerWorkspace", canWsName); @@ -172,7 +186,7 @@ void AbsorptionCorrections::run() { monteCarloAbsCor->setProperty("ContainerDensity", m_uiForm.spCanDensity->value()); - const auto canChemicalFormula = m_uiForm.leCanChemicalFormula->text(); + auto const canChemicalFormula = m_uiForm.leCanChemicalFormula->text(); monteCarloAbsCor->setProperty("ContainerChemicalFormula", canChemicalFormula.toStdString()); @@ -184,7 +198,7 @@ void AbsorptionCorrections::run() { if (nameCutIndex == -1) nameCutIndex = sampleWsName.length(); - const auto outputWsName = + auto const outputWsName = sampleWsName.left(nameCutIndex) + "_" + sampleShape + "_MC_Corrections"; monteCarloAbsCor->setProperty("CorrectionsWorkspace", @@ -212,33 +226,33 @@ void AbsorptionCorrections::addShapeSpecificSampleOptions(IAlgorithm_sptr alg, QString shape) { if (shape == "FlatPlate") { - double sampleHeight = m_uiForm.spFlatSampleHeight->value(); + double const sampleHeight = m_uiForm.spFlatSampleHeight->value(); alg->setProperty("Height", sampleHeight); - double sampleWidth = m_uiForm.spFlatSampleWidth->value(); + double const sampleWidth = m_uiForm.spFlatSampleWidth->value(); alg->setProperty("SampleWidth", sampleWidth); - double sampleThickness = m_uiForm.spFlatSampleThickness->value(); + double const sampleThickness = m_uiForm.spFlatSampleThickness->value(); alg->setProperty("SampleThickness", sampleThickness); - double sampleAngle = m_uiForm.spFlatSampleAngle->value(); + double const sampleAngle = m_uiForm.spFlatSampleAngle->value(); alg->setProperty("SampleAngle", sampleAngle); } else if (shape == "Annulus") { - double sampleHeight = m_uiForm.spAnnSampleHeight->value(); + double const sampleHeight = m_uiForm.spAnnSampleHeight->value(); alg->setProperty("Height", sampleHeight); - double sampleInnerRadius = m_uiForm.spAnnSampleInnerRadius->value(); + double const sampleInnerRadius = m_uiForm.spAnnSampleInnerRadius->value(); alg->setProperty("SampleInnerRadius", sampleInnerRadius); - double sampleOuterRadius = m_uiForm.spAnnSampleOuterRadius->value(); + double const sampleOuterRadius = m_uiForm.spAnnSampleOuterRadius->value(); alg->setProperty("SampleOuterRadius", sampleOuterRadius); } else if (shape == "Cylinder") { - double sampleRadius = m_uiForm.spCylSampleRadius->value(); + double const sampleRadius = m_uiForm.spCylSampleRadius->value(); alg->setProperty("SampleRadius", sampleRadius); - double sampleHeight = m_uiForm.spCylSampleHeight->value(); + double const sampleHeight = m_uiForm.spCylSampleHeight->value(); alg->setProperty("Height", sampleHeight); } } @@ -252,25 +266,25 @@ void AbsorptionCorrections::addShapeSpecificSampleOptions(IAlgorithm_sptr alg, * @param shape Sample shape */ void AbsorptionCorrections::addShapeSpecificCanOptions(IAlgorithm_sptr alg, - QString shape) { + QString const &shape) { if (shape == "FlatPlate") { - double canFrontThickness = m_uiForm.spFlatCanFrontThickness->value(); + double const canFrontThickness = m_uiForm.spFlatCanFrontThickness->value(); alg->setProperty("ContainerFrontThickness", canFrontThickness); - double canBackThickness = m_uiForm.spFlatCanBackThickness->value(); + double const canBackThickness = m_uiForm.spFlatCanBackThickness->value(); alg->setProperty("ContainerBackThickness", canBackThickness); } else if (shape == "Cylinder") { - double canInnerRadius = m_uiForm.spCylSampleRadius->value(); + double const canInnerRadius = m_uiForm.spCylSampleRadius->value(); alg->setProperty("ContainerInnerRadius", canInnerRadius); - double canOuterRadius = m_uiForm.spCylCanOuterRadius->value(); + double const canOuterRadius = m_uiForm.spCylCanOuterRadius->value(); alg->setProperty("ContainerOuterRadius", canOuterRadius); } else if (shape == "Annulus") { - double canInnerRadius = m_uiForm.spAnnCanInnerRadius->value(); + double const canInnerRadius = m_uiForm.spAnnCanInnerRadius->value(); alg->setProperty("ContainerInnerRadius", canInnerRadius); - double canOuterRadius = m_uiForm.spAnnCanOuterRadius->value(); + double const canOuterRadius = m_uiForm.spAnnCanOuterRadius->value(); alg->setProperty("ContainerOuterRadius", canOuterRadius); } } @@ -280,10 +294,9 @@ bool AbsorptionCorrections::validate() { // Give error for failed validation if (!uiv.isAllInputValid()) { - QString error = uiv.generateErrorMessage(); + auto const error = uiv.generateErrorMessage(); showMessageBox(error); } - return uiv.isAllInputValid(); } @@ -301,7 +314,7 @@ UserInputValidator AbsorptionCorrections::doValidation() { uiv.checkFieldIsValid("Sample Chemical Formula", m_uiForm.leSampleChemicalFormula, m_uiForm.valSampleChemicalFormula); - const auto sampleChem = + auto const sampleChem = m_uiForm.leSampleChemicalFormula->text().toStdString(); try { Mantid::Kernel::Material::parseChemicalFormula(sampleChem); @@ -313,20 +326,16 @@ UserInputValidator AbsorptionCorrections::doValidation() { bool useCan = m_uiForm.ckUseCan->isChecked(); if (useCan) { - const auto containerChem = + auto const containerChem = m_uiForm.leCanChemicalFormula->text().toStdString(); uiv.checkDataSelectorIsValid("Container", m_uiForm.dsCanInput); - const auto containerWsName = + auto const containerWsName = m_uiForm.dsCanInput->getCurrentDataName().toStdString(); - bool containerExists = - AnalysisDataService::Instance().doesExist(containerWsName); - if (containerExists && - !AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>( - containerWsName)) { + if (isWorkspaceInADS(containerWsName) && + !getADSMatrixWorkspace(containerWsName)) uiv.addErrorMessage( "Invalid container workspace. Ensure a MatrixWorkspace is provided."); - } if (uiv.checkFieldIsNotEmpty("Container Chemical Formula", m_uiForm.leCanChemicalFormula, @@ -354,105 +363,100 @@ void AbsorptionCorrections::loadSettings(const QSettings &settings) { m_uiForm.dsCanInput->readSettings(settings.group()); } +void AbsorptionCorrections::processWavelengthWorkspace() { + auto const correctionsWs = getADSWorkspaceGroup(m_pythonExportWsName); + if (correctionsWs) { + auto const wavelengthWorkspace = convertUnits(correctionsWs, "Wavelength"); + addWorkspaceToADS("__mc_corrections_wavelength", wavelengthWorkspace); + } +} + /** * Handle completion of the absorption correction algorithm. * * @param error True if algorithm has failed. */ void AbsorptionCorrections::algorithmComplete(bool error) { - if (error) { + setRunIsRunning(false); + if (!error) + processWavelengthWorkspace(); + else { + setPlotResultEnabled(false); + setSaveResultEnabled(false); emit showMessageBox( "Could not run absorption corrections.\nSee Results Log for details."); - return; - } - - auto correctionsWorkspace = - AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>( - m_pythonExportWsName); - - if (correctionsWorkspace) { - auto wavelengthWorkspace = convertUnits(correctionsWorkspace, "Wavelength"); - AnalysisDataService::Instance().addOrReplace("__mc_corrections_wavelength", - wavelengthWorkspace); - - // Enable plot and save - m_uiForm.pbPlot->setEnabled(true); - m_uiForm.cbPlotOutput->setEnabled(true); - m_uiForm.pbSave->setEnabled(true); } } -void AbsorptionCorrections::getBeamDefaults(const QString &dataName) { - auto sampleWs = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>( - dataName.toStdString()); - - if (!sampleWs) { +void AbsorptionCorrections::getParameterDefaults(QString const &dataName) { + auto const sampleWs = getADSMatrixWorkspace(dataName.toStdString()); + if (sampleWs) + getParameterDefaults(sampleWs->getInstrument()); + else displayInvalidWorkspaceTypeError(dataName.toStdString(), g_log); - return; - } +} + +void AbsorptionCorrections::getParameterDefaults( + Instrument_const_sptr instrument) { + setBeamWidthValue(instrument, "Workflow.beam-width"); + setBeamHeightValue(instrument, "Workflow.beam-height"); + setWavelengthsValue(instrument, "Workflow.absorption-wavelengths"); + setEventsValue(instrument, "Workflow.absorption-events"); + setInterpolationValue(instrument, "Workflow.absorption-interpolation"); + setMaxAttemptsValue(instrument, "Workflow.absorption-attempts"); +} - auto instrument = sampleWs->getInstrument(); - const std::string beamWidthParamName = "Workflow.beam-width"; +void AbsorptionCorrections::setBeamWidthValue( + Instrument_const_sptr instrument, + std::string const &beamWidthParamName) const { if (instrument->hasParameter(beamWidthParamName)) { - const auto beamWidth = QString::fromStdString( + auto const beamWidth = QString::fromStdString( instrument->getStringParameter(beamWidthParamName)[0]); - const auto beamWidthValue = beamWidth.toDouble(); - + auto const beamWidthValue = beamWidth.toDouble(); m_uiForm.spBeamWidth->setValue(beamWidthValue); } - const std::string beamHeightParamName = "Workflow.beam-height"; +} + +void AbsorptionCorrections::setBeamHeightValue( + Instrument_const_sptr instrument, + std::string const &beamHeightParamName) const { if (instrument->hasParameter(beamHeightParamName)) { - const auto beamHeight = QString::fromStdString( + auto const beamHeight = QString::fromStdString( instrument->getStringParameter(beamHeightParamName)[0]); - const auto beamHeightValue = beamHeight.toDouble(); - + auto const beamHeightValue = beamHeight.toDouble(); m_uiForm.spBeamHeight->setValue(beamHeightValue); } } -void AbsorptionCorrections::getMonteCarloDefaults(const QString &dataName) { - auto sampleWs = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>( - dataName.toStdString()); - - if (sampleWs) { - auto instrument = sampleWs->getInstrument(); - setWavelengthsValue(instrument, "Workflow.absorption-wavelengths"); - setEventsValue(instrument, "Workflow.absorption-events"); - setInterpolationValue(instrument, "Workflow.absorption-interpolation"); - setMaxAttemptsValue(instrument, "Workflow.absorption-attempts"); - } else - displayInvalidWorkspaceTypeError(dataName.toStdString(), g_log); -} - void AbsorptionCorrections::setWavelengthsValue( Instrument_const_sptr instrument, - const std::string &wavelengthsParamName) const { + std::string const &wavelengthsParamName) const { if (instrument->hasParameter(wavelengthsParamName)) { - const auto wavelengths = QString::fromStdString( + auto const wavelengths = QString::fromStdString( instrument->getStringParameter(wavelengthsParamName)[0]); - const auto wavelengthsValue = wavelengths.toInt(); + auto const wavelengthsValue = wavelengths.toInt(); m_uiForm.spNumberWavelengths->setValue(wavelengthsValue); } } void AbsorptionCorrections::setEventsValue( Instrument_const_sptr instrument, - const std::string &eventsParamName) const { + std::string const &eventsParamName) const { if (instrument->hasParameter(eventsParamName)) { - const auto events = QString::fromStdString( + auto const events = QString::fromStdString( instrument->getStringParameter(eventsParamName)[0]); - const auto eventsValue = events.toInt(); + auto const eventsValue = events.toInt(); m_uiForm.spNumberEvents->setValue(eventsValue); } } void AbsorptionCorrections::setInterpolationValue( Instrument_const_sptr instrument, - const std::string &interpolationParamName) const { + std::string const &interpolationParamName) const { if (instrument->hasParameter(interpolationParamName)) { - const auto interpolation = QString::fromStdString( + auto const interpolation = QString::fromStdString( instrument->getStringParameter(interpolationParamName)[0]); - const auto interpolationValue = interpolation.toStdString(); + auto const interpolationValue = interpolation.toStdString(); m_uiForm.cbInterpolation->setCurrentIndex( interpolationValue == "CSpline" ? 1 : 0); } @@ -460,36 +464,30 @@ void AbsorptionCorrections::setInterpolationValue( void AbsorptionCorrections::setMaxAttemptsValue( Instrument_const_sptr instrument, - const std::string &maxAttemptsParamName) const { + std::string const &maxAttemptsParamName) const { if (instrument->hasParameter(maxAttemptsParamName)) { - const auto maxScatterAttempts = QString::fromStdString( + auto const maxScatterAttempts = QString::fromStdString( instrument->getStringParameter(maxAttemptsParamName)[0]); - const auto maxScatterAttemptsValue = maxScatterAttempts.toInt(); + auto const maxScatterAttemptsValue = maxScatterAttempts.toInt(); m_uiForm.spMaxScatterPtAttempts->setValue(maxScatterAttemptsValue); } } -/** - * Handle saving of workspace - */ -void AbsorptionCorrections::saveClicked() { - - if (checkADSForPlotSaveWorkspace(m_pythonExportWsName, false)) - addSaveWorkspaceToQueue(QString::fromStdString(m_pythonExportWsName)); - - std::string factorsWs = - m_absCorAlgo->getPropertyValue("CorrectionsWorkspace"); - if (checkADSForPlotSaveWorkspace(factorsWs, false)) - addSaveWorkspaceToQueue(QString::fromStdString(factorsWs)); +void AbsorptionCorrections::addSaveWorkspace(std::string const &workspaceName) { + if (checkADSForPlotSaveWorkspace(workspaceName, false)) + addSaveWorkspaceToQueue(QString::fromStdString(workspaceName)); +} +void AbsorptionCorrections::saveClicked() { + auto const factorsWs = m_absCorAlgo->getPropertyValue("CorrectionsWorkspace"); + addSaveWorkspace(m_pythonExportWsName); + addSaveWorkspace(factorsWs); m_batchAlgoRunner->executeBatchAsync(); } -/** - * Handle mantid plotting - */ void AbsorptionCorrections::plotClicked() { - const auto plotType = m_uiForm.cbPlotOutput->currentText(); + setPlotResultIsPlotting(true); + auto const plotType = m_uiForm.cbPlotOutput->currentText(); if (checkADSForPlotSaveWorkspace("__mc_corrections_wavelength", false)) { if (plotType == "Both" || plotType == "Wavelength") @@ -498,30 +496,48 @@ void AbsorptionCorrections::plotClicked() { if (plotType == "Both" || plotType == "Angle") plotTimeBin(QString::fromStdString("__mc_corrections_wavelength")); } + setPlotResultIsPlotting(false); } -/** - * Handle changing of the sample density unit - */ -void AbsorptionCorrections::changeSampleDensityUnit(int index) { +void AbsorptionCorrections::runClicked() { runTab(); } - if (index == 0) { - m_uiForm.spSampleDensity->setSuffix(" g/cm3"); - } else { - m_uiForm.spSampleDensity->setSuffix(" 1/A3"); - } +void AbsorptionCorrections::changeSampleDensityUnit(int index) { + QString const suffix = index == 0 ? " g/cm3" : " 1/A3"; + m_uiForm.spSampleDensity->setSuffix(suffix); } -/** - * Handle changing of the container density unit - */ void AbsorptionCorrections::changeCanDensityUnit(int index) { + QString const suffix = index == 0 ? " g/cm3" : " 1/A3"; + m_uiForm.spCanDensity->setSuffix(suffix); +} - if (index == 0) { - m_uiForm.spCanDensity->setSuffix(" g/cm3"); - } else { - m_uiForm.spCanDensity->setSuffix(" 1/A3"); - } +void AbsorptionCorrections::setRunEnabled(bool enabled) { + m_uiForm.pbRun->setEnabled(enabled); +} + +void AbsorptionCorrections::setPlotResultEnabled(bool enabled) { + m_uiForm.pbPlot->setEnabled(enabled); + m_uiForm.cbPlotOutput->setEnabled(enabled); +} + +void AbsorptionCorrections::setSaveResultEnabled(bool enabled) { + m_uiForm.pbSave->setEnabled(enabled); +} + +void AbsorptionCorrections::setButtonsEnabled(bool enabled) { + setRunEnabled(enabled); + setPlotResultEnabled(enabled); + setSaveResultEnabled(enabled); +} + +void AbsorptionCorrections::setRunIsRunning(bool running) { + m_uiForm.pbRun->setText(running ? "Running..." : "Run"); + setButtonsEnabled(!running); +} + +void AbsorptionCorrections::setPlotResultIsPlotting(bool plotting) { + m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot"); + setButtonsEnabled(!plotting); } } // namespace CustomInterfaces diff --git a/qt/scientific_interfaces/Indirect/AbsorptionCorrections.h b/qt/scientific_interfaces/Indirect/AbsorptionCorrections.h index 5033234364fd337d9963cb240822fdc54060e792..1b20fe231df58c813d8028bed4b0c4a329500b21 100644 --- a/qt/scientific_interfaces/Indirect/AbsorptionCorrections.h +++ b/qt/scientific_interfaces/Indirect/AbsorptionCorrections.h @@ -23,36 +23,50 @@ public: Mantid::API::MatrixWorkspace_sptr sampleWorkspace() const; -private: - void setup() override; - void run() override; - bool validate() override; - void loadSettings(const QSettings &settings) override; - private slots: virtual void algorithmComplete(bool error); void saveClicked(); void plotClicked(); - void getBeamDefaults(const QString &dataName); - void getMonteCarloDefaults(const QString &dataName); - void setWavelengthsValue(Mantid::Geometry::Instrument_const_sptr instrument, - const std::string &wavelengthsParamName) const; - void setEventsValue(Mantid::Geometry::Instrument_const_sptr instrument, - const std::string &eventsParamName) const; - void setInterpolationValue(Mantid::Geometry::Instrument_const_sptr instrument, - const std::string &interpolationParamName) const; - void setMaxAttemptsValue(Mantid::Geometry::Instrument_const_sptr instrument, - const std::string &maxAttemptsParamName) const; + void runClicked(); + void getParameterDefaults(QString const &dataName); void changeSampleDensityUnit(int); void changeCanDensityUnit(int); UserInputValidator doValidation(); private: - void addSaveWorkspace(QString wsName); + void setup() override; + void run() override; + bool validate() override; + void loadSettings(const QSettings &settings) override; + + void addSaveWorkspace(std::string const &wsName); void addShapeSpecificSampleOptions(Mantid::API::IAlgorithm_sptr alg, QString shape); void addShapeSpecificCanOptions(Mantid::API::IAlgorithm_sptr alg, - QString shape); + QString const &shape); + + void processWavelengthWorkspace(); + + void getParameterDefaults(Mantid::Geometry::Instrument_const_sptr instrument); + void setBeamWidthValue(Mantid::Geometry::Instrument_const_sptr instrument, + std::string const &beamWidthParamName) const; + void setBeamHeightValue(Mantid::Geometry::Instrument_const_sptr instrument, + std::string const &beamHeightParamName) const; + void setWavelengthsValue(Mantid::Geometry::Instrument_const_sptr instrument, + std::string const &wavelengthsParamName) const; + void setEventsValue(Mantid::Geometry::Instrument_const_sptr instrument, + std::string const &eventsParamName) const; + void setInterpolationValue(Mantid::Geometry::Instrument_const_sptr instrument, + std::string const &interpolationParamName) const; + void setMaxAttemptsValue(Mantid::Geometry::Instrument_const_sptr instrument, + std::string const &maxAttemptsParamName) const; + + void setRunEnabled(bool enabled); + void setPlotResultEnabled(bool enabled); + void setSaveResultEnabled(bool enabled); + void setButtonsEnabled(bool enabled); + void setRunIsRunning(bool running); + void setPlotResultIsPlotting(bool plotting); Ui::AbsorptionCorrections m_uiForm; /// alg diff --git a/qt/scientific_interfaces/Indirect/AbsorptionCorrections.ui b/qt/scientific_interfaces/Indirect/AbsorptionCorrections.ui index e903abbbb87f1b41f4a0d2571507945eb8615587..15648ba70431f507084c4329216091635225a606 100644 --- a/qt/scientific_interfaces/Indirect/AbsorptionCorrections.ui +++ b/qt/scientific_interfaces/Indirect/AbsorptionCorrections.ui @@ -843,6 +843,66 @@ </layout> </widget> </item> + <item> + <widget class="QGroupBox" name="gbRun"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>45</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>45</height> + </size> + </property> + <property name="title"> + <string>Run</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>7</number> + </property> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>348</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbRun"> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>348</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> <item> <widget class="QGroupBox" name="gbOutput"> <property name="sizePolicy"> diff --git a/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.cpp b/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.cpp index 8d0317a8feca87b67069eb197f957240f2ee9729..266857b561a22642d43352a134fa261aa9f4b8c2 100644 --- a/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.cpp +++ b/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.cpp @@ -25,6 +25,7 @@ ApplyAbsorptionCorrections::ApplyAbsorptionCorrections(QWidget *parent) : CorrectionsTab(parent) { m_spectra = 0; m_uiForm.setupUi(parent); + connect(m_uiForm.cbGeometry, SIGNAL(currentIndexChanged(int)), this, SLOT(handleGeometryChange(int))); connect(m_uiForm.dsSample, SIGNAL(dataReady(const QString &)), this, @@ -47,6 +48,7 @@ ApplyAbsorptionCorrections::ApplyAbsorptionCorrections(QWidget *parent) SLOT(updateContainer())); connect(m_uiForm.pbPlot, SIGNAL(clicked()), this, SLOT(plotClicked())); connect(m_uiForm.pbSave, SIGNAL(clicked()), this, SLOT(saveClicked())); + connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); connect(m_uiForm.pbPlotPreview, SIGNAL(clicked()), this, SLOT(plotCurrentPreview())); @@ -175,6 +177,8 @@ void ApplyAbsorptionCorrections::updateContainer() { } void ApplyAbsorptionCorrections::run() { + setRunIsRunning(true); + // Create / Initialize algorithm API::BatchAlgorithmRunner::AlgorithmRuntimeProps absCorProps; IAlgorithm_sptr applyCorrAlg = @@ -216,6 +220,9 @@ void ApplyAbsorptionCorrections::run() { m_uiForm.ckRebinContainer->setChecked(true); } else { m_batchAlgoRunner->clearQueue(); + setRunIsRunning(false); + setPlotResultEnabled(false); + setSaveResultEnabled(false); g_log.error("Cannot apply absorption corrections " "using a sample and " "container with different binning."); @@ -275,6 +282,9 @@ void ApplyAbsorptionCorrections::run() { break; default: m_batchAlgoRunner->clearQueue(); + setRunIsRunning(false); + setPlotResultEnabled(false); + setSaveResultEnabled(false); g_log.error( "ApplyAbsorptionCorrections cannot run with corrections that do " "not match sample binning."); @@ -374,30 +384,33 @@ void ApplyAbsorptionCorrections::addInterpolationStep( void ApplyAbsorptionCorrections::absCorComplete(bool error) { disconnect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this, SLOT(absCorComplete(bool))); - if (error) { - emit showMessageBox( - "Unable to apply corrections.\nSee Results Log for more details."); - return; - } - if (m_uiForm.ckUseCan->isChecked()) { - if (m_uiForm.ckShiftCan->isChecked()) { // If container is shifted - IAlgorithm_sptr shiftLog = - AlgorithmManager::Instance().create("AddSampleLog"); - shiftLog->initialize(); - - shiftLog->setProperty("Workspace", m_pythonExportWsName); - shiftLog->setProperty("LogName", "container_shift"); - shiftLog->setProperty("LogType", "Number"); - shiftLog->setProperty("LogText", boost::lexical_cast<std::string>( - m_uiForm.spCanShift->value())); - m_batchAlgoRunner->addAlgorithm(shiftLog); + if (!error) { + if (m_uiForm.ckUseCan->isChecked()) { + if (m_uiForm.ckShiftCan->isChecked()) { // If container is shifted + IAlgorithm_sptr shiftLog = + AlgorithmManager::Instance().create("AddSampleLog"); + shiftLog->initialize(); + + shiftLog->setProperty("Workspace", m_pythonExportWsName); + shiftLog->setProperty("LogName", "container_shift"); + shiftLog->setProperty("LogType", "Number"); + shiftLog->setProperty("LogText", boost::lexical_cast<std::string>( + m_uiForm.spCanShift->value())); + m_batchAlgoRunner->addAlgorithm(shiftLog); + } } + // Run algorithm queue + connect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this, + SLOT(postProcessComplete(bool))); + m_batchAlgoRunner->executeBatchAsync(); + } else { + setRunIsRunning(false); + setPlotResultEnabled(false); + setSaveResultEnabled(false); + emit showMessageBox( + "Unable to apply corrections.\nSee Results Log for more details."); } - // Run algorithm queue - connect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this, - SLOT(postProcessComplete(bool))); - m_batchAlgoRunner->executeBatchAsync(); } /** @@ -408,35 +421,32 @@ void ApplyAbsorptionCorrections::absCorComplete(bool error) { void ApplyAbsorptionCorrections::postProcessComplete(bool error) { disconnect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this, SLOT(postProcessComplete(bool))); + setRunIsRunning(false); - if (error) { - emit showMessageBox("Unable to process corrected workspace.\nSee Results " - "Log for more details."); - return; - } - - // Enable post processing plot and save - m_uiForm.cbPlotOutput->setEnabled(true); - m_uiForm.pbPlot->setEnabled(true); - m_uiForm.pbSave->setEnabled(true); - - // Handle preview plot - plotPreview(m_uiForm.spPreviewSpec->value()); + if (!error) { + // Handle preview plot + plotPreview(m_uiForm.spPreviewSpec->value()); - // Clean up unwanted workspaces - IAlgorithm_sptr deleteAlg = - AlgorithmManager::Instance().create("DeleteWorkspace"); - if (AnalysisDataService::Instance().doesExist("__algorithm_can")) { + // Clean up unwanted workspaces + IAlgorithm_sptr deleteAlg = + AlgorithmManager::Instance().create("DeleteWorkspace"); + if (AnalysisDataService::Instance().doesExist("__algorithm_can")) { - deleteAlg->initialize(); - deleteAlg->setProperty("Workspace", "__algorithm_can"); - deleteAlg->execute(); - } - const auto conv = - AnalysisDataService::Instance().doesExist("__algorithm_can_Wavelength"); - if (conv) { - deleteAlg->setProperty("Workspace", "__algorithm_can_Wavelength"); - deleteAlg->execute(); + deleteAlg->initialize(); + deleteAlg->setProperty("Workspace", "__algorithm_can"); + deleteAlg->execute(); + } + const auto conv = + AnalysisDataService::Instance().doesExist("__algorithm_can_Wavelength"); + if (conv) { + deleteAlg->setProperty("Workspace", "__algorithm_can_Wavelength"); + deleteAlg->execute(); + } + } else { + setPlotResultEnabled(false); + setSaveResultEnabled(false); + emit showMessageBox("Unable to process corrected workspace.\nSee Results " + "Log for more details."); } } @@ -539,7 +549,7 @@ void ApplyAbsorptionCorrections::plotPreview(int wsIndex) { if (AnalysisDataService::Instance().doesExist(m_pythonExportWsName)) m_uiForm.ppPreview->addSpectrum( "Corrected", QString::fromStdString(m_pythonExportWsName), wsIndex, - Qt::green); + Qt::blue); // Plot container if (m_ppContainerWS && useCan) { m_uiForm.ppPreview->addSpectrum( @@ -549,23 +559,17 @@ void ApplyAbsorptionCorrections::plotPreview(int wsIndex) { m_spectra = boost::numeric_cast<size_t>(wsIndex); } -/** - * Handles saving of the workspace - */ -void ApplyAbsorptionCorrections::saveClicked() { +void ApplyAbsorptionCorrections::saveClicked() { if (checkADSForPlotSaveWorkspace(m_pythonExportWsName, false)) addSaveWorkspaceToQueue(QString::fromStdString(m_pythonExportWsName)); m_batchAlgoRunner->executeBatchAsync(); } -/** - * Handles mantid plotting of workspace - */ void ApplyAbsorptionCorrections::plotClicked() { + setPlotResultIsPlotting(true); QString plotType = m_uiForm.cbPlotOutput->currentText(); - if (checkADSForPlotSaveWorkspace(m_pythonExportWsName, true)) { if (plotType == "Spectra" || plotType == "Both") @@ -574,8 +578,11 @@ void ApplyAbsorptionCorrections::plotClicked() { if (plotType == "Contour" || plotType == "Both") plot2D(QString::fromStdString(m_pythonExportWsName)); } + setPlotResultIsPlotting(false); } +void ApplyAbsorptionCorrections::runClicked() { runTab(); } + /** * Plots the current spectrum displayed in the preview plot */ @@ -636,5 +643,35 @@ void ApplyAbsorptionCorrections::plotInPreview(const QString &curveName, m_uiForm.spPreviewSpec->setMaximum(boost::numeric_cast<int>(m_spectra)); } } + +void ApplyAbsorptionCorrections::setRunEnabled(bool enabled) { + m_uiForm.pbRun->setEnabled(enabled); +} + +void ApplyAbsorptionCorrections::setPlotResultEnabled(bool enabled) { + m_uiForm.pbPlot->setEnabled(enabled); + m_uiForm.cbPlotOutput->setEnabled(enabled); +} + +void ApplyAbsorptionCorrections::setSaveResultEnabled(bool enabled) { + m_uiForm.pbSave->setEnabled(enabled); +} + +void ApplyAbsorptionCorrections::setButtonsEnabled(bool enabled) { + setRunEnabled(enabled); + setPlotResultEnabled(enabled); + setSaveResultEnabled(enabled); +} + +void ApplyAbsorptionCorrections::setRunIsRunning(bool running) { + m_uiForm.pbRun->setText(running ? "Running..." : "Run"); + setButtonsEnabled(!running); +} + +void ApplyAbsorptionCorrections::setPlotResultIsPlotting(bool plotting) { + m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot"); + setButtonsEnabled(!plotting); +} + } // namespace CustomInterfaces } // namespace MantidQt diff --git a/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.h b/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.h index a998b9a0e3051e115d74478960cc8ead8a1b4bad..274a8cde1120e1b0d3d7b69e534172768a283f6b 100644 --- a/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.h +++ b/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.h @@ -38,6 +38,7 @@ private slots: /// Handles mantid plot and save void saveClicked(); void plotClicked(); + void runClicked(); void plotCurrentPreview(); private: @@ -52,6 +53,13 @@ private: Mantid::API::MatrixWorkspace_sptr &ws, const QColor &curveColor); + void setRunEnabled(bool enabled); + void setPlotResultEnabled(bool enabled); + void setSaveResultEnabled(bool enabled); + void setButtonsEnabled(bool enabled); + void setRunIsRunning(bool running); + void setPlotResultIsPlotting(bool plotting); + Ui::ApplyAbsorptionCorrections m_uiForm; std::string m_originalSampleUnits; diff --git a/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.ui b/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.ui index b5c0aaa76ee74869b152375fdb836d44185a0526..4d13cf6e2ae2df758637e9a9560941dc3c6e5e4c 100644 --- a/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.ui +++ b/qt/scientific_interfaces/Indirect/ApplyAbsorptionCorrections.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>752</width> + <width>751</width> <height>744</height> </rect> </property> @@ -257,13 +257,24 @@ </widget> </item> <item> - <widget class="QGroupBox" name="groupBox"> + <widget class="QGroupBox" name="gbPreview"> <property name="title"> <string>Preview</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> - <widget class="MantidQt::MantidWidgets::PreviewPlot" name="ppPreview" native="true"/> + <widget class="MantidQt::MantidWidgets::PreviewPlot" name="ppPreview" native="true"> + <property name="showLegend" stdset="0"> + <bool>true</bool> + </property> + <property name="canvasColour" stdset="0"> + <color> + <red>255</red> + <green>255</green> + <blue>255</blue> + </color> + </property> + </widget> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_2"> @@ -302,6 +313,66 @@ </layout> </widget> </item> + <item> + <widget class="QGroupBox" name="gbRun"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>45</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>45</height> + </size> + </property> + <property name="title"> + <string>Run</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>7</number> + </property> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>310</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbRun"> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>310</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> <item> <widget class="QGroupBox" name="gbOutput"> <property name="title"> diff --git a/qt/scientific_interfaces/Indirect/CMakeLists.txt b/qt/scientific_interfaces/Indirect/CMakeLists.txt index 623e89206253c7f2677ceb61f24276114dcf608c..5b81eed0b127e45e07b33dbe3fb66bfc89858951 100644 --- a/qt/scientific_interfaces/Indirect/CMakeLists.txt +++ b/qt/scientific_interfaces/Indirect/CMakeLists.txt @@ -267,3 +267,6 @@ mtd_add_qt_library (TARGET_NAME MantidScientificInterfacesIndirect "\$ORIGIN/../../${LIB_DIR};\$ORIGIN/../../plugins/qt4/" ) + +# Testing target +add_subdirectory ( test ) diff --git a/qt/scientific_interfaces/Indirect/CalculatePaalmanPings.cpp b/qt/scientific_interfaces/Indirect/CalculatePaalmanPings.cpp index 2e0afadbc054866d8ab6e06230e769d2375990f9..a2963baf344f885c69c4bfe7c9a38e006f9d842a 100644 --- a/qt/scientific_interfaces/Indirect/CalculatePaalmanPings.cpp +++ b/qt/scientific_interfaces/Indirect/CalculatePaalmanPings.cpp @@ -47,9 +47,10 @@ CalculatePaalmanPings::CalculatePaalmanPings(QWidget *parent) SLOT(validateChemical())); connect(m_uiForm.leCanChemicalFormula, SIGNAL(editingFinished()), this, SLOT(validateChemical())); - // Connect slots for plot and save + // Connect slots for run, plot and save connect(m_uiForm.pbSave, SIGNAL(clicked()), this, SLOT(saveClicked())); connect(m_uiForm.pbPlot, SIGNAL(clicked()), this, SLOT(plotClicked())); + connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); // Connect slots for toggling the mass/number density unit connect(m_uiForm.cbSampleDensity, SIGNAL(currentIndexChanged(int)), this, @@ -71,6 +72,8 @@ void CalculatePaalmanPings::setup() { doValidation(true); } void CalculatePaalmanPings::validateChemical() { doValidation(true); } void CalculatePaalmanPings::run() { + setRunIsRunning(true); + // Get correct corrections algorithm auto sampleShape = m_uiForm.cbSampleShape->currentText(); auto algorithmName = sampleShape.replace(" ", "") + "PaalmanPingsCorrection"; @@ -303,49 +306,48 @@ void CalculatePaalmanPings::absCorComplete(bool error) { disconnect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this, SLOT(absCorComplete(bool))); - if (error) { - emit showMessageBox("Absorption correction calculation failed.\nSee " - "Results Log for more details."); - return; - } + if (!error) { + // Convert the spectrum axis of correction factors to Q + const auto sampleWsName = + m_uiForm.dsSample->getCurrentDataName().toStdString(); + MatrixWorkspace_sptr sampleWs = + AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>( + sampleWsName); + WorkspaceGroup_sptr corrections = + AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>( + m_pythonExportWsName); + for (size_t i = 0; i < corrections->size(); i++) { + MatrixWorkspace_sptr factorWs = + boost::dynamic_pointer_cast<MatrixWorkspace>(corrections->getItem(i)); + if (!factorWs || !sampleWs) + continue; + + if (getEMode(sampleWs) == "Indirect") { + API::BatchAlgorithmRunner::AlgorithmRuntimeProps convertSpecProps; + IAlgorithm_sptr convertSpecAlgo = + AlgorithmManager::Instance().create("ConvertSpectrumAxis"); + convertSpecAlgo->initialize(); + convertSpecAlgo->setProperty("InputWorkspace", factorWs); + convertSpecAlgo->setProperty("OutputWorkspace", factorWs->getName()); + convertSpecAlgo->setProperty("Target", "ElasticQ"); + convertSpecAlgo->setProperty("EMode", "Indirect"); + + try { + convertSpecAlgo->setProperty("EFixed", getEFixed(factorWs)); + } catch (std::runtime_error &) { + } - // Convert the spectrum axis of correction factors to Q - const auto sampleWsName = - m_uiForm.dsSample->getCurrentDataName().toStdString(); - MatrixWorkspace_sptr sampleWs = - AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(sampleWsName); - WorkspaceGroup_sptr corrections = - AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>( - m_pythonExportWsName); - for (size_t i = 0; i < corrections->size(); i++) { - MatrixWorkspace_sptr factorWs = - boost::dynamic_pointer_cast<MatrixWorkspace>(corrections->getItem(i)); - if (!factorWs || !sampleWs) - continue; - - if (getEMode(sampleWs) == "Indirect") { - API::BatchAlgorithmRunner::AlgorithmRuntimeProps convertSpecProps; - IAlgorithm_sptr convertSpecAlgo = - AlgorithmManager::Instance().create("ConvertSpectrumAxis"); - convertSpecAlgo->initialize(); - convertSpecAlgo->setProperty("InputWorkspace", factorWs); - convertSpecAlgo->setProperty("OutputWorkspace", factorWs->getName()); - convertSpecAlgo->setProperty("Target", "ElasticQ"); - convertSpecAlgo->setProperty("EMode", "Indirect"); - - try { - convertSpecAlgo->setProperty("EFixed", getEFixed(factorWs)); - } catch (std::runtime_error &) { + m_batchAlgoRunner->addAlgorithm(convertSpecAlgo); } - - m_batchAlgoRunner->addAlgorithm(convertSpecAlgo); } - } - // Run algorithm queue - connect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this, - SLOT(postProcessComplete(bool))); - m_batchAlgoRunner->executeBatchAsync(); + // Run algorithm queue + connect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this, + SLOT(postProcessComplete(bool))); + m_batchAlgoRunner->executeBatchAsync(); + } else + emit showMessageBox("Absorption correction calculation failed.\nSee " + "Results Log for more details."); } /** @@ -356,17 +358,13 @@ void CalculatePaalmanPings::absCorComplete(bool error) { void CalculatePaalmanPings::postProcessComplete(bool error) { disconnect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this, SLOT(postProcessComplete(bool))); - + setRunIsRunning(false); if (error) { + setPlotResultEnabled(false); + setSaveResultEnabled(false); emit showMessageBox("Correction factor post processing failed.\nSee " "Results Log for more details."); - return; } - - // Enable post processing plot and save - m_uiForm.cbPlotOutput->setEnabled(true); - m_uiForm.pbPlot->setEnabled(true); - m_uiForm.pbSave->setEnabled(true); } void CalculatePaalmanPings::loadSettings(const QSettings &settings) { @@ -544,9 +542,6 @@ void CalculatePaalmanPings::addShapeSpecificCanOptions(IAlgorithm_sptr alg, } } -/** - * Handles saving of workspace - */ void CalculatePaalmanPings::saveClicked() { if (checkADSForPlotSaveWorkspace(m_pythonExportWsName, false)) @@ -554,13 +549,10 @@ void CalculatePaalmanPings::saveClicked() { m_batchAlgoRunner->executeBatchAsync(); } -/** - * Handles mantid plotting of workspace - */ void CalculatePaalmanPings::plotClicked() { + setPlotResultIsPlotting(true); QString plotType = m_uiForm.cbPlotOutput->currentText(); - if (checkADSForPlotSaveWorkspace(m_pythonExportWsName, true)) { if (plotType == "Both" || plotType == "Wavelength") @@ -569,8 +561,11 @@ void CalculatePaalmanPings::plotClicked() { if (plotType == "Both" || plotType == "Angle") plotTimeBin(QString::fromStdString(m_pythonExportWsName)); } + setPlotResultIsPlotting(false); } +void CalculatePaalmanPings::runClicked() { runTab(); } + /** * Handle changing of the sample density unit */ @@ -595,5 +590,34 @@ void CalculatePaalmanPings::changeCanDensityUnit(int index) { } } +void CalculatePaalmanPings::setRunEnabled(bool enabled) { + m_uiForm.pbRun->setEnabled(enabled); +} + +void CalculatePaalmanPings::setPlotResultEnabled(bool enabled) { + m_uiForm.pbPlot->setEnabled(enabled); + m_uiForm.cbPlotOutput->setEnabled(enabled); +} + +void CalculatePaalmanPings::setSaveResultEnabled(bool enabled) { + m_uiForm.pbSave->setEnabled(enabled); +} + +void CalculatePaalmanPings::setButtonsEnabled(bool enabled) { + setRunEnabled(enabled); + setPlotResultEnabled(enabled); + setSaveResultEnabled(enabled); +} + +void CalculatePaalmanPings::setRunIsRunning(bool running) { + m_uiForm.pbRun->setText(running ? "Running..." : "Run"); + setButtonsEnabled(!running); +} + +void CalculatePaalmanPings::setPlotResultIsPlotting(bool plotting) { + m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot"); + setButtonsEnabled(!plotting); +} + } // namespace CustomInterfaces } // namespace MantidQt diff --git a/qt/scientific_interfaces/Indirect/CalculatePaalmanPings.h b/qt/scientific_interfaces/Indirect/CalculatePaalmanPings.h index 61a6584b45baf8f600da4062ae347057adc7e1c6..e2b1874db48a4c4e9d0c9c467848eb3f42169ece 100644 --- a/qt/scientific_interfaces/Indirect/CalculatePaalmanPings.h +++ b/qt/scientific_interfaces/Indirect/CalculatePaalmanPings.h @@ -20,14 +20,6 @@ class DLLExport CalculatePaalmanPings : public CorrectionsTab { public: CalculatePaalmanPings(QWidget *parent = nullptr); -private: - void setup() override; - void run() override; - bool validate() override; - void loadSettings(const QSettings &settings) override; - - bool doValidation(bool silent = false); - private slots: void absCorComplete(bool error); void postProcessComplete(bool error); @@ -36,14 +28,30 @@ private slots: void validateChemical(); void saveClicked(); void plotClicked(); + void runClicked(); void changeSampleDensityUnit(int); void changeCanDensityUnit(int); private: + void setup() override; + void run() override; + bool validate() override; + void loadSettings(const QSettings &settings) override; + + bool doValidation(bool silent = false); + void addShapeSpecificSampleOptions(Mantid::API::IAlgorithm_sptr alg, QString shape); void addShapeSpecificCanOptions(Mantid::API::IAlgorithm_sptr alg, QString shape); + + void setRunEnabled(bool enabled); + void setPlotResultEnabled(bool enabled); + void setSaveResultEnabled(bool enabled); + void setButtonsEnabled(bool enabled); + void setRunIsRunning(bool running); + void setPlotResultIsPlotting(bool plotting); + boost::optional<double> getInstrumentParameter(Mantid::Geometry::Instrument_const_sptr instrument, const std::string ¶meterName); diff --git a/qt/scientific_interfaces/Indirect/CalculatePaalmanPings.ui b/qt/scientific_interfaces/Indirect/CalculatePaalmanPings.ui index ad4111343b534a5b2c488c1d34c65a17aacba79f..44e3eb962995fd7533d2ed4e2984f5cf60573a2f 100644 --- a/qt/scientific_interfaces/Indirect/CalculatePaalmanPings.ui +++ b/qt/scientific_interfaces/Indirect/CalculatePaalmanPings.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>661</width> - <height>575</height> + <height>662</height> </rect> </property> <property name="windowTitle"> @@ -16,12 +16,6 @@ <layout class="QVBoxLayout" name="layoutAbsF2P"> <item> <widget class="QGroupBox" name="gbInput"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> <property name="title"> <string>Input</string> </property> @@ -110,11 +104,11 @@ </item> <item> <widget class="QGroupBox" name="corrDetails"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + <property name="minimumSize"> + <size> + <width>0</width> + <height>93</height> + </size> </property> <property name="toolTip"> <string>Correction computation details. By default will be set automatically from the instrument parameters of the input workspace. Modify to override.</string> @@ -235,12 +229,6 @@ </item> <item> <widget class="QGroupBox" name="gbShapeDetails"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> <property name="title"> <string>Shape Details</string> </property> @@ -282,33 +270,58 @@ </property> <widget class="QWidget" name="pgFlatPlate"> <layout class="QGridLayout" name="gridLayout_2"> - <property name="margin"> + <property name="leftMargin"> <number>0</number> </property> - <item row="0" column="2"> - <widget class="QLabel" name="lbFlatSampleAngle"> - <property name="text"> - <string>Sample Angle:</string> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="3" column="1"> + <widget class="QDoubleSpinBox" name="spFlatCanFrontThickness"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="suffix"> + <string> cm</string> + </property> + <property name="decimals"> + <number>3</number> + </property> + <property name="singleStep"> + <double>0.100000000000000</double> </property> </widget> </item> - <item row="3" column="0"> - <widget class="QLabel" name="lbFlatCanFrontThickness"> + <item row="3" column="2"> + <widget class="QLabel" name="lbFlatCanBackThickness"> <property name="enabled"> <bool>false</bool> </property> <property name="text"> - <string>Container Front Thickness:</string> + <string>Container Back Thickness:</string> </property> </widget> </item> - <item row="3" column="2"> - <widget class="QLabel" name="lbFlatCanBackThickness"> + <item row="0" column="0"> + <widget class="QLabel" name="lbFlatSampleThickness"> + <property name="text"> + <string>Sample Thickness:</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="lbFlatCanFrontThickness"> <property name="enabled"> <bool>false</bool> </property> <property name="text"> - <string>Container Back Thickness:</string> + <string>Container Front Thickness:</string> </property> </widget> </item> @@ -328,18 +341,8 @@ </property> </widget> </item> - <item row="0" column="0"> - <widget class="QLabel" name="lbFlatSampleThickness"> - <property name="text"> - <string>Sample Thickness:</string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QDoubleSpinBox" name="spFlatCanFrontThickness"> - <property name="enabled"> - <bool>false</bool> - </property> + <item row="0" column="1"> + <widget class="QDoubleSpinBox" name="spFlatSampleThickness"> <property name="suffix"> <string> cm</string> </property> @@ -358,16 +361,10 @@ </property> </widget> </item> - <item row="0" column="1"> - <widget class="QDoubleSpinBox" name="spFlatSampleThickness"> - <property name="suffix"> - <string> cm</string> - </property> - <property name="decimals"> - <number>3</number> - </property> - <property name="singleStep"> - <double>0.100000000000000</double> + <item row="0" column="2"> + <widget class="QLabel" name="lbFlatSampleAngle"> + <property name="text"> + <string>Sample Angle:</string> </property> </widget> </item> @@ -375,7 +372,16 @@ </widget> <widget class="QWidget" name="pgCylinder"> <layout class="QGridLayout" name="gridLayout_4"> - <property name="margin"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> <number>0</number> </property> <item row="5" column="3"> @@ -494,7 +500,16 @@ </widget> <widget class="QWidget" name="pgAnnulus"> <layout class="QGridLayout" name="gridLayout_5"> - <property name="margin"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> <number>0</number> </property> <item row="5" column="3"> @@ -641,11 +656,11 @@ </item> <item> <widget class="QGroupBox" name="gbSampleDetails"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + <property name="minimumSize"> + <size> + <width>0</width> + <height>93</height> + </size> </property> <property name="title"> <string>Sample Details</string> @@ -719,11 +734,11 @@ <property name="enabled"> <bool>false</bool> </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + <property name="minimumSize"> + <size> + <width>0</width> + <height>93</height> + </size> </property> <property name="title"> <string>Can Details</string> @@ -790,13 +805,67 @@ </widget> </item> <item> - <widget class="QGroupBox" name="gbOutput"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + <widget class="QGroupBox" name="gbRun"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>45</height> + </size> </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>45</height> + </size> + </property> + <property name="title"> + <string>Run</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>7</number> + </property> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>265</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbRun"> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>265</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="gbOutput"> <property name="title"> <string>Output Options</string> </property> diff --git a/qt/scientific_interfaces/Indirect/ContainerSubtraction.cpp b/qt/scientific_interfaces/Indirect/ContainerSubtraction.cpp index a2d3196c5d787637404058bdeb4c55fbd4424ea4..f286267fa6292505f7df4ed220f678492a965eab 100644 --- a/qt/scientific_interfaces/Indirect/ContainerSubtraction.cpp +++ b/qt/scientific_interfaces/Indirect/ContainerSubtraction.cpp @@ -23,7 +23,6 @@ ContainerSubtraction::ContainerSubtraction(QWidget *parent) : CorrectionsTab(parent), m_spectra(0) { m_uiForm.setupUi(parent); - // Connect slots connect(m_uiForm.dsSample, SIGNAL(dataReady(const QString &)), this, SLOT(newSample(const QString &))); connect(m_uiForm.dsContainer, SIGNAL(dataReady(const QString &)), this, @@ -36,6 +35,7 @@ ContainerSubtraction::ContainerSubtraction(QWidget *parent) SLOT(updateCan())); connect(m_uiForm.pbSave, SIGNAL(clicked()), this, SLOT(saveClicked())); connect(m_uiForm.pbPlot, SIGNAL(clicked()), this, SLOT(plotClicked())); + connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); connect(m_uiForm.pbPlotPreview, SIGNAL(clicked()), this, SLOT(plotCurrentPreview())); @@ -64,39 +64,42 @@ void ContainerSubtraction::setTransformedContainer( void ContainerSubtraction::setup() {} void ContainerSubtraction::run() { - if (!m_csSampleWS) - return; - if (!m_csContainerWS) - return; - - m_originalSampleUnits = m_csSampleWS->getAxis(0)->unit()->unitID(); - - // Check if using shift / scale - const bool shift = m_uiForm.ckShiftCan->isChecked(); - const bool scale = m_uiForm.ckScaleCan->isChecked(); - - auto containerWs = m_csContainerWS; - if (shift) { - containerWs = shiftWorkspace(containerWs, m_uiForm.spShift->value()); - containerWs = rebinToWorkspace(containerWs, m_csSampleWS); - } else if (!checkWorkspaceBinningMatches(m_csSampleWS, containerWs)) { - containerWs = requestRebinToSample(containerWs); - - if (!checkWorkspaceBinningMatches(m_csSampleWS, containerWs)) { - g_log.error("Cannot apply container corrections using a sample and " - "container with different binning."); - return; + setRunIsRunning(true); + + if (m_csSampleWS && m_csContainerWS) { + m_originalSampleUnits = m_csSampleWS->getAxis(0)->unit()->unitID(); + + // Check if using shift / scale + const bool shift = m_uiForm.ckShiftCan->isChecked(); + const bool scale = m_uiForm.ckScaleCan->isChecked(); + + auto containerWs = m_csContainerWS; + if (shift) { + containerWs = shiftWorkspace(containerWs, m_uiForm.spShift->value()); + containerWs = rebinToWorkspace(containerWs, m_csSampleWS); + } else if (!checkWorkspaceBinningMatches(m_csSampleWS, containerWs)) { + containerWs = requestRebinToSample(containerWs); + + if (!checkWorkspaceBinningMatches(m_csSampleWS, containerWs)) { + setRunIsRunning(false); + setPlotResultEnabled(false); + setSaveResultEnabled(false); + g_log.error("Cannot apply container corrections using a sample and " + "container with different binning."); + return; + } } - } - if (scale) - containerWs = scaleWorkspace(containerWs, m_uiForm.spCanScale->value()); + if (scale) + containerWs = scaleWorkspace(containerWs, m_uiForm.spCanScale->value()); - m_csSubtractedWS = minusWorkspace(m_csSampleWS, containerWs); - m_pythonExportWsName = createOutputName(); - AnalysisDataService::Instance().addOrReplace(m_pythonExportWsName, - m_csSubtractedWS); - containerSubtractionComplete(); + m_csSubtractedWS = minusWorkspace(m_csSampleWS, containerWs); + m_pythonExportWsName = createOutputName(); + AnalysisDataService::Instance().addOrReplace(m_pythonExportWsName, + m_csSubtractedWS); + containerSubtractionComplete(); + } + setRunIsRunning(false); } std::string ContainerSubtraction::createOutputName() { @@ -320,16 +323,8 @@ void ContainerSubtraction::containerSubtractionComplete() { "Number", logText); m_batchAlgoRunner->addAlgorithm(shiftLog); } - - // Enable post process plotting and saving - m_uiForm.cbPlotOutput->setEnabled(true); - m_uiForm.pbPlot->setEnabled(true); - m_uiForm.pbSave->setEnabled(true); } -/** - * Handles saving of workspace - */ void ContainerSubtraction::saveClicked() { // Check workspace exists @@ -338,12 +333,10 @@ void ContainerSubtraction::saveClicked() { m_batchAlgoRunner->executeBatchAsync(); } -/** - * Handles Mantid plotting of workspace - */ void ContainerSubtraction::plotClicked() { - QString plotType = m_uiForm.cbPlotOutput->currentText(); + setPlotResultIsPlotting(true); + QString plotType = m_uiForm.cbPlotOutput->currentText(); if (checkADSForPlotSaveWorkspace(m_pythonExportWsName, true)) { if (plotType == "Spectra" || plotType == "Both") @@ -352,8 +345,11 @@ void ContainerSubtraction::plotClicked() { if (plotType == "Contour" || plotType == "Both") plot2D(QString::fromStdString(m_pythonExportWsName)); } + setPlotResultIsPlotting(false); } +void ContainerSubtraction::runClicked() { runTab(); } + /** * Plots the current spectrum displayed in the preview plot */ @@ -548,5 +544,34 @@ IAlgorithm_sptr ContainerSubtraction::addSampleLogAlgorithm( return shiftLog; } +void ContainerSubtraction::setRunEnabled(bool enabled) { + m_uiForm.pbRun->setEnabled(enabled); +} + +void ContainerSubtraction::setPlotResultEnabled(bool enabled) { + m_uiForm.pbPlot->setEnabled(enabled); + m_uiForm.cbPlotOutput->setEnabled(enabled); +} + +void ContainerSubtraction::setSaveResultEnabled(bool enabled) { + m_uiForm.pbSave->setEnabled(enabled); +} + +void ContainerSubtraction::setButtonsEnabled(bool enabled) { + setRunEnabled(enabled); + setPlotResultEnabled(enabled); + setSaveResultEnabled(enabled); +} + +void ContainerSubtraction::setRunIsRunning(bool running) { + m_uiForm.pbRun->setText(running ? "Running..." : "Run"); + setButtonsEnabled(!running); +} + +void ContainerSubtraction::setPlotResultIsPlotting(bool plotting) { + m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot"); + setButtonsEnabled(!plotting); +} + } // namespace CustomInterfaces } // namespace MantidQt diff --git a/qt/scientific_interfaces/Indirect/ContainerSubtraction.h b/qt/scientific_interfaces/Indirect/ContainerSubtraction.h index 6414f6a008dae9ab47cde3aa06850625f8e317da..45023799de1d67a620a1c306f38a9edcb17004a3 100644 --- a/qt/scientific_interfaces/Indirect/ContainerSubtraction.h +++ b/qt/scientific_interfaces/Indirect/ContainerSubtraction.h @@ -34,13 +34,13 @@ private slots: void plotPreview(int wsIndex); /// Handle abs. correction algorithm completion void containerSubtractionComplete(); - /// Handles saving workspace - void saveClicked(); - /// Handles mantid plotting - void plotClicked(); /// Handles plotting the preview. void plotCurrentPreview(); + void saveClicked(); + void plotClicked(); + void runClicked(); + private: void setup() override; void run() override; @@ -90,6 +90,13 @@ private: const std::string &name, const std::string &type, const std::string &value) const; + void setRunEnabled(bool enabled); + void setPlotResultEnabled(bool enabled); + void setSaveResultEnabled(bool enabled); + void setButtonsEnabled(bool enabled); + void setRunIsRunning(bool running); + void setPlotResultIsPlotting(bool plotting); + Ui::ContainerSubtraction m_uiForm; std::string m_originalSampleUnits; diff --git a/qt/scientific_interfaces/Indirect/ContainerSubtraction.ui b/qt/scientific_interfaces/Indirect/ContainerSubtraction.ui index e4592b1d0f2b9a651eb6eab25d56b20488bcdace..ba7ceed68b4fd1a115d8a785a279b2437ef30e06 100644 --- a/qt/scientific_interfaces/Indirect/ContainerSubtraction.ui +++ b/qt/scientific_interfaces/Indirect/ContainerSubtraction.ui @@ -253,6 +253,60 @@ </layout> </widget> </item> + <item> + <widget class="QGroupBox" name="gbRun"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>45</height> + </size> + </property> + <property name="title"> + <string>Run</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>7</number> + </property> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>439</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbRun"> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>439</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> <item> <widget class="QGroupBox" name="gbOutput"> <property name="title"> diff --git a/qt/scientific_interfaces/Indirect/ConvFit.cpp b/qt/scientific_interfaces/Indirect/ConvFit.cpp index 229a551fc1f6c969a8180e112c6dfcef398c7307..b6d1bb739286777163450e3da41b3028fddb4918 100644 --- a/qt/scientific_interfaces/Indirect/ConvFit.cpp +++ b/qt/scientific_interfaces/Indirect/ConvFit.cpp @@ -195,17 +195,21 @@ void ConvFit::setSaveResultEnabled(bool enabled) { m_uiForm->pbSave->setEnabled(enabled); } +void ConvFit::setButtonsEnabled(bool enabled) { + setRunEnabled(enabled); + setPlotResultEnabled(enabled); + setSaveResultEnabled(enabled); + setFitSingleSpectrumEnabled(enabled); +} + void ConvFit::setRunIsRunning(bool running) { m_uiForm->pbRun->setText(running ? "Running..." : "Run"); - setRunEnabled(!running); - setPlotResultEnabled(!running); - setSaveResultEnabled(!running); - setFitSingleSpectrumEnabled(!running); + setButtonsEnabled(!running); } void ConvFit::setPlotResultIsPlotting(bool plotting) { m_uiForm->pbPlot->setText(plotting ? "Plotting..." : "Plot"); - setPlotResultEnabled(!plotting); + setButtonsEnabled(!plotting); } void ConvFit::runClicked() { runTab(); } diff --git a/qt/scientific_interfaces/Indirect/ConvFit.h b/qt/scientific_interfaces/Indirect/ConvFit.h index 4659b93a83a55ff3b14027da9ddf1df77f404d9f..b73f96366c31a7113bdd7128b4ac38e89009365a 100644 --- a/qt/scientific_interfaces/Indirect/ConvFit.h +++ b/qt/scientific_interfaces/Indirect/ConvFit.h @@ -46,7 +46,7 @@ private: void setRunEnabled(bool enabled); void setFitSingleSpectrumEnabled(bool enabled); - + void setButtonsEnabled(bool enabled); void setPlotResultIsPlotting(bool plotting); std::string fitTypeString() const; diff --git a/qt/scientific_interfaces/Indirect/ConvFit.ui b/qt/scientific_interfaces/Indirect/ConvFit.ui index c4313b319f25fd8353149c473b4591ec31215339..f71838fc948e8417caa088021b03fa9b4b1eb9da 100644 --- a/qt/scientific_interfaces/Indirect/ConvFit.ui +++ b/qt/scientific_interfaces/Indirect/ConvFit.ui @@ -49,12 +49,6 @@ </item> <item> <widget class="QFrame" name="frame"> - <property name="frameShape"> - <enum>QFrame::StyledPanel</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> - </property> <layout class="QVBoxLayout" name="verticalLayout_3"> <property name="spacing"> <number>0</number> diff --git a/qt/scientific_interfaces/Indirect/Elwin.cpp b/qt/scientific_interfaces/Indirect/Elwin.cpp index 7c8c6c064dd2b274d4c01b985d4a2a7ecb892cdc..34d8fd5b0c3ab91fd405ac7e59494fba830cab39 100644 --- a/qt/scientific_interfaces/Indirect/Elwin.cpp +++ b/qt/scientific_interfaces/Indirect/Elwin.cpp @@ -256,8 +256,9 @@ void Elwin::unGroupInput(bool error) { ungroupAlg->setProperty("InputWorkspace", "IDA_Elwin_Input"); ungroupAlg->execute(); } - setPlotResultEnabled(true); - setSaveResultEnabled(true); + } else { + setPlotResultEnabled(false); + setSaveResultEnabled(false); } } @@ -529,16 +530,20 @@ void Elwin::setSaveResultEnabled(bool enabled) { m_uiForm.pbSave->setEnabled(enabled); } +void Elwin::setButtonsEnabled(bool enabled) { + setRunEnabled(enabled); + setPlotResultEnabled(enabled); + setSaveResultEnabled(enabled); +} + void Elwin::setRunIsRunning(bool running) { m_uiForm.pbRun->setText(running ? "Running..." : "Run"); - setRunEnabled(!running); - setPlotResultEnabled(!running); - setSaveResultEnabled(!running); + setButtonsEnabled(!running); } void Elwin::setPlotResultIsPlotting(bool plotting) { m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot Result"); - setPlotResultEnabled(!plotting); + setButtonsEnabled(!plotting); } void Elwin::runClicked() { runTab(); } diff --git a/qt/scientific_interfaces/Indirect/Elwin.h b/qt/scientific_interfaces/Indirect/Elwin.h index 8fc9430e2dae840e8004cf7f2477d15aebd1e8df..73249fadae0612c11d36a8e87d077c823923fb5e 100644 --- a/qt/scientific_interfaces/Indirect/Elwin.h +++ b/qt/scientific_interfaces/Indirect/Elwin.h @@ -20,6 +20,19 @@ class DLLExport Elwin : public IndirectDataAnalysisTab { public: Elwin(QWidget *parent = nullptr); +private slots: + void newInputFiles(); + void newPreviewFileSelected(int index); + void plotInput(); + void twoRanges(QtProperty *prop, bool); + void minChanged(double val); + void maxChanged(double val); + void updateRS(QtProperty *prop, double val); + void unGroupInput(bool error); + void runClicked(); + void saveClicked(); + void plotClicked(); + private: void run() override; void setup() override; @@ -32,24 +45,10 @@ private: void setRunEnabled(bool enabled); void setPlotResultEnabled(bool enabled); void setSaveResultEnabled(bool enabled); - + void setButtonsEnabled(bool enabled); void setRunIsRunning(bool running); void setPlotResultIsPlotting(bool plotting); -private slots: - void newInputFiles(); - void newPreviewFileSelected(int index); - void plotInput(); - void twoRanges(QtProperty *prop, bool); - void minChanged(double val); - void maxChanged(double val); - void updateRS(QtProperty *prop, double val); - void unGroupInput(bool error); - void runClicked(); - void saveClicked(); - void plotClicked(); - -private: Ui::Elwin m_uiForm; QtTreePropertyBrowser *m_elwTree; }; diff --git a/qt/scientific_interfaces/Indirect/Elwin.ui b/qt/scientific_interfaces/Indirect/Elwin.ui index 622327b9b724682b52372f9c4cf32f8cd7571499..9a006e4f6f6588c8dda39978be6957c52429aca9 100644 --- a/qt/scientific_interfaces/Indirect/Elwin.ui +++ b/qt/scientific_interfaces/Indirect/Elwin.ui @@ -198,12 +198,6 @@ </item> <item> <widget class="QFrame" name="fResults"> - <property name="frameShape"> - <enum>QFrame::StyledPanel</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> - </property> <layout class="QVBoxLayout" name="verticalLayout"> <property name="spacing"> <number>0</number> diff --git a/qt/scientific_interfaces/Indirect/ILLEnergyTransfer.ui b/qt/scientific_interfaces/Indirect/ILLEnergyTransfer.ui index b47a07a31c7269dd6ddd66a51a882338f1d47187..42cc0c511bf0783dd5f05ced8f55cf3ed6a70bab 100644 --- a/qt/scientific_interfaces/Indirect/ILLEnergyTransfer.ui +++ b/qt/scientific_interfaces/Indirect/ILLEnergyTransfer.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>670</width> - <height>621</height> + <width>672</width> + <height>585</height> </rect> </property> <property name="sizePolicy"> diff --git a/qt/scientific_interfaces/Indirect/ISISCalibration.cpp b/qt/scientific_interfaces/Indirect/ISISCalibration.cpp index 75d8f5d3229ce1e60cab93cfa58cc663f5969651..812ce7be79ea08ae2dacc5da474848a9dcc4223f 100644 --- a/qt/scientific_interfaces/Indirect/ISISCalibration.cpp +++ b/qt/scientific_interfaces/Indirect/ISISCalibration.cpp @@ -173,9 +173,17 @@ ISISCalibration::ISISCalibration(IndirectDataReduction *idrUI, QWidget *parent) connect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this, SLOT(algorithmComplete(bool))); - // Handle plotting and saving + // Handle running, plotting and saving + connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); connect(m_uiForm.pbSave, SIGNAL(clicked()), this, SLOT(saveClicked())); connect(m_uiForm.pbPlot, SIGNAL(clicked()), this, SLOT(plotClicked())); + + connect(this, + SIGNAL(updateRunButton(bool, std::string const &, QString const &, + QString const &)), + this, + SLOT(updateRunButton(bool, std::string const &, QString const &, + QString const &))); } //---------------------------------------------------------------------------------------------- @@ -618,17 +626,16 @@ void ISISCalibration::resCheck(bool state) { * Called when a user starts to type / edit the runs to load. */ void ISISCalibration::pbRunEditing() { - emit updateRunButton(false, "Editing...", - "Run numbers are currently being edited."); + updateRunButton(false, "unchanged", "Editing...", + "Run numbers are currently being edited."); } /** * Called when the FileFinder starts finding the files. */ void ISISCalibration::pbRunFinding() { - emit updateRunButton( - false, "Finding files...", - "Searching for data files for the run numbers entered..."); + updateRunButton(false, "unchanged", "Finding files...", + "Searching for data files for the run numbers entered..."); m_uiForm.leRunNo->setEnabled(false); } @@ -636,13 +643,12 @@ void ISISCalibration::pbRunFinding() { * Called when the FileFinder has finished finding the files. */ void ISISCalibration::pbRunFinished() { - if (!m_uiForm.leRunNo->isValid()) { - emit updateRunButton( - false, "Invalid Run(s)", + if (!m_uiForm.leRunNo->isValid()) + updateRunButton( + false, "unchanged", "Invalid Run(s)", "Cannot find data files for some of the run numbers entered."); - } else { - emit updateRunButton(); - } + else + updateRunButton(); m_uiForm.leRunNo->setEnabled(true); } @@ -660,10 +666,16 @@ void ISISCalibration::saveClicked() { m_batchAlgoRunner->executeBatchAsync(); } +/** + * Handle when Run is clicked + */ +void ISISCalibration::runClicked() { runTab(); } + /** * Handle mantid plotting */ void ISISCalibration::plotClicked() { + setPlotIsPlotting(true); plotTimeBin(m_outputCalibrationName); checkADSForPlotSaveWorkspace(m_outputCalibrationName.toStdString(), true); @@ -676,6 +688,7 @@ void ISISCalibration::plotClicked() { plotWorkspaces.append(m_outputResolutionName + "_pre_smooth"); } plotSpectrum(plotWorkspaces); + setPlotIsPlotting(false); } void ISISCalibration::addRuntimeSmoothing(const QString &workspaceName) { @@ -767,5 +780,42 @@ IAlgorithm_sptr ISISCalibration::energyTransferReductionAlgorithm( return reductionAlg; } +void ISISCalibration::setRunEnabled(bool enabled) { + m_uiForm.pbRun->setEnabled(enabled); +} + +void ISISCalibration::setPlotEnabled(bool enabled) { + m_uiForm.pbPlot->setEnabled(enabled); +} + +void ISISCalibration::setSaveEnabled(bool enabled) { + m_uiForm.pbSave->setEnabled(enabled); +} + +void ISISCalibration::setOutputButtonsEnabled( + std::string const &enableOutputButtons) { + bool enable = enableOutputButtons == "enable" ? true : false; + setPlotEnabled(enable); + setSaveEnabled(enable); +} + +void ISISCalibration::updateRunButton(bool enabled, + std::string const &enableOutputButtons, + QString const message, + QString const tooltip) { + setRunEnabled(enabled); + m_uiForm.pbRun->setText(message); + m_uiForm.pbRun->setToolTip(tooltip); + if (enableOutputButtons != "unchanged") + setOutputButtonsEnabled(enableOutputButtons); +} + +void ISISCalibration::setPlotIsPlotting(bool plotting) { + m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot Result"); + setPlotEnabled(!plotting); + setRunEnabled(!plotting); + setSaveEnabled(!plotting); +} + } // namespace CustomInterfaces } // namespace MantidQt diff --git a/qt/scientific_interfaces/Indirect/ISISCalibration.h b/qt/scientific_interfaces/Indirect/ISISCalibration.h index 649d13d0cf9fbda0a47d8bf8b0afe5251bee1269..198cdaffa946fab1d01dfe64eff13cc8f43602c0 100644 --- a/qt/scientific_interfaces/Indirect/ISISCalibration.h +++ b/qt/scientific_interfaces/Indirect/ISISCalibration.h @@ -66,10 +66,21 @@ private slots: void pbRunFinding(); //< Called when the FileFinder starts finding the files. void pbRunFinished(); //< Called when the FileFinder has finished finding the // files. - /// Handles saving and plotting + /// Handles running, saving and plotting + void runClicked(); void saveClicked(); void plotClicked(); + void setRunEnabled(bool enabled); + void setPlotEnabled(bool enabled); + void setSaveEnabled(bool enabled); + void setOutputButtonsEnabled(std::string const &enableOutputButtons); + void updateRunButton(bool enabled = true, + std::string const &enableOutputButtons = "unchanged", + QString const message = "Run", + QString const tooltip = ""); + void setPlotIsPlotting(bool plotting); + private: void createRESfile(const QString &file); void addRuntimeSmoothing(const QString &workspaceName); diff --git a/qt/scientific_interfaces/Indirect/ISISCalibration.ui b/qt/scientific_interfaces/Indirect/ISISCalibration.ui index 450f9bf90d2cf6b11cc860c87015cd6d78260f80..31d197a5daf61dfa35d74b3e0a348ef27d7d7b67 100644 --- a/qt/scientific_interfaces/Indirect/ISISCalibration.ui +++ b/qt/scientific_interfaces/Indirect/ISISCalibration.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>500</width> - <height>339</height> + <height>367</height> </rect> </property> <property name="minimumSize"> @@ -233,6 +233,66 @@ </layout> </widget> </item> + <item> + <widget class="QGroupBox" name="gbRun"> + <property name="title"> + <string>Run</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>7</number> + </property> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>185</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbRun"> + <property name="minimumSize"> + <size> + <width>150</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>180</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>184</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> <item> <widget class="QGroupBox" name="gbOutput"> <property name="title"> @@ -282,15 +342,16 @@ </widget> <customwidgets> <customwidget> - <class>MantidQt::MantidWidgets::PreviewPlot</class> + <class>MantidQt::API::MWRunFiles</class> <extends>QWidget</extends> - <header>MantidQtWidgets/LegacyQwt/PreviewPlot.h</header> + <header>MantidQtWidgets/Common/MWRunFiles.h</header> <container>1</container> </customwidget> <customwidget> - <class>MantidQt::API::MWRunFiles</class> + <class>MantidQt::MantidWidgets::PreviewPlot</class> <extends>QWidget</extends> - <header>MantidQtWidgets/Common/MWRunFiles.h</header> + <header>MantidQtWidgets/LegacyQwt/PreviewPlot.h</header> + <container>1</container> </customwidget> </customwidgets> <resources/> diff --git a/qt/scientific_interfaces/Indirect/ISISDiagnostics.cpp b/qt/scientific_interfaces/Indirect/ISISDiagnostics.cpp index a1b6afff80777248cbbf5d7392a37cf51de636b0..8a78cf86651ccf408788f6a44718fc51df5efdbe 100644 --- a/qt/scientific_interfaces/Indirect/ISISDiagnostics.cpp +++ b/qt/scientific_interfaces/Indirect/ISISDiagnostics.cpp @@ -124,10 +124,18 @@ ISISDiagnostics::ISISDiagnostics(IndirectDataReduction *idrUI, QWidget *parent) // Reverts run button back to normal when file finding has finished connect(m_uiForm.dsInputFiles, SIGNAL(fileFindingFinished()), this, SLOT(pbRunFinished())); - // Handles plotting and saving + // Handles running, plotting and saving + connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); connect(m_uiForm.pbPlot, SIGNAL(clicked()), this, SLOT(plotClicked())); connect(m_uiForm.pbSave, SIGNAL(clicked()), this, SLOT(saveClicked())); + connect(this, + SIGNAL(updateRunButton(bool, std::string const &, QString const &, + QString const &)), + this, + SLOT(updateRunButton(bool, std::string const &, QString const &, + QString const &))); + // Set default UI state sliceTwoRanges(nullptr, false); m_uiForm.ckUseCalibration->setChecked(false); @@ -445,17 +453,16 @@ void ISISDiagnostics::sliceAlgDone(bool error) { * Called when a user starts to type / edit the runs to load. */ void ISISDiagnostics::pbRunEditing() { - emit updateRunButton(false, "Editing...", - "Run numbers are curently being edited."); + updateRunButton(false, "unchanged", "Editing...", + "Run numbers are curently being edited."); } /** * Called when the FileFinder starts finding the files. */ void ISISDiagnostics::pbRunFinding() { - emit updateRunButton( - false, "Finding files...", - "Searchig for data files for the run numbers entered..."); + updateRunButton(false, "unchanged", "Finding files...", + "Searchig for data files for the run numbers entered..."); m_uiForm.dsInputFiles->setEnabled(false); } @@ -463,23 +470,29 @@ void ISISDiagnostics::pbRunFinding() { * Called when the FileFinder has finished finding the files. */ void ISISDiagnostics::pbRunFinished() { - if (!m_uiForm.dsInputFiles->isValid()) { - emit updateRunButton( - false, "Invalid Run(s)", + if (!m_uiForm.dsInputFiles->isValid()) + updateRunButton( + false, "unchanged", "Invalid Run(s)", "Cannot find data files for some of the run numbers enetered."); - } else { - emit updateRunButton(); - } + else + updateRunButton(); m_uiForm.dsInputFiles->setEnabled(true); } +/** + * Handle when Run is clicked + */ +void ISISDiagnostics::runClicked() { runTab(); } + /** * Handles mantid plotting */ void ISISDiagnostics::plotClicked() { + setPlotIsPlotting(true); if (checkADSForPlotSaveWorkspace(m_pythonExportWsName, true)) plotSpectrum(QString::fromStdString(m_pythonExportWsName)); + setPlotIsPlotting(false); } /** @@ -490,5 +503,43 @@ void ISISDiagnostics::saveClicked() { addSaveWorkspaceToQueue(QString::fromStdString(m_pythonExportWsName)); m_batchAlgoRunner->executeBatchAsync(); } + +void ISISDiagnostics::setRunEnabled(bool enabled) { + m_uiForm.pbRun->setEnabled(enabled); +} + +void ISISDiagnostics::setPlotEnabled(bool enabled) { + m_uiForm.pbPlot->setEnabled(enabled); +} + +void ISISDiagnostics::setSaveEnabled(bool enabled) { + m_uiForm.pbSave->setEnabled(enabled); +} + +void ISISDiagnostics::setOutputButtonsEnabled( + std::string const &enableOutputButtons) { + bool enable = enableOutputButtons == "enable" ? true : false; + setPlotEnabled(enable); + setSaveEnabled(enable); +} + +void ISISDiagnostics::updateRunButton(bool enabled, + std::string const &enableOutputButtons, + QString const message, + QString const tooltip) { + setRunEnabled(enabled); + m_uiForm.pbRun->setText(message); + m_uiForm.pbRun->setToolTip(tooltip); + if (enableOutputButtons != "unchanged") + setOutputButtonsEnabled(enableOutputButtons); +} + +void ISISDiagnostics::setPlotIsPlotting(bool plotting) { + m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot Result"); + setPlotEnabled(!plotting); + setRunEnabled(!plotting); + setSaveEnabled(!plotting); +} + } // namespace CustomInterfaces } // namespace MantidQt diff --git a/qt/scientific_interfaces/Indirect/ISISDiagnostics.h b/qt/scientific_interfaces/Indirect/ISISDiagnostics.h index 4c8540cbb527ba64743616f8b1efedac1022fd20..57ee022be3946ba547988ee17741c16ceb665a53 100644 --- a/qt/scientific_interfaces/Indirect/ISISDiagnostics.h +++ b/qt/scientific_interfaces/Indirect/ISISDiagnostics.h @@ -63,9 +63,20 @@ private slots: void pbRunFinding(); //< Called when the FileFinder starts finding the files. void pbRunFinished(); //< Called when the FileFinder has finished finding the // files. + void runClicked(); void saveClicked(); void plotClicked(); + void setRunEnabled(bool enabled); + void setPlotEnabled(bool enabled); + void setSaveEnabled(bool enabled); + void setOutputButtonsEnabled(std::string const &enableOutputButtons); + void updateRunButton(bool enabled = true, + std::string const &enableOutputButtons = "unchanged", + QString const message = "Run", + QString const tooltip = ""); + void setPlotIsPlotting(bool plotting); + private: Ui::ISISDiagnostics m_uiForm; }; diff --git a/qt/scientific_interfaces/Indirect/ISISDiagnostics.ui b/qt/scientific_interfaces/Indirect/ISISDiagnostics.ui index c21a581e42f0b64f9d88d9cff6cc75585b430902..e3d7e441779ac23131c4108a1230541975d0a536 100644 --- a/qt/scientific_interfaces/Indirect/ISISDiagnostics.ui +++ b/qt/scientific_interfaces/Indirect/ISISDiagnostics.ui @@ -146,6 +146,72 @@ </layout> </widget> </item> + <item> + <widget class="QGroupBox" name="gbRun"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Run</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>7</number> + </property> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>197</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbRun"> + <property name="minimumSize"> + <size> + <width>150</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>180</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>196</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> <item> <widget class="QGroupBox" name="gbOutput"> <property name="title"> @@ -191,6 +257,12 @@ </layout> </widget> <customwidgets> + <customwidget> + <class>MantidQt::API::MWRunFiles</class> + <extends>QWidget</extends> + <header>MantidQtWidgets/Common/MWRunFiles.h</header> + <container>1</container> + </customwidget> <customwidget> <class>MantidQt::MantidWidgets::DataSelector</class> <extends>QWidget</extends> @@ -202,11 +274,6 @@ <header>MantidQtWidgets/LegacyQwt/PreviewPlot.h</header> <container>1</container> </customwidget> - <customwidget> - <class>MantidQt::API::MWRunFiles</class> - <extends>QWidget</extends> - <header>MantidQtWidgets/Common/MWRunFiles.h</header> - </customwidget> </customwidgets> <tabstops> <tabstop>ckUseCalibration</tabstop> diff --git a/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.cpp b/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.cpp index 1668ab5d498231d082aa33800e20b5461f46dcf7..40d86abe339064687757c71a9f5b6b876b925020 100644 --- a/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.cpp +++ b/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.cpp @@ -100,10 +100,18 @@ ISISEnergyTransfer::ISISEnergyTransfer(IndirectDataReduction *idrUI, // Reverts run button back to normal when file finding has finished connect(m_uiForm.dsRunFiles, SIGNAL(fileFindingFinished()), this, SLOT(pbRunFinished())); - // Handle plotting and saving + // Handle running, plotting and saving + connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); connect(m_uiForm.pbPlot, SIGNAL(clicked()), this, SLOT(plotClicked())); connect(m_uiForm.pbSave, SIGNAL(clicked()), this, SLOT(saveClicked())); + connect(this, + SIGNAL(updateRunButton(bool, std::string const &, QString const &, + QString const &)), + this, + SLOT(updateRunButton(bool, std::string const &, QString const &, + QString const &))); + // Update UI widgets to show default values mappingOptionSelected(m_uiForm.cbGroupingOptions->currentText()); @@ -786,17 +794,16 @@ void ISISEnergyTransfer::plotRawComplete(bool error) { * Called when a user starts to type / edit the runs to load. */ void ISISEnergyTransfer::pbRunEditing() { - emit updateRunButton(false, "Editing...", - "Run numbers are currently being edited."); + updateRunButton(false, "unchanged", "Editing...", + "Run numbers are currently being edited."); } /** * Called when the FileFinder starts finding the files. */ void ISISEnergyTransfer::pbRunFinding() { - emit updateRunButton( - false, "Finding files...", - "Searching for data files for the run numbers entered..."); + updateRunButton(false, "unchanged", "Finding files...", + "Searching for data files for the run numbers entered..."); m_uiForm.dsRunFiles->setEnabled(false); } @@ -804,20 +811,26 @@ void ISISEnergyTransfer::pbRunFinding() { * Called when the FileFinder has finished finding the files. */ void ISISEnergyTransfer::pbRunFinished() { - if (!m_uiForm.dsRunFiles->isValid()) { - emit updateRunButton( - false, "Invalid Run(s)", + if (!m_uiForm.dsRunFiles->isValid()) + updateRunButton( + false, "unchanged", "Invalid Run(s)", "Cannot find data files for some of the run numbers entered."); - } else { - emit updateRunButton(); - } + else + updateRunButton(); m_uiForm.dsRunFiles->setEnabled(true); } + +/** + * Handle when Run is clicked + */ +void ISISEnergyTransfer::runClicked() { runTab(); } + /** * Handle mantid plotting of workspaces */ void ISISEnergyTransfer::plotClicked() { + setPlotIsPlotting(true); for (const auto &it : m_outputWorkspaces) { if (checkADSForPlotSaveWorkspace(it, true)) { const auto plotType = m_uiForm.cbPlotType->currentText(); @@ -828,6 +841,7 @@ void ISISEnergyTransfer::plotClicked() { m_pythonRunner.runPythonCode(pyInput); } } + setPlotIsPlotting(false); } /** @@ -851,5 +865,44 @@ void ISISEnergyTransfer::saveClicked() { m_pythonRunner.runPythonCode(pyInput); } +void ISISEnergyTransfer::setRunEnabled(bool enabled) { + m_uiForm.pbRun->setEnabled(enabled); +} + +void ISISEnergyTransfer::setPlotEnabled(bool enabled) { + m_uiForm.pbPlot->setEnabled(enabled); + m_uiForm.cbPlotType->setEnabled(enabled); +} + +void ISISEnergyTransfer::setSaveEnabled(bool enabled) { + m_uiForm.pbSave->setEnabled(enabled); + m_uiForm.loSaveFormats->setEnabled(enabled); +} + +void ISISEnergyTransfer::setOutputButtonsEnabled( + std::string const &enableOutputButtons) { + bool enable = enableOutputButtons == "enable" ? true : false; + setPlotEnabled(enable); + setSaveEnabled(enable); +} + +void ISISEnergyTransfer::updateRunButton(bool enabled, + std::string const &enableOutputButtons, + QString const message, + QString const tooltip) { + setRunEnabled(enabled); + m_uiForm.pbRun->setText(message); + m_uiForm.pbRun->setToolTip(tooltip); + if (enableOutputButtons != "unchanged") + setOutputButtonsEnabled(enableOutputButtons); +} + +void ISISEnergyTransfer::setPlotIsPlotting(bool plotting) { + m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot"); + setPlotEnabled(!plotting); + setRunEnabled(!plotting); + setSaveEnabled(!plotting); +} + } // namespace CustomInterfaces } // namespace MantidQt diff --git a/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.h b/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.h index 6b72b85c2e3a6a17330c83103c9d497719797b3e..d7cf30723dfd372566f6c0761e09191d89dcf27b 100644 --- a/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.h +++ b/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.h @@ -54,10 +54,21 @@ private slots: // files. void plotRawComplete( bool error); //< Called when the Plot Raw algorithmm chain completes - /// Handles plotting and saving + /// Handles running, plotting and saving + void runClicked(); void plotClicked(); void saveClicked(); + void setRunEnabled(bool enabled); + void setPlotEnabled(bool enabled); + void setSaveEnabled(bool enabled); + void setOutputButtonsEnabled(std::string const &enableOutputButtons); + void updateRunButton(bool enabled = true, + std::string const &enableOutputButtons = "unchanged", + QString const message = "Run", + QString const tooltip = ""); + void setPlotIsPlotting(bool plotting); + private: Ui::ISISEnergyTransfer m_uiForm; diff --git a/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.ui b/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.ui index 8fea6d1d7f674e7f19714b8b8f18f00999340697..c707ab0f15f2e85353c243bd24dee669e6896358 100644 --- a/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.ui +++ b/qt/scientific_interfaces/Indirect/ISISEnergyTransfer.ui @@ -246,7 +246,7 @@ <number>0</number> </property> <property name="currentIndex"> - <number>2</number> + <number>1</number> </property> <widget class="QWidget" name="pgMapFile"> <layout class="QHBoxLayout" name="horizontalLayout_5"> @@ -844,6 +844,66 @@ </layout> </widget> </item> + <item> + <widget class="QGroupBox" name="gbRun"> + <property name="title"> + <string>Run</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>7</number> + </property> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbRun"> + <property name="minimumSize"> + <size> + <width>150</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>180</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_10"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> <item> <widget class="QGroupBox" name="gbOutput"> <property name="title"> diff --git a/qt/scientific_interfaces/Indirect/IndirectBayes.cpp b/qt/scientific_interfaces/Indirect/IndirectBayes.cpp index 48aee3726243814dca6ed6c84a1e254e9d1d00f5..248aff44ba52de9c6fc97cc8e40fdaae33c81b0f 100644 --- a/qt/scientific_interfaces/Indirect/IndirectBayes.cpp +++ b/qt/scientific_interfaces/Indirect/IndirectBayes.cpp @@ -49,7 +49,6 @@ IndirectBayes::IndirectBayes(QWidget *parent) // Connect statements for the buttons shared between all tabs on the Indirect // Bayes interface - connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); connect(m_uiForm.pbHelp, SIGNAL(clicked()), this, SLOT(helpClicked())); connect(m_uiForm.pbManageDirs, SIGNAL(clicked()), this, SLOT(manageUserDirectories())); @@ -101,21 +100,6 @@ void IndirectBayes::loadSettings() { settings.endGroup(); } -/** - * Slot to run the underlying algorithm code based on the currently selected - * tab. - * - * This method checks the tabs validate method is passing before calling - * the run method. - */ -void IndirectBayes::runClicked() { - int tabIndex = m_uiForm.indirectBayesTabs->currentIndex(); - - if (m_bayesTabs[tabIndex]->validateTab()) { - m_bayesTabs[tabIndex]->runTab(); - } -} - /** * Slot to open a new browser window and navigate to the help page * on the wiki for the currently selected tab. diff --git a/qt/scientific_interfaces/Indirect/IndirectBayes.h b/qt/scientific_interfaces/Indirect/IndirectBayes.h index 963d24906401070ad36659e4cb5d7922616f0057..12ecffcddfc51305e582c864c96032d21b7fbe28 100644 --- a/qt/scientific_interfaces/Indirect/IndirectBayes.h +++ b/qt/scientific_interfaces/Indirect/IndirectBayes.h @@ -48,8 +48,6 @@ public: // public constructor, destructor and functions private slots: // Run the appropriate action depending based on the selected tab - /// Slot for clicking on the run button - void runClicked(); /// Slot for clicking on the hlep button void helpClicked(); /// Slot for clicking on the manage directories button diff --git a/qt/scientific_interfaces/Indirect/IndirectBayes.ui b/qt/scientific_interfaces/Indirect/IndirectBayes.ui index a258af6592862e57f2f471a7ec5d87ef1ac82e44..e10a747d1f2e14f7c52c16ff3aba4373b8ccefc5 100644 --- a/qt/scientific_interfaces/Indirect/IndirectBayes.ui +++ b/qt/scientific_interfaces/Indirect/IndirectBayes.ui @@ -14,96 +14,76 @@ <string>Indirect Bayes</string> </property> <widget class="QWidget" name="centralwidget"> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QTabWidget" name="indirectBayesTabs"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="ResNorm"> - <attribute name="title"> - <string>ResNorm</string> - </attribute> - </widget> - <widget class="QWidget" name="Quasi"> - <attribute name="title"> - <string>Quasi</string> - </attribute> - </widget> - <widget class="QWidget" name="Stretch"> - <attribute name="title"> - <string>Stretch</string> - </attribute> - </widget> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="layout_bottom"> - <item> - <widget class="QPushButton" name="pbHelp"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="maximumSize"> - <size> - <width>20</width> - <height>20</height> - </size> - </property> - <property name="text"> - <string>?</string> - </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTabWidget" name="indirectBayesTabs"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="ResNorm"> + <attribute name="title"> + <string>ResNorm</string> + </attribute> </widget> - </item> - <item> - <spacer name="horizontalSpacer_14"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="pbRun"> - <property name="text"> - <string>Run</string> - </property> + <widget class="QWidget" name="Quasi"> + <attribute name="title"> + <string>Quasi</string> + </attribute> </widget> - </item> - <item> - <spacer name="horizontalSpacer_11"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="pbManageDirs"> - <property name="text"> - <string>Manage Directories</string> - </property> + <widget class="QWidget" name="Stretch"> + <attribute name="title"> + <string>Stretch</string> + </attribute> </widget> - </item> - </layout> - </item> - </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="layout_bottom"> + <item> + <widget class="QPushButton" name="pbHelp"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string>?</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_14"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbManageDirs"> + <property name="text"> + <string>Manage Directories</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> </widget> </widget> <resources/> diff --git a/qt/scientific_interfaces/Indirect/IndirectCorrections.cpp b/qt/scientific_interfaces/Indirect/IndirectCorrections.cpp index 49ee1ed7d58b6b60ea711ddd137df6a06fd330fe..fd4a1468af3009880b23afd8ae490594e60c7f62 100644 --- a/qt/scientific_interfaces/Indirect/IndirectCorrections.cpp +++ b/qt/scientific_interfaces/Indirect/IndirectCorrections.cpp @@ -90,7 +90,7 @@ void IndirectCorrections::initLayout() { connect(m_uiForm.pbPythonExport, SIGNAL(clicked()), this, SLOT(exportTabPython())); connect(m_uiForm.pbHelp, SIGNAL(clicked()), this, SLOT(help())); - connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(run())); + // connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(run())); connect(m_uiForm.pbManageDirs, SIGNAL(clicked()), this, SLOT(openDirectoryDialog())); } @@ -125,14 +125,6 @@ void IndirectCorrections::loadSettings() { settings.endGroup(); } -/** - * Private slot, called when the Run button is pressed. Runs current tab. - */ -void IndirectCorrections::run() { - const unsigned int currentTab = m_uiForm.twTabs->currentIndex(); - m_tabs[currentTab]->runTab(); -} - /** * Opens a directory dialog. */ diff --git a/qt/scientific_interfaces/Indirect/IndirectCorrections.h b/qt/scientific_interfaces/Indirect/IndirectCorrections.h index 857f820bf96c885215ae5dbde6ba1eebf1aa10e5..268a1740539d54ce8343b84d6eb8cd8ea6edaf40 100644 --- a/qt/scientific_interfaces/Indirect/IndirectCorrections.h +++ b/qt/scientific_interfaces/Indirect/IndirectCorrections.h @@ -74,8 +74,6 @@ private: private slots: /// Called when the user clicks the Py button void exportTabPython(); - /// Called when the Run button is pressed. Runs current tab. - void run(); /// Opens a directory dialog. void openDirectoryDialog(); /// Opens the Mantid Wiki web page of the current tab. diff --git a/qt/scientific_interfaces/Indirect/IndirectCorrections.ui b/qt/scientific_interfaces/Indirect/IndirectCorrections.ui index 2944656aedc2baccdf8d256413a65d7bb3c27c13..ae444920c7f74f064cb2ba2cdd9c82c66d0190cc 100644 --- a/qt/scientific_interfaces/Indirect/IndirectCorrections.ui +++ b/qt/scientific_interfaces/Indirect/IndirectCorrections.ui @@ -113,26 +113,6 @@ </property> </spacer> </item> - <item> - <widget class="QPushButton" name="pbRun"> - <property name="text"> - <string>Run</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_11"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> <item> <widget class="QPushButton" name="pbManageDirs"> <property name="text"> @@ -148,7 +128,6 @@ <tabstops> <tabstop>twTabs</tabstop> <tabstop>pbHelp</tabstop> - <tabstop>pbRun</tabstop> <tabstop>pbManageDirs</tabstop> </tabstops> <resources/> diff --git a/qt/scientific_interfaces/Indirect/IndirectDataReduction.cpp b/qt/scientific_interfaces/Indirect/IndirectDataReduction.cpp index 125ba6c3b7b4c8dd2136daba3aaed7c97c7c1719..19799ec2ba076aacd9b0a474ccf083f57477acfd 100644 --- a/qt/scientific_interfaces/Indirect/IndirectDataReduction.cpp +++ b/qt/scientific_interfaces/Indirect/IndirectDataReduction.cpp @@ -94,26 +94,15 @@ void IndirectDataReduction::exportTabPython() { m_tabs[tabName].second->exportPythonScript(); } -/** - * This is the function called when the "Run" button is clicked. It will call - * the relevent function - * in the subclass. - */ -void IndirectDataReduction::runClicked() { - QString tabName = - m_uiForm.twIDRTabs->tabText(m_uiForm.twIDRTabs->currentIndex()); - m_tabs[tabName].second->runTab(); -} - /** * Sets up Qt UI file and connects signals, slots. */ void IndirectDataReduction::initLayout() { m_uiForm.setupUi(this); - // Do not allow running until setup and instrument laoding are done - updateRunButton(false, "Loading UI", - "Initialising user interface components..."); + // Do not allow running until setup and instrument loading are done + emitUpdateRunButton(false, "disable", "Loading UI...", + "Initialising user interface components..."); // Create the tabs addTab<ISISEnergyTransfer>("ISIS Energy Transfer"); @@ -130,15 +119,13 @@ void IndirectDataReduction::initLayout() { // Connect the Python export buton connect(m_uiForm.pbPythonExport, SIGNAL(clicked()), this, SLOT(exportTabPython())); - // Connect the "Run" button - connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); // Connect the "Manage User Directories" Button connect(m_uiForm.pbManageDirectories, SIGNAL(clicked()), this, SLOT(openDirectoryDialog())); // Reset the Run button state when the tab is changed connect(m_uiForm.twIDRTabs, SIGNAL(currentChanged(int)), this, - SLOT(updateRunButton())); + SLOT(emitUpdateRunButton())); // Handle instrument configuration changes connect(m_uiForm.iicInstrumentConfiguration, @@ -550,9 +537,8 @@ void IndirectDataReduction::showMessageBox(const QString &message) { * @param message Message shown on the button * @param tooltip Tooltip shown when hovering over button */ -void IndirectDataReduction::updateRunButton(bool enabled, QString message, - QString tooltip) { - m_uiForm.pbRun->setEnabled(enabled); - m_uiForm.pbRun->setText(message); - m_uiForm.pbRun->setToolTip(tooltip); +void IndirectDataReduction::emitUpdateRunButton( + bool enabled, std::string const &enableOutputButtons, QString message, + QString tooltip) { + emit updateRunButton(enabled, enableOutputButtons, message, tooltip); } diff --git a/qt/scientific_interfaces/Indirect/IndirectDataReduction.h b/qt/scientific_interfaces/Indirect/IndirectDataReduction.h index 0f16ba599a237bf147061ff0dd6ed3937b90da3b..ce35d9e82f4a72ac7542aa0775ccacf4906b6cb7 100644 --- a/qt/scientific_interfaces/Indirect/IndirectDataReduction.h +++ b/qt/scientific_interfaces/Indirect/IndirectDataReduction.h @@ -72,6 +72,10 @@ public: signals: /// Emitted when the instrument setup is changed void newInstrumentConfiguration(); + /// Emitted to update the state of the Run button + void updateRunButton(bool enabled = true, + std::string const &enableOutputButtons = "unchanged", + QString message = "Run", QString tooltip = ""); private slots: /// Shows/hides tabs based on facility @@ -80,16 +84,15 @@ private slots: void helpClicked(); /// Exports the current tab algorithms as a Python script void exportTabPython(); - /// Runs the current tab - void runClicked(); /// Opens the manage directory dialog void openDirectoryDialog(); /// Shows a information dialog box void showMessageBox(const QString &message); /// Updates the state of the Run button - void updateRunButton(bool enabled = true, QString message = "Run", - QString tooltip = ""); + void emitUpdateRunButton(bool enabled = true, + std::string const &enableOutputButtons = "unchanged", + QString message = "Run", QString tooltip = ""); /// Called when the load instrument algorithms complete void instrumentLoadingDone(bool error); diff --git a/qt/scientific_interfaces/Indirect/IndirectDataReduction.ui b/qt/scientific_interfaces/Indirect/IndirectDataReduction.ui index d90a46e6ea20eeb5307f8c183bd8fd3b3c717250..d377616ef679a08fc0a7cadded16ff430a828e90 100644 --- a/qt/scientific_interfaces/Indirect/IndirectDataReduction.ui +++ b/qt/scientific_interfaces/Indirect/IndirectDataReduction.ui @@ -123,34 +123,6 @@ </property> </widget> </item> - <item alignment="Qt::AlignLeft"> - <widget class="QPushButton" name="pbRun"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>150</width> - <height>0</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>180</width> - <height>16777215</height> - </size> - </property> - <property name="toolTip"> - <string>Run conversion to energy process.</string> - </property> - <property name="text"> - <string>Run</string> - </property> - </widget> - </item> <item alignment="Qt::AlignRight|Qt::AlignVCenter"> <widget class="QPushButton" name="pbManageDirectories"> <property name="sizePolicy"> @@ -180,7 +152,6 @@ <tabstop>twIDRTabs</tabstop> <tabstop>pbHelp</tabstop> <tabstop>pbPythonExport</tabstop> - <tabstop>pbRun</tabstop> <tabstop>pbManageDirectories</tabstop> </tabstops> <resources/> diff --git a/qt/scientific_interfaces/Indirect/IndirectDataReductionTab.cpp b/qt/scientific_interfaces/Indirect/IndirectDataReductionTab.cpp index 3c8259caf4bee8bc9c457a7434225a8d5ec0c962..937fd4278a47866d7e075c76ef548edcf46f6b7d 100644 --- a/qt/scientific_interfaces/Indirect/IndirectDataReductionTab.cpp +++ b/qt/scientific_interfaces/Indirect/IndirectDataReductionTab.cpp @@ -43,7 +43,8 @@ void IndirectDataReductionTab::runTab() { if (validate()) { m_tabStartTime = DateAndTime::getCurrentTime(); m_tabRunning = true; - emit updateRunButton(false, "Running...", "Running data reduction..."); + emit updateRunButton(false, "disable", "Running...", + "Running data reduction..."); run(); } else { g_log.warning("Failed to validate indirect tab input!"); @@ -60,7 +61,8 @@ void IndirectDataReductionTab::tabExecutionComplete(bool error) { UNUSED_ARG(error); if (m_tabRunning) { m_tabRunning = false; - emit updateRunButton(); + auto const enableOutputButtons = error == false ? "enable" : "disable"; + emit updateRunButton(true, enableOutputButtons); } } diff --git a/qt/scientific_interfaces/Indirect/IndirectDataReductionTab.h b/qt/scientific_interfaces/Indirect/IndirectDataReductionTab.h index 3457a8a3e6af96c282111260b274218b69696433..43a76b9bb1725bd86d2c150fe7d72a53a4a9f53b 100644 --- a/qt/scientific_interfaces/Indirect/IndirectDataReductionTab.h +++ b/qt/scientific_interfaces/Indirect/IndirectDataReductionTab.h @@ -69,8 +69,9 @@ public slots: signals: /// Update the Run button on the IDR main window - void updateRunButton(bool enabled = true, QString message = "Run", - QString tooltip = ""); + void updateRunButton(bool enabled = true, + std::string const &enableOutputButtons = "unchanged", + QString message = "Run", QString tooltip = ""); /// Emitted when the instrument setup is changed void newInstrumentConfiguration(); diff --git a/qt/scientific_interfaces/Indirect/IndirectFitAnalysisTab.cpp b/qt/scientific_interfaces/Indirect/IndirectFitAnalysisTab.cpp index 6ba66aca387594f413c13a48f1a923a131d34b72..9d5a2233695520235761bba34c1dc18f26630079 100644 --- a/qt/scientific_interfaces/Indirect/IndirectFitAnalysisTab.cpp +++ b/qt/scientific_interfaces/Indirect/IndirectFitAnalysisTab.cpp @@ -117,6 +117,8 @@ void IndirectFitAnalysisTab::setup() { connect(m_dataPresenter.get(), SIGNAL(dataChanged()), this, SLOT(updateResultOptions())); + connect(m_dataPresenter.get(), SIGNAL(dataChanged()), this, + SLOT(emitUpdateFitTypes())); connectDataAndSpectrumPresenters(); connectDataAndPlotPresenters(); @@ -222,6 +224,8 @@ void IndirectFitAnalysisTab::connectDataAndSpectrumPresenters() { void IndirectFitAnalysisTab::connectDataAndFitBrowserPresenters() { connect(m_dataPresenter.get(), SIGNAL(dataChanged()), this, SLOT(updateBrowserFittingRange())); + connect(m_dataPresenter.get(), SIGNAL(dataChanged()), this, + SLOT(setBrowserWorkspace())); connect(m_fitPropertyBrowser, SIGNAL(startXChanged(double)), this, SLOT(setDataTableStartX(double))); connect(m_fitPropertyBrowser, SIGNAL(endXChanged(double)), this, @@ -369,6 +373,14 @@ void IndirectFitAnalysisTab::updateBrowserFittingRange() { setBrowserEndX(range.second); } +void IndirectFitAnalysisTab::setBrowserWorkspace() { + if (m_fittingModel->numberOfWorkspaces() != 0) { + auto const name = + m_fittingModel->getWorkspace(getSelectedDataIndex())->getName(); + m_fitPropertyBrowser->setWorkspaceName(QString::fromStdString(name)); + } +} + void IndirectFitAnalysisTab::setBrowserWorkspace(std::size_t dataIndex) { const auto name = m_fittingModel->getWorkspace(dataIndex)->getName(); m_fitPropertyBrowser->setWorkspaceName(QString::fromStdString(name)); @@ -501,6 +513,14 @@ void IndirectFitAnalysisTab::addComboBoxFunctionGroup( m_fitPropertyBrowser->addComboBoxFunctionGroup(groupName, functions); } +/** + * Removes all options from the Fit Type combo-box apart from the 'None' option + * + */ +void IndirectFitAnalysisTab::clearFitTypeComboBox() { + m_fitPropertyBrowser->clearFitTypeComboBox(); +} + /** * Sets the available background options in this fit analysis tab. * @@ -964,6 +984,8 @@ void IndirectFitAnalysisTab::updateResultOptions() { setSaveResultEnabled(isFit); } +void IndirectFitAnalysisTab::emitUpdateFitTypes() { emit updateFitTypes(); } + } // namespace IDA } // namespace CustomInterfaces } // namespace MantidQt diff --git a/qt/scientific_interfaces/Indirect/IndirectFitAnalysisTab.h b/qt/scientific_interfaces/Indirect/IndirectFitAnalysisTab.h index 961a1dc4318e8b88fc82a26dfc80e54d2b32d6e7..ac9f4713d8dfa69f4821727037e6fa601274e0a0 100644 --- a/qt/scientific_interfaces/Indirect/IndirectFitAnalysisTab.h +++ b/qt/scientific_interfaces/Indirect/IndirectFitAnalysisTab.h @@ -74,6 +74,7 @@ public: void addComboBoxFunctionGroup( const QString &groupName, const std::vector<Mantid::API::IFunction_sptr> &functions); + void clearFitTypeComboBox(); void setBackgroundOptions(const QStringList &backgrounds); @@ -157,6 +158,7 @@ signals: void functionChanged(); void parameterChanged(const Mantid::API::IFunction *); void customBoolChanged(const QString &key, bool value); + void updateFitTypes(); protected slots: @@ -169,6 +171,7 @@ protected slots: void setBrowserStartX(double startX); void setBrowserEndX(double endX); void updateBrowserFittingRange(); + void setBrowserWorkspace(); void setBrowserWorkspace(std::size_t dataIndex); void setBrowserWorkspaceIndex(std::size_t spectrum); void tableStartXChanged(double startX, std::size_t dataIndex, @@ -196,6 +199,9 @@ protected slots: void updateResultOptions(); void saveResult(); +private slots: + void emitUpdateFitTypes(); + private: /// Overidden by child class. void setup() override; diff --git a/qt/scientific_interfaces/Indirect/IndirectFitData.cpp b/qt/scientific_interfaces/Indirect/IndirectFitData.cpp index ca2f2e729b57d8e804fed6a630afe10872848dc2..5ccd35e13aa97cfe33e367bf806aa4d7ba5fa87b 100644 --- a/qt/scientific_interfaces/Indirect/IndirectFitData.cpp +++ b/qt/scientific_interfaces/Indirect/IndirectFitData.cpp @@ -6,6 +6,8 @@ // SPDX - License - Identifier: GPL - 3.0 + #include "IndirectFitData.h" +#include "MantidKernel/Strings.h" + #include <boost/algorithm/string.hpp> #include <boost/format.hpp> @@ -15,6 +17,7 @@ using namespace Mantid::API; namespace { using namespace MantidQt::CustomInterfaces::IDA; +using namespace Mantid::Kernel::Strings; std::string rangeToString(const std::pair<std::size_t, std::size_t> &range, const std::string &delimiter = "-") { @@ -96,6 +99,53 @@ private: const std::string m_rangeDelimiter; }; +std::string constructSpectraString(std::vector<int> const &spectras) { + return joinCompress(spectras.begin(), spectras.end()); +} + +std::vector<std::string> splitStringBy(std::string const &str, + std::string const &delimiter) { + std::vector<std::string> subStrings; + boost::split(subStrings, str, boost::is_any_of(delimiter)); + subStrings.erase(std::remove_if(subStrings.begin(), subStrings.end(), + [](std::string const &subString) { + return subString.empty(); + }), + subStrings.end()); + return subStrings; +} + +std::string getSpectraRange(std::string const &string) { + auto bounds = splitStringBy(string, "-"); + return bounds[0] > bounds[1] ? bounds[1] + "-" + bounds[0] : string; +} + +std::string rearrangeSpectraSubString(std::string const &string) { + return string.find("-") != std::string::npos ? getSpectraRange(string) + : string; +} + +// Swaps the two numbers in a spectra range if they go from large to small +std::string rearrangeSpectraRangeStrings(std::string const &string) { + std::string spectraString; + std::vector<std::string> subStrings = splitStringBy(string, ","); + for (auto it = subStrings.begin(); it < subStrings.end(); ++it) { + spectraString += rearrangeSpectraSubString(*it); + spectraString += it != subStrings.end() ? "," : ""; + } + return spectraString; +} + +std::string createSpectraString(std::string string) { + string.erase(std::remove_if(string.begin(), string.end(), isspace), + string.end()); + std::vector<int> spectras = parseRange(rearrangeSpectraRangeStrings(string)); + std::sort(spectras.begin(), spectras.end()); + // Remove duplicate entries + spectras.erase(std::unique(spectras.begin(), spectras.end()), spectras.end()); + return constructSpectraString(spectras); +} + struct CombineSpectra : boost::static_visitor<Spectra> { Spectra operator()(const std::pair<std::size_t, std::size_t> &spectra1, @@ -104,15 +154,16 @@ struct CombineSpectra : boost::static_visitor<Spectra> { return std::make_pair(spectra1.first, spectra2.second); else if (spectra2.second + 1 == spectra1.first) return std::make_pair(spectra2.first, spectra1.second); - else - return DiscontinuousSpectra<std::size_t>(rangeToString(spectra1) + "," + - rangeToString(spectra2)); + else { + return DiscontinuousSpectra<std::size_t>(createSpectraString( + rangeToString(spectra1) + "," + rangeToString(spectra2))); + } } Spectra operator()(const Spectra &spectra1, const Spectra &spectra2) const { - return DiscontinuousSpectra<std::size_t>( + return DiscontinuousSpectra<std::size_t>(createSpectraString( boost::apply_visitor(SpectraToString(), spectra1) + "," + - boost::apply_visitor(SpectraToString(), spectra2)); + boost::apply_visitor(SpectraToString(), spectra2))); } }; @@ -171,6 +222,45 @@ tryPassFormatArgument(boost::basic_format<char> &formatString, std::pair<double, double> getBinRange(MatrixWorkspace_sptr workspace) { return std::make_pair(workspace->x(0).front(), workspace->x(0).back()); } + +double convertBoundToDoubleAndFormat(std::string const &str) { + return std::round(std::stod(str) * 10) / 10; +} + +std::string constructExcludeRegionString(std::vector<double> const &bounds) { + std::string excludeRegion; + for (auto it = bounds.begin(); it < bounds.end(); ++it) { + auto splitDouble = splitStringBy(std::to_string(*it), "."); + excludeRegion += splitDouble[0] + "." + splitDouble[1].front(); + excludeRegion += it == bounds.end() - 1 ? "" : ","; + } + return excludeRegion; +} + +std::string orderExcludeRegionString(std::vector<double> &bounds) { + for (auto it = bounds.begin(); it < bounds.end() - 1; it += 2) + if (*it > *(it + 1)) + std::iter_swap(it, it + 1); + return constructExcludeRegionString(bounds); +} + +std::vector<double> +getBoundsAsDoubleVector(std::vector<std::string> const &boundStrings) { + std::vector<double> bounds; + bounds.reserve(boundStrings.size()); + for (auto bound : boundStrings) + bounds.emplace_back(convertBoundToDoubleAndFormat(bound)); + return bounds; +} + +std::string createExcludeRegionString(std::string regionString) { + regionString.erase( + std::remove_if(regionString.begin(), regionString.end(), isspace), + regionString.end()); + auto bounds = getBoundsAsDoubleVector(splitStringBy(regionString, ",")); + return orderExcludeRegionString(bounds); +} + } // namespace namespace MantidQt { @@ -224,7 +314,9 @@ std::size_t IndirectFitData::numberOfSpectra() const { } bool IndirectFitData::zeroSpectra() const { - return boost::apply_visitor(CheckZeroSpectrum(), m_spectra); + if (m_workspace->getNumberHistograms()) + return boost::apply_visitor(CheckZeroSpectrum(), m_spectra); + return true; } std::pair<double, double> @@ -247,8 +339,15 @@ IndirectFitData::excludeRegionsVector(std::size_t spectrum) const { return vectorFromString<double>(getExcludeRegion(spectrum)); } -void IndirectFitData::setSpectra(const std::string &spectra) { - setSpectra(DiscontinuousSpectra<std::size_t>(spectra)); +void IndirectFitData::setSpectra(std::string const &spectra) { + try { + const Spectra spec = + DiscontinuousSpectra<std::size_t>(createSpectraString(spectra)); + setSpectra(spec); + } catch (std::exception &ex) { + throw std::runtime_error("Spectra too large for cast: " + + std::string(ex.what())); + } } void IndirectFitData::setSpectra(Spectra &&spectra) { @@ -256,12 +355,12 @@ void IndirectFitData::setSpectra(Spectra &&spectra) { m_spectra = std::move(spectra); } -void IndirectFitData::setSpectra(const Spectra &spectra) { +void IndirectFitData::setSpectra(Spectra const &spectra) { validateSpectra(spectra); m_spectra = spectra; } -void IndirectFitData::validateSpectra(const Spectra &spectra) { +void IndirectFitData::validateSpectra(Spectra const &spectra) { const auto visitor = SpectraOutOfRange<std::size_t>(0, workspace()->getNumberHistograms() - 1); auto notInRange = boost::apply_visitor(visitor, spectra); @@ -273,7 +372,8 @@ void IndirectFitData::validateSpectra(const Spectra &spectra) { } } -void IndirectFitData::setStartX(double startX, std::size_t spectrum) { +void IndirectFitData::setStartX(double const &startX, + std::size_t const &spectrum) { const auto range = m_ranges.find(spectrum); if (range != m_ranges.end()) range->second.first = startX; @@ -284,7 +384,7 @@ void IndirectFitData::setStartX(double startX, std::size_t spectrum) { "Unable to set StartX: Workspace no longer exists."); } -void IndirectFitData::setEndX(double endX, std::size_t spectrum) { +void IndirectFitData::setEndX(double const &endX, std::size_t const &spectrum) { const auto range = m_ranges.find(spectrum); if (range != m_ranges.end()) range->second.second = endX; @@ -294,12 +394,15 @@ void IndirectFitData::setEndX(double endX, std::size_t spectrum) { throw std::runtime_error("Unable to set EndX: Workspace no longer exists."); } -void IndirectFitData::setExcludeRegionString(const std::string &excludeRegion, - std::size_t spectrum) { - m_excludeRegions[spectrum] = excludeRegion; +void IndirectFitData::setExcludeRegionString( + std::string const &excludeRegionString, std::size_t const &spectrum) { + if (!excludeRegionString.empty()) + m_excludeRegions[spectrum] = createExcludeRegionString(excludeRegionString); + else + m_excludeRegions[spectrum] = excludeRegionString; } -IndirectFitData &IndirectFitData::combine(const IndirectFitData &fitData) { +IndirectFitData &IndirectFitData::combine(IndirectFitData const &fitData) { m_workspace = fitData.m_workspace; setSpectra( boost::apply_visitor(CombineSpectra(), m_spectra, fitData.m_spectra)); diff --git a/qt/scientific_interfaces/Indirect/IndirectFitData.h b/qt/scientific_interfaces/Indirect/IndirectFitData.h index 344dc08e70ab9510843bf6b343c853bca2d56ce1..2366846ecb950f6a8a8212a0bcf0e093d08caedc 100644 --- a/qt/scientific_interfaces/Indirect/IndirectFitData.h +++ b/qt/scientific_interfaces/Indirect/IndirectFitData.h @@ -7,6 +7,7 @@ #ifndef MANTIDQTCUSTOMINTERFACESIDA_INDIRECTFITDATA_H_ #define MANTIDQTCUSTOMINTERFACESIDA_INDIRECTFITDATA_H_ +#include "DllConfig.h" #include "MantidAPI/MatrixWorkspace.h" #include "MantidKernel/ArrayProperty.h" @@ -71,6 +72,9 @@ public: } typename std::vector<T>::const_iterator end() const { return m_vec.end(); } const T &operator[](std::size_t index) const { return m_vec[index]; } + bool operator==(DiscontinuousSpectra<std::size_t> const &spec) const { + return this->getString() == spec.getString(); + } private: std::string m_str; @@ -137,7 +141,7 @@ private: fitting range and exclude regions. Provides methods for accessing and applying the fitting data. */ -class IndirectFitData { +class MANTIDQT_INDIRECT_DLL IndirectFitData { public: IndirectFitData(Mantid::API::MatrixWorkspace_sptr workspace, const Spectra &spectra); @@ -154,7 +158,7 @@ public: bool zeroSpectra() const; std::pair<double, double> getRange(std::size_t spectrum) const; std::string getExcludeRegion(std::size_t spectrum) const; - IndirectFitData &combine(const IndirectFitData &fitData); + IndirectFitData &combine(IndirectFitData const &fitData); std::vector<double> excludeRegionsVector(std::size_t spectrum) const; @@ -168,16 +172,16 @@ public: ApplyEnumeratedSpectra<F>(std::forward<F>(functor), start), m_spectra); } - void setSpectra(const std::string &spectra); + void setSpectra(std::string const &spectra); void setSpectra(Spectra &&spectra); - void setSpectra(const Spectra &spectra); - void setStartX(double startX, std::size_t index); - void setEndX(double endX, std::size_t spectrum); - void setExcludeRegionString(const std::string &excludeRegion, - std::size_t spectrum); + void setSpectra(Spectra const &spectra); + void setStartX(double const &startX, std::size_t const &index); + void setEndX(double const &endX, std::size_t const &spectrum); + void setExcludeRegionString(std::string const &excludeRegion, + std::size_t const &spectrum); private: - void validateSpectra(const Spectra &spectra); + void validateSpectra(Spectra const &spectra); Mantid::API::MatrixWorkspace_sptr m_workspace; Spectra m_spectra; diff --git a/qt/scientific_interfaces/Indirect/IndirectFitOutput.cpp b/qt/scientific_interfaces/Indirect/IndirectFitOutput.cpp index 31fb3c5edb6d3b60e837c1c9ae7fd8283eb66f0d..e5e1909e15c11d95d7718017c7783399d9ef9ce0 100644 --- a/qt/scientific_interfaces/Indirect/IndirectFitOutput.cpp +++ b/qt/scientific_interfaces/Indirect/IndirectFitOutput.cpp @@ -272,10 +272,9 @@ IndirectFitOutput::getResultLocation(IndirectFitData const *fitData, } std::vector<std::string> IndirectFitOutput::getResultParameterNames() const { - if (auto resultWorkspace = getLastResultWorkspace()) { + if (auto resultWorkspace = getLastResultWorkspace()) if (auto workspace = getMatrixWorkspaceFromGroup(resultWorkspace, 0)) return getAxisLabels(workspace, 1); - } return std::vector<std::string>(); } @@ -324,11 +323,11 @@ void IndirectFitOutput::addOutput(WorkspaceGroup_sptr resultGroup, m_resultGroup = resultGroup; } -void IndirectFitOutput::addOutput( - Mantid::API::WorkspaceGroup_sptr resultGroup, - Mantid::API::ITableWorkspace_sptr parameterTable, - Mantid::API::WorkspaceGroup_sptr resultWorkspace, - IndirectFitData const *fitData, std::size_t spectrum) { +void IndirectFitOutput::addOutput(WorkspaceGroup_sptr resultGroup, + ITableWorkspace_sptr parameterTable, + WorkspaceGroup_sptr resultWorkspace, + IndirectFitData const *fitData, + std::size_t spectrum) { TableRowExtractor extractRowFromTable(parameterTable); m_parameters[fitData][spectrum] = extractRowFromTable(0); m_outputResultLocations[fitData][spectrum] = ResultLocation(resultGroup, 0); @@ -359,8 +358,8 @@ void IndirectFitOutput::updateParameters(ITableWorkspace_sptr parameterTable, } void IndirectFitOutput::updateFitResultsFromUnstructured( - Mantid::API::WorkspaceGroup_sptr resultGroup, - const FitDataIterator &fitDataBegin, const FitDataIterator &fitDataEnd) { + WorkspaceGroup_sptr resultGroup, const FitDataIterator &fitDataBegin, + const FitDataIterator &fitDataEnd) { std::unordered_map<MatrixWorkspace *, std::unordered_map<std::size_t, std::size_t>> resultIndices; @@ -376,8 +375,8 @@ void IndirectFitOutput::updateFitResultsFromUnstructured( } void IndirectFitOutput::updateFitResultsFromStructured( - Mantid::API::WorkspaceGroup_sptr resultGroup, - const FitDataIterator &fitDataBegin, const FitDataIterator &fitDataEnd) { + WorkspaceGroup_sptr resultGroup, const FitDataIterator &fitDataBegin, + const FitDataIterator &fitDataEnd) { auto update = [&](IndirectFitData const *inputData) { auto &fitResults = extractOrAddDefault(m_outputResultLocations, inputData); return [&](std::size_t index, std::size_t spectrum) { diff --git a/qt/scientific_interfaces/Indirect/IndirectFitOutput.h b/qt/scientific_interfaces/Indirect/IndirectFitOutput.h index 85a8188e4867032dc67fce43565e87f0078bfd9b..c92182711fd7ffea092c7ad2e052d16ddd7caabc 100644 --- a/qt/scientific_interfaces/Indirect/IndirectFitOutput.h +++ b/qt/scientific_interfaces/Indirect/IndirectFitOutput.h @@ -9,6 +9,7 @@ #include "IndirectFitData.h" +#include "DllConfig.h" #include "MantidAPI/ITableWorkspace.h" #include "MantidAPI/WorkspaceGroup.h" @@ -49,7 +50,7 @@ using FitDataIterator = IndirectFitOutput - Stores the output of a QENS fit and provides convenient access to the output parameters. */ -class IndirectFitOutput { +class MANTIDQT_INDIRECT_DLL IndirectFitOutput { public: IndirectFitOutput(Mantid::API::WorkspaceGroup_sptr resultGroup, Mantid::API::ITableWorkspace_sptr parameterTable, diff --git a/qt/scientific_interfaces/Indirect/IndirectFitPlotModel.cpp b/qt/scientific_interfaces/Indirect/IndirectFitPlotModel.cpp index d7446b0846f1529ceaa973301d59f5acdc3c6d0d..634c668f12269b0bd42eec48997c7fffe22e2e0a 100644 --- a/qt/scientific_interfaces/Indirect/IndirectFitPlotModel.cpp +++ b/qt/scientific_interfaces/Indirect/IndirectFitPlotModel.cpp @@ -17,7 +17,7 @@ namespace { using namespace Mantid::API; -// The name of the conjoined input and guess name -- required for +// The name of the conjoined input and guess workspaces -- required for // creating an external guess plot. const std::string INPUT_AND_GUESS_NAME = "__QENSInputAndGuess"; @@ -124,11 +124,13 @@ void IndirectFitPlotModel::setActiveSpectrum(std::size_t spectrum) { } void IndirectFitPlotModel::setStartX(double startX) { - m_fittingModel->setStartX(startX, m_activeIndex, m_activeSpectrum); + if (getRange().second > startX) + m_fittingModel->setStartX(startX, m_activeIndex, m_activeSpectrum); } void IndirectFitPlotModel::setEndX(double endX) { - m_fittingModel->setEndX(endX, m_activeIndex, m_activeSpectrum); + if (getRange().first < endX) + m_fittingModel->setEndX(endX, m_activeIndex, m_activeSpectrum); } void IndirectFitPlotModel::setFWHM(double fwhm) { @@ -182,7 +184,9 @@ std::size_t IndirectFitPlotModel::numberOfWorkspaces() const { } std::string IndirectFitPlotModel::getFitDataName(std::size_t index) const { - return m_fittingModel->createDisplayName("%1% (%2%)", "-", index); + if (m_fittingModel->getWorkspace(index)) + return m_fittingModel->createDisplayName("%1% (%2%)", "-", index); + return ""; } std::string IndirectFitPlotModel::getFitDataName() const { @@ -190,7 +194,9 @@ std::string IndirectFitPlotModel::getFitDataName() const { } std::string IndirectFitPlotModel::getLastFitDataName() const { - return getFitDataName(m_fittingModel->numberOfWorkspaces() - 1); + if (m_fittingModel->numberOfWorkspaces()) + return getFitDataName(m_fittingModel->numberOfWorkspaces() - 1); + return ""; } boost::optional<double> IndirectFitPlotModel::getFirstHWHM() const { diff --git a/qt/scientific_interfaces/Indirect/IndirectFittingModel.cpp b/qt/scientific_interfaces/Indirect/IndirectFittingModel.cpp index 09ecfd3c501a75c6418c4121f69e194358159e75..24a20a606dfc25897470eb174008ce2b919ace52 100644 --- a/qt/scientific_interfaces/Indirect/IndirectFittingModel.cpp +++ b/qt/scientific_interfaces/Indirect/IndirectFittingModel.cpp @@ -418,7 +418,12 @@ std::size_t IndirectFittingModel::numberOfWorkspaces() const { } std::size_t IndirectFittingModel::getNumberOfSpectra(std::size_t index) const { - return m_fittingData[index]->numberOfSpectra(); + if (index < m_fittingData.size()) + return m_fittingData[index]->numberOfSpectra(); + else + throw std::runtime_error( + "Cannot find the number of spectra for a workspace: the workspace " + "index provided is too large."); } std::vector<std::string> IndirectFittingModel::getFitParameterNames() const { @@ -513,8 +518,17 @@ void IndirectFittingModel::addNewWorkspace(MatrixWorkspace_sptr workspace, createDefaultParameters(m_fittingData.size() - 1)); } +void IndirectFittingModel::removeWorkspaceFromFittingData( + std::size_t const &index) { + if (m_fittingData.size() > index) + removeFittingData(index); + else + throw std::runtime_error("Cannot remove a workspace from the fitting data: " + "the workspace index provided is too large."); +} + void IndirectFittingModel::removeWorkspace(std::size_t index) { - removeFittingData(index); + removeWorkspaceFromFittingData(index); if (index > 0 && m_fittingData.size() > index) { const auto previousWS = m_fittingData[index - 1]->workspace(); diff --git a/qt/scientific_interfaces/Indirect/IndirectFittingModel.h b/qt/scientific_interfaces/Indirect/IndirectFittingModel.h index 1d9784f3e15a979cce5ecfa264166684747b7a87..c5e9aafa4c3afc64c5d4791ebb8e0b1c75b8db28 100644 --- a/qt/scientific_interfaces/Indirect/IndirectFittingModel.h +++ b/qt/scientific_interfaces/Indirect/IndirectFittingModel.h @@ -73,7 +73,7 @@ public: void setStartX(double startX, std::size_t dataIndex, std::size_t spectrum); void setEndX(double endX, std::size_t dataIndex, std::size_t spectrum); void setExcludeRegion(const std::string &exclude, std::size_t dataIndex, - std::size_t index); + std::size_t spectrum); void addWorkspace(const std::string &workspaceName); void addWorkspace(const std::string &workspaceName, @@ -133,6 +133,8 @@ protected: void removeFittingData(std::size_t index); private: + void removeWorkspaceFromFittingData(std::size_t const &index); + Mantid::API::IAlgorithm_sptr createSequentialFit(Mantid::API::IFunction_sptr function, const std::string &input, diff --git a/qt/scientific_interfaces/Indirect/IndirectMoments.cpp b/qt/scientific_interfaces/Indirect/IndirectMoments.cpp index 2697470dd173d68f9e667b6d525c34e105da92e4..2686fba0ab2dcee23ce451d345885873a27e203a 100644 --- a/qt/scientific_interfaces/Indirect/IndirectMoments.cpp +++ b/qt/scientific_interfaces/Indirect/IndirectMoments.cpp @@ -57,8 +57,16 @@ IndirectMoments::IndirectMoments(IndirectDataReduction *idrUI, QWidget *parent) SLOT(momentsAlgComplete(bool))); // Plot and save + connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); connect(m_uiForm.pbPlot, SIGNAL(clicked()), this, SLOT(plotClicked())); connect(m_uiForm.pbSave, SIGNAL(clicked()), this, SLOT(saveClicked())); + + connect(this, + SIGNAL(updateRunButton(bool, std::string const &, QString const &, + QString const &)), + this, + SLOT(updateRunButton(bool, std::string const &, QString const &, + QString const &))); } //---------------------------------------------------------------------------------------------- @@ -206,15 +214,21 @@ void IndirectMoments::momentsAlgComplete(bool error) { m_uiForm.pbSave->setEnabled(true); } +/** + * Handle when Run is clicked + */ +void IndirectMoments::runClicked() { runTab(); } + /** * Handle mantid plotting */ void IndirectMoments::plotClicked() { + setPlotIsPlotting(true); QString outputWs = getWorkspaceBasename(m_uiForm.dsInput->getCurrentDataName()) + "_Moments"; - if (checkADSForPlotSaveWorkspace(outputWs.toStdString(), true)) { + if (checkADSForPlotSaveWorkspace(outputWs.toStdString(), true)) plotSpectra(outputWs, {0, 2, 4}); - } + setPlotIsPlotting(false); } /** @@ -228,5 +242,40 @@ void IndirectMoments::saveClicked() { m_batchAlgoRunner->executeBatchAsync(); } +void IndirectMoments::setRunEnabled(bool enabled) { + m_uiForm.pbRun->setEnabled(enabled); +} +void IndirectMoments::setPlotEnabled(bool enabled) { + m_uiForm.pbPlot->setEnabled(enabled); +} +void IndirectMoments::setSaveEnabled(bool enabled) { + m_uiForm.pbSave->setEnabled(enabled); +} + +void IndirectMoments::setOutputButtonsEnabled( + std::string const &enableOutputButtons) { + bool enable = enableOutputButtons == "enable" ? true : false; + setPlotEnabled(enable); + setSaveEnabled(enable); +} + +void IndirectMoments::updateRunButton(bool enabled, + std::string const &enableOutputButtons, + QString const message, + QString const tooltip) { + setRunEnabled(enabled); + m_uiForm.pbRun->setText(message); + m_uiForm.pbRun->setToolTip(tooltip); + if (enableOutputButtons != "unchanged") + setOutputButtonsEnabled(enableOutputButtons); +} + +void IndirectMoments::setPlotIsPlotting(bool plotting) { + m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot Result"); + setPlotEnabled(!plotting); + setRunEnabled(!plotting); + setSaveEnabled(!plotting); +} + } // namespace CustomInterfaces } // namespace MantidQt diff --git a/qt/scientific_interfaces/Indirect/IndirectMoments.h b/qt/scientific_interfaces/Indirect/IndirectMoments.h index 2a6888b538be4773e488debeb226ee18a4d832d1..28445d176ad0291e01d9a2cafd01f1197957fdf3 100644 --- a/qt/scientific_interfaces/Indirect/IndirectMoments.h +++ b/qt/scientific_interfaces/Indirect/IndirectMoments.h @@ -44,8 +44,19 @@ protected slots: /// Called when the algorithm completes to update preview plot void momentsAlgComplete(bool error); /// Slots for plot and save - void saveClicked(); + void runClicked(); void plotClicked(); + void saveClicked(); + + void setRunEnabled(bool enabled); + void setPlotEnabled(bool enabled); + void setSaveEnabled(bool enabled); + void setOutputButtonsEnabled(std::string const &enableOutputButtons); + void updateRunButton(bool enabled = true, + std::string const &enableOutputButtons = "unchanged", + QString const message = "Run", + QString const tooltip = ""); + void setPlotIsPlotting(bool plotting); private: Ui::IndirectMoments m_uiForm; diff --git a/qt/scientific_interfaces/Indirect/IndirectMoments.ui b/qt/scientific_interfaces/Indirect/IndirectMoments.ui index fd47eb5f5042c74a8e1cc397555356a9ea51cbbb..cbebc1e394907bc527575b187101b2a658f41b64 100644 --- a/qt/scientific_interfaces/Indirect/IndirectMoments.ui +++ b/qt/scientific_interfaces/Indirect/IndirectMoments.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>500</width> - <height>398</height> + <height>439</height> </rect> </property> <property name="minimumSize"> @@ -136,7 +136,16 @@ <string>Preview</string> </property> <layout class="QHBoxLayout" name="horizontalLayout_1"> - <property name="margin"> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <property name="bottomMargin"> <number>6</number> </property> <item> @@ -156,6 +165,66 @@ </layout> </widget> </item> + <item> + <widget class="QGroupBox" name="gbRun"> + <property name="title"> + <string>Run</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>7</number> + </property> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>185</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbRun"> + <property name="minimumSize"> + <size> + <width>150</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>180</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>184</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> <item> <widget class="QGroupBox" name="gbOutput"> <property name="title"> diff --git a/qt/scientific_interfaces/Indirect/IndirectSpectrumSelectionPresenter.cpp b/qt/scientific_interfaces/Indirect/IndirectSpectrumSelectionPresenter.cpp index 04a81f3ff68a4b9a78845a11d3d035130342d019..603d416b1df07c8faa067f57dc9cac4aa3a7a635 100644 --- a/qt/scientific_interfaces/Indirect/IndirectSpectrumSelectionPresenter.cpp +++ b/qt/scientific_interfaces/Indirect/IndirectSpectrumSelectionPresenter.cpp @@ -7,7 +7,7 @@ #include "IndirectSpectrumSelectionPresenter.h" #include "MantidKernel/ArrayProperty.h" - +#include "MantidKernel/Strings.h" #include "MantidQtWidgets/Common/SignalBlocker.h" #include <algorithm> @@ -18,6 +18,7 @@ namespace { using namespace MantidQt::CustomInterfaces::IDA; +using namespace Mantid::Kernel::Strings; struct SetViewSpectra : boost::static_visitor<> { explicit SetViewSpectra(IndirectSpectrumSelectionView *view) : m_view(view) {} @@ -43,6 +44,53 @@ std::string NATURAL_NUMBER(std::size_t digits) { return OR("0", "[1-9][0-9]{," + std::to_string(digits - 1) + "}"); } +std::string constructSpectraString(std::vector<int> const &spectras) { + return joinCompress(spectras.begin(), spectras.end()); +} + +std::vector<std::string> splitStringBy(std::string const &str, + std::string const &delimiter) { + std::vector<std::string> subStrings; + boost::split(subStrings, str, boost::is_any_of(delimiter)); + subStrings.erase(std::remove_if(subStrings.begin(), subStrings.end(), + [](std::string const &subString) { + return subString.empty(); + }), + subStrings.end()); + return subStrings; +} + +std::string getSpectraRange(std::string const &string) { + auto bounds = splitStringBy(string, "-"); + return bounds[0] > bounds[1] ? bounds[1] + "-" + bounds[0] : string; +} + +std::string rearrangeSpectraSubString(std::string const &string) { + return string.find("-") != std::string::npos ? getSpectraRange(string) + : string; +} + +// Swaps the two numbers in a spectra range if they go from large to small +std::string rearrangeSpectraRangeStrings(std::string const &string) { + std::string spectraString; + std::vector<std::string> subStrings = splitStringBy(string, ","); + for (auto it = subStrings.begin(); it < subStrings.end(); ++it) { + spectraString += rearrangeSpectraSubString(*it); + spectraString += it != subStrings.end() ? "," : ""; + } + return spectraString; +} + +std::string createSpectraString(std::string string) { + string.erase(std::remove_if(string.begin(), string.end(), isspace), + string.end()); + std::vector<int> spectras = parseRange(rearrangeSpectraRangeStrings(string)); + std::sort(spectras.begin(), spectras.end()); + // Remove duplicate entries + spectras.erase(std::unique(spectras.begin(), spectras.end()), spectras.end()); + return constructSpectraString(spectras); +} + namespace Regexes { const std::string EMPTY = "^$"; const std::string SPACE = "[ ]*"; @@ -77,6 +125,8 @@ IndirectSpectrumSelectionPresenter::IndirectSpectrumSelectionPresenter( connect(m_view.get(), SIGNAL(selectedSpectraChanged(std::size_t, std::size_t)), this, SLOT(updateSpectraRange(std::size_t, std::size_t))); + connect(m_view.get(), SIGNAL(selectedSpectraChanged(const std::string &)), + this, SLOT(displaySpectraList(const std::string &))); connect(m_view.get(), SIGNAL(maskSpectrumChanged(int)), this, SLOT(setMaskIndex(int))); @@ -84,6 +134,8 @@ IndirectSpectrumSelectionPresenter::IndirectSpectrumSelectionPresenter( SLOT(displayBinMask())); connect(m_view.get(), SIGNAL(maskChanged(const std::string &)), this, SLOT(setBinMask(const std::string &))); + connect(m_view.get(), SIGNAL(maskChanged(const std::string &)), this, + SLOT(displayBinMask())); connect(m_view.get(), SIGNAL(maskChanged(const std::string &)), this, SIGNAL(maskChanged(const std::string &))); @@ -134,7 +186,7 @@ void IndirectSpectrumSelectionPresenter::setSpectraRange(std::size_t minimum, } void IndirectSpectrumSelectionPresenter::setModelSpectra( - const Spectra &spectra) { + Spectra const &spectra) { try { m_model->setSpectra(spectra, m_activeIndex); m_spectraError.clear(); @@ -148,8 +200,9 @@ void IndirectSpectrumSelectionPresenter::setModelSpectra( } void IndirectSpectrumSelectionPresenter::updateSpectraList( - const std::string &spectraList) { - setModelSpectra(DiscontinuousSpectra<std::size_t>(spectraList)); + std::string const &spectraList) { + setModelSpectra( + DiscontinuousSpectra<std::size_t>(createSpectraString(spectraList))); emit spectraChanged(m_activeIndex); } @@ -160,15 +213,20 @@ void IndirectSpectrumSelectionPresenter::updateSpectraRange( } void IndirectSpectrumSelectionPresenter::setMaskSpectraList( - const std::string &spectra) { + std::string const &spectra) { if (m_spectraError.empty()) m_view->setMaskSpectraList(vectorFromString<std::size_t>(spectra)); else m_view->setMaskSpectraList({}); } +void IndirectSpectrumSelectionPresenter::displaySpectraList( + std::string const &spectra) { + m_view->displaySpectra(createSpectraString(spectra)); +} + void IndirectSpectrumSelectionPresenter::setBinMask( - const std::string &maskString) { + std::string const &maskString) { auto validator = validateMaskBinsString(); if (validator.isAllInputValid()) { diff --git a/qt/scientific_interfaces/Indirect/IndirectSpectrumSelectionPresenter.h b/qt/scientific_interfaces/Indirect/IndirectSpectrumSelectionPresenter.h index 07d6f5d0b1bdc6b1e98c1422a7721c4ca1d31f11..72d33f7f0bc5521db615e53feaac0e6b538d45de 100644 --- a/qt/scientific_interfaces/Indirect/IndirectSpectrumSelectionPresenter.h +++ b/qt/scientific_interfaces/Indirect/IndirectSpectrumSelectionPresenter.h @@ -34,9 +34,9 @@ public: signals: void spectraChanged(std::size_t); - void maskChanged(const std::string &); - void invalidSpectraString(const QString &errorMessage); - void invalidMaskBinsString(const QString &errorMessage); + void maskChanged(std::string const &); + void invalidSpectraString(QString const &errorMessage); + void invalidMaskBinsString(QString const &errorMessage); public slots: void setActiveModelIndex(std::size_t index); @@ -47,15 +47,16 @@ public slots: void enableView(); private slots: - void setBinMask(const std::string &maskString); - void setMaskSpectraList(const std::string &spectraList); - void updateSpectraList(const std::string &spectraList); + void setBinMask(std::string const &maskString); + void setMaskSpectraList(std::string const &spectraList); + void updateSpectraList(std::string const &spectraList); void updateSpectraRange(std::size_t minimum, std::size_t maximum); + void displaySpectraList(std::string const &spectra); void setMaskIndex(int index); private: void setSpectraRange(std::size_t minimum, std::size_t maximum); - void setModelSpectra(const Spectra &spectra); + void setModelSpectra(Spectra const &spectra); UserInputValidator validateSpectraString(); UserInputValidator &validateSpectraString(UserInputValidator &validator); diff --git a/qt/scientific_interfaces/Indirect/IndirectSpectrumSelectionView.h b/qt/scientific_interfaces/Indirect/IndirectSpectrumSelectionView.h index 60657423a13734a911c5be43f2b9c2de673a6aff..93085c6cbcb751bf32e8ff7ad675778f260bb5ab 100644 --- a/qt/scientific_interfaces/Indirect/IndirectSpectrumSelectionView.h +++ b/qt/scientific_interfaces/Indirect/IndirectSpectrumSelectionView.h @@ -92,6 +92,7 @@ private slots: private: void setSpectraRangeMinimum(int minimum); void setSpectraRangeMaximum(int maximum); + void displaySpectraList(); QValidator *createValidator(const QString ®ex); std::unique_ptr<Ui::IndirectSpectrumSelector> m_selector; diff --git a/qt/scientific_interfaces/Indirect/IndirectSqw.cpp b/qt/scientific_interfaces/Indirect/IndirectSqw.cpp index ccf9329611e02f95248d9c9ee15c15e7009a5fec..e91e3d798ab349244184833d6ccb210092cbb2b9 100644 --- a/qt/scientific_interfaces/Indirect/IndirectSqw.cpp +++ b/qt/scientific_interfaces/Indirect/IndirectSqw.cpp @@ -8,6 +8,7 @@ #include "../General/UserInputValidator.h" #include "MantidAPI/MatrixWorkspace.h" +#include "MantidQtWidgets/Common/SignalBlocker.h" #include <QFileInfo> @@ -28,8 +29,19 @@ IndirectSqw::IndirectSqw(IndirectDataReduction *idrUI, QWidget *parent) connect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this, SLOT(sqwAlgDone(bool))); - connect(m_uiForm.pbPlot, SIGNAL(clicked()), this, SLOT(plotClicked())); + connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); + connect(m_uiForm.pbPlotSpectrum, SIGNAL(clicked()), this, + SLOT(plotSpectrumClicked())); + connect(m_uiForm.pbPlotContour, SIGNAL(clicked()), this, + SLOT(plotContourClicked())); connect(m_uiForm.pbSave, SIGNAL(clicked()), this, SLOT(saveClicked())); + + connect(this, + SIGNAL(updateRunButton(bool, std::string const &, QString const &, + QString const &)), + this, + SLOT(updateRunButton(bool, std::string const &, QString const &, + QString const &))); } //---------------------------------------------------------------------------------------------- @@ -131,19 +143,33 @@ void IndirectSqw::run() { m_batchAlgoRunner->executeBatch(); } +MatrixWorkspace_const_sptr +IndirectSqw::getADSWorkspace(std::string const &name) const { + return AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(name); +} + +std::size_t IndirectSqw::getOutWsNumberOfSpectra() const { + return getADSWorkspace(m_pythonExportWsName)->getNumberHistograms(); +} + /** * Handles plotting the S(Q, w) workspace when the algorithm chain is finished. * * @param error If the algorithm chain failed */ void IndirectSqw::sqwAlgDone(bool error) { - if (error) - return; + if (!error) { + setPlotSpectrumEnabled(true); + setPlotContourEnabled(true); + setSaveEnabled(true); - // Enable save and plot - m_uiForm.pbPlot->setEnabled(true); - m_uiForm.pbSave->setEnabled(true); - m_uiForm.cbPlotType->setEnabled(true); + setPlotSpectrumIndexMax(static_cast<int>(getOutWsNumberOfSpectra()) - 1); + } +} + +void IndirectSqw::setPlotSpectrumIndexMax(int maximum) { + MantidQt::API::SignalBlocker<QObject> blocker(m_uiForm.spSpectrum); + m_uiForm.spSpectrum->setMaximum(maximum); } /** @@ -177,31 +203,84 @@ void IndirectSqw::plotContour() { } } -/** - * Handles mantid plotting - */ -void IndirectSqw::plotClicked() { - QString plotType = m_uiForm.cbPlotType->currentText(); - if (plotType == "Contour" && - (checkADSForPlotSaveWorkspace(m_pythonExportWsName, true))) +void IndirectSqw::runClicked() { runTab(); } + +void IndirectSqw::plotSpectrumClicked() { + setPlotSpectrumIsPlotting(true); + + auto const spectrumNumber = m_uiForm.spSpectrum->text().toInt(); + if (checkADSForPlotSaveWorkspace(m_pythonExportWsName, true)) + plotSpectrum(QString::fromStdString(m_pythonExportWsName), spectrumNumber); + + setPlotSpectrumIsPlotting(false); +} + +void IndirectSqw::plotContourClicked() { + setPlotContourIsPlotting(true); + + if (checkADSForPlotSaveWorkspace(m_pythonExportWsName, true)) plot2D(QString::fromStdString(m_pythonExportWsName)); - else if (plotType == "Spectra" && - (checkADSForPlotSaveWorkspace(m_pythonExportWsName, true))) { - auto ws = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>( - m_pythonExportWsName); - int numHist = static_cast<int>(ws->getNumberHistograms()); - plotSpectrum(QString::fromStdString(m_pythonExportWsName), 0, numHist - 1); - } + setPlotContourIsPlotting(false); } -/** - * Handles saving of workspaces - */ void IndirectSqw::saveClicked() { if (checkADSForPlotSaveWorkspace(m_pythonExportWsName, false)) addSaveWorkspaceToQueue(QString::fromStdString(m_pythonExportWsName)); m_batchAlgoRunner->executeBatch(); } + +void IndirectSqw::setRunEnabled(bool enabled) { + m_uiForm.pbRun->setEnabled(enabled); +} + +void IndirectSqw::setPlotSpectrumEnabled(bool enabled) { + m_uiForm.pbPlotSpectrum->setEnabled(enabled); + m_uiForm.spSpectrum->setEnabled(enabled); +} + +void IndirectSqw::setPlotContourEnabled(bool enabled) { + m_uiForm.pbPlotContour->setEnabled(enabled); +} + +void IndirectSqw::setSaveEnabled(bool enabled) { + m_uiForm.pbSave->setEnabled(enabled); +} + +void IndirectSqw::setOutputButtonsEnabled( + std::string const &enableOutputButtons) { + bool enable = enableOutputButtons == "enable" ? true : false; + setPlotSpectrumEnabled(enable); + setPlotContourEnabled(enable); + setSaveEnabled(enable); +} + +void IndirectSqw::updateRunButton(bool enabled, + std::string const &enableOutputButtons, + QString const message, + QString const tooltip) { + setRunEnabled(enabled); + m_uiForm.pbRun->setText(message); + m_uiForm.pbRun->setToolTip(tooltip); + if (enableOutputButtons != "unchanged") + setOutputButtonsEnabled(enableOutputButtons); +} + +void IndirectSqw::setPlotSpectrumIsPlotting(bool plotting) { + m_uiForm.pbPlotSpectrum->setText(plotting ? "Plotting..." : "Plot Spectrum"); + setPlotSpectrumEnabled(!plotting); + setPlotContourEnabled(!plotting); + setRunEnabled(!plotting); + setSaveEnabled(!plotting); +} + +void IndirectSqw::setPlotContourIsPlotting(bool plotting) { + m_uiForm.pbPlotContour->setText(plotting ? "Plotting..." : "Plot Contour"); + setPlotSpectrumEnabled(!plotting); + setPlotContourEnabled(!plotting); + setRunEnabled(!plotting); + setSaveEnabled(!plotting); +} + } // namespace CustomInterfaces } // namespace MantidQt diff --git a/qt/scientific_interfaces/Indirect/IndirectSqw.h b/qt/scientific_interfaces/Indirect/IndirectSqw.h index ea84dd6f4b981136b7e69d249cb951f0260971e7..14a5126f3e80ac8f2458d7e19da94061a2fdee4c 100644 --- a/qt/scientific_interfaces/Indirect/IndirectSqw.h +++ b/qt/scientific_interfaces/Indirect/IndirectSqw.h @@ -33,10 +33,32 @@ public: private slots: void plotContour(); void sqwAlgDone(bool error); - void plotClicked(); + + void runClicked(); + void plotSpectrumClicked(); + void plotContourClicked(); void saveClicked(); + void updateRunButton(bool enabled = true, + std::string const &enableOutputButtons = "unchanged", + QString const message = "Run", + QString const tooltip = ""); + private: + Mantid::API::MatrixWorkspace_const_sptr + getADSWorkspace(std::string const &name) const; + std::size_t getOutWsNumberOfSpectra() const; + + void setPlotSpectrumIndexMax(int maximum); + + void setRunEnabled(bool enabled); + void setPlotSpectrumEnabled(bool enabled); + void setPlotContourEnabled(bool enabled); + void setSaveEnabled(bool enabled); + void setOutputButtonsEnabled(std::string const &enableOutputButtons); + void setPlotSpectrumIsPlotting(bool plotting); + void setPlotContourIsPlotting(bool plotting); + Ui::IndirectSqw m_uiForm; }; } // namespace CustomInterfaces diff --git a/qt/scientific_interfaces/Indirect/IndirectSqw.ui b/qt/scientific_interfaces/Indirect/IndirectSqw.ui index 015e9896790716824eb69426aff52a3bb0051aaf..64fdd7dc5efffb5a1ce49d3d329edc23f6c75ce7 100644 --- a/qt/scientific_interfaces/Indirect/IndirectSqw.ui +++ b/qt/scientific_interfaces/Indirect/IndirectSqw.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>601</width> - <height>268</height> + <height>303</height> </rect> </property> <property name="minimumSize"> @@ -349,8 +349,92 @@ </layout> </widget> </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>45</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>45</height> + </size> + </property> + <property name="title"> + <string>Run</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>7</number> + </property> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>198</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbRun"> + <property name="minimumSize"> + <size> + <width>150</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>180</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>197</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> <item> <widget class="QGroupBox" name="sqw_outputOptions"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>56</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>56</height> + </size> + </property> <property name="title"> <string>Output</string> </property> @@ -358,46 +442,34 @@ <item> <widget class="QLabel" name="lbPlotOutput"> <property name="text"> - <string>Plot Output:</string> + <string>Plot Spectrum:</string> </property> </widget> </item> <item> - <widget class="QComboBox" name="cbPlotType"> + <widget class="QSpinBox" name="spSpectrum"> <property name="enabled"> <bool>false</bool> </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + </widget> + </item> + <item> + <widget class="QPushButton" name="pbPlotSpectrum"> + <property name="enabled"> + <bool>false</bool> </property> - <property name="minimumSize"> - <size> - <width>150</width> - <height>0</height> - </size> + <property name="text"> + <string>Plot Spectrum</string> </property> - <item> - <property name="text"> - <string>Contour</string> - </property> - </item> - <item> - <property name="text"> - <string>Spectra</string> - </property> - </item> </widget> </item> <item> - <widget class="QPushButton" name="pbPlot"> + <widget class="QPushButton" name="pbPlotContour"> <property name="enabled"> <bool>false</bool> </property> <property name="text"> - <string>Plot</string> + <string>Plot Contour</string> </property> </widget> </item> @@ -444,7 +516,6 @@ <tabstop>spELow</tabstop> <tabstop>spEWidth</tabstop> <tabstop>spEHigh</tabstop> - <tabstop>cbPlotType</tabstop> <tabstop>pbSave</tabstop> </tabstops> <resources/> diff --git a/qt/scientific_interfaces/Indirect/IndirectSymmetrise.cpp b/qt/scientific_interfaces/Indirect/IndirectSymmetrise.cpp index 016e482c94fa72c8ce4683cbea6fc45bd6855c6a..deb3033f408686fab7f2039d8ccafbfca09f7788 100644 --- a/qt/scientific_interfaces/Indirect/IndirectSymmetrise.cpp +++ b/qt/scientific_interfaces/Indirect/IndirectSymmetrise.cpp @@ -137,10 +137,18 @@ IndirectSymmetrise::IndirectSymmetrise(IndirectDataReduction *idrUI, SLOT(xRangeMinChanged(double))); connect(negativeERaw, SIGNAL(maxValueChanged(double)), this, SLOT(xRangeMaxChanged(double))); - // Handle plotting and saving + // Handle running, plotting and saving + connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); connect(m_uiForm.pbSave, SIGNAL(clicked()), this, SLOT(saveClicked())); connect(m_uiForm.pbPlot, SIGNAL(clicked()), this, SLOT(plotClicked())); + connect(this, + SIGNAL(updateRunButton(bool, std::string const &, QString const &, + QString const &)), + this, + SLOT(updateRunButton(bool, std::string const &, QString const &, + QString const &))); + // Set default X range values m_dblManager->setValue(m_properties["EMin"], 0.1); m_dblManager->setValue(m_properties["EMax"], 0.5); @@ -520,15 +528,22 @@ void IndirectSymmetrise::xRangeMaxChanged(double value) { m_dblManager->setValue(m_properties["EMin"], std::abs(value)); } } + +/** + * Handle when Run is clicked + */ +void IndirectSymmetrise::runClicked() { runTab(); } + /** * Handles mantid plotting */ void IndirectSymmetrise::plotClicked() { - + setPlotIsPlotting(true); QStringList workspaces; workspaces.append(m_uiForm.dsInput->getCurrentDataName()); workspaces.append(QString::fromStdString(m_pythonExportWsName)); plotSpectrum(workspaces); + setPlotIsPlotting(false); } /** @@ -538,5 +553,43 @@ void IndirectSymmetrise::saveClicked() { if (checkADSForPlotSaveWorkspace(m_pythonExportWsName, false)) plotSpectrum(QString::fromStdString(m_pythonExportWsName)); } + +void IndirectSymmetrise::setRunEnabled(bool enabled) { + m_uiForm.pbRun->setEnabled(enabled); +} + +void IndirectSymmetrise::setPlotEnabled(bool enabled) { + m_uiForm.pbPlot->setEnabled(enabled); +} + +void IndirectSymmetrise::setSaveEnabled(bool enabled) { + m_uiForm.pbSave->setEnabled(enabled); +} + +void IndirectSymmetrise::setOutputButtonsEnabled( + std::string const &enableOutputButtons) { + bool enable = enableOutputButtons == "enable" ? true : false; + setPlotEnabled(enable); + setSaveEnabled(enable); +} + +void IndirectSymmetrise::updateRunButton(bool enabled, + std::string const &enableOutputButtons, + QString const message, + QString const tooltip) { + setRunEnabled(enabled); + m_uiForm.pbRun->setText(message); + m_uiForm.pbRun->setToolTip(tooltip); + if (enableOutputButtons != "unchanged") + setOutputButtonsEnabled(enableOutputButtons); +} + +void IndirectSymmetrise::setPlotIsPlotting(bool plotting) { + m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot Result"); + setPlotEnabled(!plotting); + setRunEnabled(!plotting); + setSaveEnabled(!plotting); +} + } // namespace CustomInterfaces } // namespace MantidQt diff --git a/qt/scientific_interfaces/Indirect/IndirectSymmetrise.h b/qt/scientific_interfaces/Indirect/IndirectSymmetrise.h index 4e8ef86672a6f0470974ca57df3bd712b2631a4b..2863af4ba71f674f59e3f6454083ba6cb82b754b 100644 --- a/qt/scientific_interfaces/Indirect/IndirectSymmetrise.h +++ b/qt/scientific_interfaces/Indirect/IndirectSymmetrise.h @@ -60,9 +60,21 @@ private slots: void previewAlgDone(bool error); void xRangeMaxChanged(double value); void xRangeMinChanged(double value); + + void runClicked(); void plotClicked(); void saveClicked(); + void setRunEnabled(bool enabled); + void setPlotEnabled(bool enabled); + void setSaveEnabled(bool enabled); + void setOutputButtonsEnabled(std::string const &enableOutputButtons); + void updateRunButton(bool enabled = true, + std::string const &enableOutputButtons = "unchanged", + QString const message = "Run", + QString const tooltip = ""); + void setPlotIsPlotting(bool plotting); + private: Ui::IndirectSymmetrise m_uiForm; }; diff --git a/qt/scientific_interfaces/Indirect/IndirectSymmetrise.ui b/qt/scientific_interfaces/Indirect/IndirectSymmetrise.ui index ff5f01ff9e2f4b11852570aa36775b46c1780318..e852648f04a57ee270599f114d7d0efeae915e9f 100644 --- a/qt/scientific_interfaces/Indirect/IndirectSymmetrise.ui +++ b/qt/scientific_interfaces/Indirect/IndirectSymmetrise.ui @@ -108,13 +108,82 @@ </layout> </widget> </item> + <item> + <widget class="QGroupBox" name="gbRun"> + <property name="title"> + <string>Run</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>7</number> + </property> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>185</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbRun"> + <property name="minimumSize"> + <size> + <width>150</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>180</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>184</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> <item> <widget class="QGroupBox" name="gbOutput"> <property name="title"> <string>Output</string> </property> <layout class="QHBoxLayout" name="horizontalLayout1"> - <property name="margin"> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <property name="bottomMargin"> <number>6</number> </property> <item> diff --git a/qt/scientific_interfaces/Indirect/IndirectTransmission.cpp b/qt/scientific_interfaces/Indirect/IndirectTransmission.cpp index 5cf2f333fa93e700fed3ceb041b22e0543ea70ad..aa5b06d17780839692cf40628160b15bf63d37d5 100644 --- a/qt/scientific_interfaces/Indirect/IndirectTransmission.cpp +++ b/qt/scientific_interfaces/Indirect/IndirectTransmission.cpp @@ -32,8 +32,17 @@ IndirectTransmission::IndirectTransmission(IndirectDataReduction *idrUI, SLOT(dataLoaded())); connect(m_uiForm.dsCanInput, SIGNAL(dataReady(QString)), this, SLOT(dataLoaded())); + + connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); connect(m_uiForm.pbPlot, SIGNAL(clicked()), this, SLOT(plotClicked())); connect(m_uiForm.pbSave, SIGNAL(clicked()), this, SLOT(saveClicked())); + + connect(this, + SIGNAL(updateRunButton(bool, std::string const &, QString const &, + QString const &)), + this, + SLOT(updateRunButton(bool, std::string const &, QString const &, + QString const &))); } //---------------------------------------------------------------------------------------------- @@ -133,6 +142,11 @@ void IndirectTransmission::instrumentSet() { m_uiForm.dsCanInput->setInstrumentOverride(instDetails["instrument"]); } +/** + * Handle when Run is clicked + */ +void IndirectTransmission::runClicked() { runTab(); } + /** * Handle saving of workspace */ @@ -149,10 +163,49 @@ void IndirectTransmission::saveClicked() { * Handle mantid plotting */ void IndirectTransmission::plotClicked() { + setPlotIsPlotting(true); QString outputWs = (m_uiForm.dsSampleInput->getCurrentDataName() + "_transmission"); if (checkADSForPlotSaveWorkspace(outputWs.toStdString(), true)) plotSpectrum(outputWs); + setPlotIsPlotting(false); +} + +void IndirectTransmission::setRunEnabled(bool enabled) { + m_uiForm.pbRun->setEnabled(enabled); +} + +void IndirectTransmission::setPlotEnabled(bool enabled) { + m_uiForm.pbPlot->setEnabled(enabled); } + +void IndirectTransmission::setSaveEnabled(bool enabled) { + m_uiForm.pbSave->setEnabled(enabled); +} + +void IndirectTransmission::setOutputButtonsEnabled( + std::string const &enableOutputButtons) { + bool enable = enableOutputButtons == "enable" ? true : false; + setPlotEnabled(enable); + setSaveEnabled(enable); +} + +void IndirectTransmission::updateRunButton( + bool enabled, std::string const &enableOutputButtons, QString const message, + QString const tooltip) { + setRunEnabled(enabled); + m_uiForm.pbRun->setText(message); + m_uiForm.pbRun->setToolTip(tooltip); + if (enableOutputButtons != "unchanged") + setOutputButtonsEnabled(enableOutputButtons); +} + +void IndirectTransmission::setPlotIsPlotting(bool plotting) { + m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot Result"); + setPlotEnabled(!plotting); + setRunEnabled(!plotting); + setSaveEnabled(!plotting); +} + } // namespace CustomInterfaces } // namespace MantidQt diff --git a/qt/scientific_interfaces/Indirect/IndirectTransmission.h b/qt/scientific_interfaces/Indirect/IndirectTransmission.h index 03bc814b8677b3c8f9b4f2e982095858e188e609..a8dc8ee732624364fa944cf55a83a9d89f8b66c8 100644 --- a/qt/scientific_interfaces/Indirect/IndirectTransmission.h +++ b/qt/scientific_interfaces/Indirect/IndirectTransmission.h @@ -39,9 +39,21 @@ private slots: void previewPlot(); void transAlgDone(bool error); void instrumentSet(); + + void runClicked(); void plotClicked(); void saveClicked(); + void setRunEnabled(bool enabled); + void setPlotEnabled(bool enabled); + void setSaveEnabled(bool enabled); + void setOutputButtonsEnabled(std::string const &enableOutputButtons); + void updateRunButton(bool enabled = true, + std::string const &enableOutputButtons = "unchanged", + QString const message = "Run", + QString const tooltip = ""); + void setPlotIsPlotting(bool plotting); + private: Ui::IndirectTransmission m_uiForm; }; diff --git a/qt/scientific_interfaces/Indirect/IndirectTransmission.ui b/qt/scientific_interfaces/Indirect/IndirectTransmission.ui index b409fe4fd0341879cd50830d39c6996d52b56af5..0cf2636f037752ad5ee8bcdca0c1f91cfae04779 100644 --- a/qt/scientific_interfaces/Indirect/IndirectTransmission.ui +++ b/qt/scientific_interfaces/Indirect/IndirectTransmission.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>514</width> - <height>360</height> + <width>525</width> + <height>402</height> </rect> </property> <property name="minimumSize"> @@ -126,6 +126,66 @@ </layout> </widget> </item> + <item> + <widget class="QGroupBox" name="gbRun"> + <property name="title"> + <string>Run</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>7</number> + </property> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>197</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbRun"> + <property name="minimumSize"> + <size> + <width>150</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>180</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>Run</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>197</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> <item> <widget class="QGroupBox" name="gbOutput"> <property name="title"> diff --git a/qt/scientific_interfaces/Indirect/Iqt.cpp b/qt/scientific_interfaces/Indirect/Iqt.cpp index 72f7ae349ca737693016384f58036d3e0e00acc2..5866c91b89de081a776eab23c900c30131cc9468 100644 --- a/qt/scientific_interfaces/Indirect/Iqt.cpp +++ b/qt/scientific_interfaces/Indirect/Iqt.cpp @@ -187,10 +187,10 @@ void Iqt::run() { */ void Iqt::algorithmComplete(bool error) { setRunIsRunning(false); - if (!error) { - setPlotResultEnabled(true); - setTiledPlotEnabled(true); - setSaveResultEnabled(true); + if (error) { + setPlotResultEnabled(false); + setTiledPlotEnabled(false); + setSaveResultEnabled(false); } } /** @@ -478,22 +478,26 @@ void Iqt::setSaveResultEnabled(bool enabled) { m_uiForm.pbSave->setEnabled(enabled); } +void Iqt::setButtonsEnabled(bool enabled) { + setRunEnabled(enabled); + setPlotResultEnabled(enabled); + setSaveResultEnabled(enabled); + setTiledPlotEnabled(enabled); +} + void Iqt::setRunIsRunning(bool running) { m_uiForm.pbRun->setText(running ? "Running..." : "Run"); - setRunEnabled(!running); - setPlotResultEnabled(!running); - setSaveResultEnabled(!running); - setTiledPlotEnabled(!running); + setButtonsEnabled(!running); } void Iqt::setPlotResultIsPlotting(bool plotting) { m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot Result"); - setPlotResultEnabled(!plotting); + setButtonsEnabled(!plotting); } void Iqt::setTiledPlotIsPlotting(bool plotting) { m_uiForm.pbTile->setText(plotting ? "Plotting..." : "Tiled Plot"); - setTiledPlotEnabled(!plotting); + setButtonsEnabled(!plotting); } void Iqt::runClicked() { runTab(); } diff --git a/qt/scientific_interfaces/Indirect/Iqt.h b/qt/scientific_interfaces/Indirect/Iqt.h index 26f8c5e83dd75dd658cfc7d82eab7da242e1dc76..d23248a10445eee0960eb4d8a353d594dc34ec1e 100644 --- a/qt/scientific_interfaces/Indirect/Iqt.h +++ b/qt/scientific_interfaces/Indirect/Iqt.h @@ -31,7 +31,7 @@ private: void setPlotResultEnabled(bool enabled); void setTiledPlotEnabled(bool enabled); void setSaveResultEnabled(bool enabled); - + void setButtonsEnabled(bool enabled); void setRunIsRunning(bool running); void setPlotResultIsPlotting(bool plotting); void setTiledPlotIsPlotting(bool plotting); diff --git a/qt/scientific_interfaces/Indirect/Iqt.ui b/qt/scientific_interfaces/Indirect/Iqt.ui index 994211782a71fba5f972028349c7dbc26a972497..5b9b86651793a63ba54719f029912eb51d7067eb 100644 --- a/qt/scientific_interfaces/Indirect/Iqt.ui +++ b/qt/scientific_interfaces/Indirect/Iqt.ui @@ -195,12 +195,6 @@ <height>0</height> </size> </property> - <property name="frameShape"> - <enum>QFrame::StyledPanel</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> - </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <property name="spacing"> <number>0</number> diff --git a/qt/scientific_interfaces/Indirect/IqtFit.cpp b/qt/scientific_interfaces/Indirect/IqtFit.cpp index f5b089429db78e51985b94965459320caedc281c..c6506b49fd361012ad300148dbf2791629b5d1a6 100644 --- a/qt/scientific_interfaces/Indirect/IqtFit.cpp +++ b/qt/scientific_interfaces/Indirect/IqtFit.cpp @@ -152,17 +152,21 @@ void IqtFit::setSaveResultEnabled(bool enabled) { m_uiForm->pbSave->setEnabled(enabled); } +void IqtFit::setButtonsEnabled(bool enabled) { + setRunEnabled(enabled); + setPlotResultEnabled(enabled); + setSaveResultEnabled(enabled); + setFitSingleSpectrumEnabled(enabled); +} + void IqtFit::setRunIsRunning(bool running) { m_uiForm->pbRun->setText(running ? "Running..." : "Run"); - setRunEnabled(!running); - setPlotResultEnabled(!running); - setSaveResultEnabled(!running); - setFitSingleSpectrumEnabled(!running); + setButtonsEnabled(!running); } void IqtFit::setPlotResultIsPlotting(bool plotting) { m_uiForm->pbPlot->setText(plotting ? "Plotting..." : "Plot"); - setPlotResultEnabled(!plotting); + setButtonsEnabled(!plotting); } void IqtFit::runClicked() { runTab(); } diff --git a/qt/scientific_interfaces/Indirect/IqtFit.h b/qt/scientific_interfaces/Indirect/IqtFit.h index a2a8aa17a8dda8e4624683c6e5d388a706e105a2..8fd5ead60b8bec92a74dd56ac58b53b3b383aee6 100644 --- a/qt/scientific_interfaces/Indirect/IqtFit.h +++ b/qt/scientific_interfaces/Indirect/IqtFit.h @@ -57,6 +57,7 @@ private: void setRunEnabled(bool enabled); void setFitSingleSpectrumEnabled(bool enabled); + void setButtonsEnabled(bool enabled); void setPlotResultIsPlotting(bool plotting); diff --git a/qt/scientific_interfaces/Indirect/IqtFit.ui b/qt/scientific_interfaces/Indirect/IqtFit.ui index f839f4e2bc9255d23eaaafd95b631e30ff8a475e..5ffba688ae1c27aa39c4f8af69dac4844b708098 100644 --- a/qt/scientific_interfaces/Indirect/IqtFit.ui +++ b/qt/scientific_interfaces/Indirect/IqtFit.ui @@ -45,12 +45,6 @@ </item> <item> <widget class="QFrame" name="frame"> - <property name="frameShape"> - <enum>QFrame::StyledPanel</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> - </property> <layout class="QVBoxLayout" name="verticalLayout_3"> <property name="spacing"> <number>0</number> diff --git a/qt/scientific_interfaces/Indirect/JumpFit.cpp b/qt/scientific_interfaces/Indirect/JumpFit.cpp index d989bcd3c688125b679669528acdbb77565452b3..c66bd341e88cdc7c72b7a51595811cedb64020a7 100644 --- a/qt/scientific_interfaces/Indirect/JumpFit.cpp +++ b/qt/scientific_interfaces/Indirect/JumpFit.cpp @@ -49,22 +49,8 @@ void JumpFit::setupFitTab() { setSampleWSSuffices({"_Result"}); setSampleFBSuffices({"_Result.nxs"}); - auto &functionFactory = FunctionFactory::Instance(); - auto chudleyElliot = functionFactory.createFunction("ChudleyElliot"); - auto hallRoss = functionFactory.createFunction("HallRoss"); - auto fickDiffusion = functionFactory.createFunction("FickDiffusion"); - auto teixeiraWater = functionFactory.createFunction("TeixeiraWater"); - auto eisfDiffCylinder = functionFactory.createFunction("EISFDiffCylinder"); - auto eisfDiffSphere = functionFactory.createFunction("EISFDiffSphere"); - auto eisfDiffSphereAklyl = - functionFactory.createFunction("EISFDiffSphereAlkyl"); - addComboBoxFunctionGroup("ChudleyElliot", {chudleyElliot}); - addComboBoxFunctionGroup("HallRoss", {hallRoss}); - addComboBoxFunctionGroup("FickDiffusion", {fickDiffusion}); - addComboBoxFunctionGroup("TeixeiraWater", {teixeiraWater}); - addComboBoxFunctionGroup("EISFDiffCylinder", {eisfDiffCylinder}); - addComboBoxFunctionGroup("EISFDiffSphere", {eisfDiffSphere}); - addComboBoxFunctionGroup("EISFDiffSphereAlkyl", {eisfDiffSphereAklyl}); + addWidthFunctionsToFitTypeComboBox(); + addEISFFunctionsToFitTypeComboBox(); m_uiForm->cbParameter->setEnabled(false); @@ -74,6 +60,43 @@ void JumpFit::setupFitTab() { connect(m_uiForm->pbPlot, SIGNAL(clicked()), this, SLOT(plotClicked())); connect(this, SIGNAL(functionChanged()), this, SLOT(updateModelFitTypeString())); + connect(m_uiForm->cbParameterType, SIGNAL(currentIndexChanged(int)), this, + SLOT(updateParameterFitTypes())); + connect(this, SIGNAL(updateFitTypes()), this, + SLOT(updateParameterFitTypes())); +} + +void JumpFit::addEISFFunctionsToFitTypeComboBox() { + auto &functionFactory = FunctionFactory::Instance(); + auto const eisfDiffCylinder = + functionFactory.createFunction("EISFDiffCylinder"); + auto const eisfDiffSphere = functionFactory.createFunction("EISFDiffSphere"); + auto const eisfDiffSphereAklyl = + functionFactory.createFunction("EISFDiffSphereAlkyl"); + addComboBoxFunctionGroup("EISFDiffCylinder", {eisfDiffCylinder}); + addComboBoxFunctionGroup("EISFDiffSphere", {eisfDiffSphere}); + addComboBoxFunctionGroup("EISFDiffSphereAlkyl", {eisfDiffSphereAklyl}); +} + +void JumpFit::addWidthFunctionsToFitTypeComboBox() { + auto &functionFactory = FunctionFactory::Instance(); + auto const chudleyElliot = functionFactory.createFunction("ChudleyElliot"); + auto const hallRoss = functionFactory.createFunction("HallRoss"); + auto const fickDiffusion = functionFactory.createFunction("FickDiffusion"); + auto const teixeiraWater = functionFactory.createFunction("TeixeiraWater"); + addComboBoxFunctionGroup("ChudleyElliot", {chudleyElliot}); + addComboBoxFunctionGroup("HallRoss", {hallRoss}); + addComboBoxFunctionGroup("FickDiffusion", {fickDiffusion}); + addComboBoxFunctionGroup("TeixeiraWater", {teixeiraWater}); +} + +void JumpFit::updateParameterFitTypes() { + auto const parameter = m_uiForm->cbParameterType->currentText().toStdString(); + clearFitTypeComboBox(); + if (parameter == "EISF") + addEISFFunctionsToFitTypeComboBox(); + else if (parameter == "Width") + addWidthFunctionsToFitTypeComboBox(); } void JumpFit::updateModelFitTypeString() { @@ -114,17 +137,21 @@ void JumpFit::setSaveResultEnabled(bool enabled) { m_uiForm->pbSave->setEnabled(enabled); } +void JumpFit::setButtonsEnabled(bool enabled) { + setRunEnabled(enabled); + setPlotResultEnabled(enabled); + setSaveResultEnabled(enabled); + setFitSingleSpectrumEnabled(enabled); +} + void JumpFit::setRunIsRunning(bool running) { m_uiForm->pbRun->setText(running ? "Running..." : "Run"); - setRunEnabled(!running); - setPlotResultEnabled(!running); - setSaveResultEnabled(!running); - setFitSingleSpectrumEnabled(!running); + setButtonsEnabled(!running); } void JumpFit::setPlotResultIsPlotting(bool plotting) { m_uiForm->pbPlot->setText(plotting ? "Plotting..." : "Plot"); - setPlotResultEnabled(!plotting); + setButtonsEnabled(!plotting); } void JumpFit::runClicked() { runTab(); } diff --git a/qt/scientific_interfaces/Indirect/JumpFit.h b/qt/scientific_interfaces/Indirect/JumpFit.h index 3c7e13a1a02295e19fbf8b207e7652d221063a6a..81742380b129e9c8e384cc34fd01709a87d50197 100644 --- a/qt/scientific_interfaces/Indirect/JumpFit.h +++ b/qt/scientific_interfaces/Indirect/JumpFit.h @@ -39,10 +39,16 @@ protected: void setRunIsRunning(bool running) override; +private slots: + void updateParameterFitTypes(); + private: + void addEISFFunctionsToFitTypeComboBox(); + void addWidthFunctionsToFitTypeComboBox(); + void setRunEnabled(bool enabled); void setFitSingleSpectrumEnabled(bool enabled); - + void setButtonsEnabled(bool enabled); void setPlotResultIsPlotting(bool plotting); JumpFitModel *m_jumpFittingModel; diff --git a/qt/scientific_interfaces/Indirect/JumpFit.ui b/qt/scientific_interfaces/Indirect/JumpFit.ui index 5747dec9b190ed6d200b440ee91884887330e2d0..557bb1e0b1a51f9b50f4e2883f906fe3aedf8716 100644 --- a/qt/scientific_interfaces/Indirect/JumpFit.ui +++ b/qt/scientific_interfaces/Indirect/JumpFit.ui @@ -138,12 +138,6 @@ </item> <item> <widget class="QFrame" name="fResults"> - <property name="frameShape"> - <enum>QFrame::StyledPanel</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> - </property> <layout class="QVBoxLayout" name="verticalLayout_3"> <property name="spacing"> <number>0</number> diff --git a/qt/scientific_interfaces/Indirect/MSDFit.cpp b/qt/scientific_interfaces/Indirect/MSDFit.cpp index b345224ad0cc0082175755f7a5a17f342668a928..9f038d32c7133e11b85933de82c87e52346a5859 100644 --- a/qt/scientific_interfaces/Indirect/MSDFit.cpp +++ b/qt/scientific_interfaces/Indirect/MSDFit.cpp @@ -98,17 +98,21 @@ void MSDFit::setSaveResultEnabled(bool enabled) { m_uiForm->pbSave->setEnabled(enabled); } +void MSDFit::setButtonsEnabled(bool enabled) { + setRunEnabled(enabled); + setPlotResultEnabled(enabled); + setSaveResultEnabled(enabled); + setFitSingleSpectrumEnabled(enabled); +} + void MSDFit::setRunIsRunning(bool running) { m_uiForm->pbRun->setText(running ? "Running..." : "Run"); - setRunEnabled(!running); - setPlotResultEnabled(!running); - setSaveResultEnabled(!running); - setFitSingleSpectrumEnabled(!running); + setButtonsEnabled(!running); } void MSDFit::setPlotResultIsPlotting(bool plotting) { m_uiForm->pbPlot->setText(plotting ? "Plotting..." : "Plot"); - setPlotResultEnabled(!plotting); + setButtonsEnabled(!plotting); } void MSDFit::runClicked() { runTab(); } diff --git a/qt/scientific_interfaces/Indirect/MSDFit.h b/qt/scientific_interfaces/Indirect/MSDFit.h index 5958fe078db11b74e128ea3b517419b2d7ecc02c..7e08dffc71956d82a77f53b54f6afbff7f88eec4 100644 --- a/qt/scientific_interfaces/Indirect/MSDFit.h +++ b/qt/scientific_interfaces/Indirect/MSDFit.h @@ -41,7 +41,7 @@ private: void setRunEnabled(bool enabled); void setFitSingleSpectrumEnabled(bool enabled); - + void setButtonsEnabled(bool enabled); void setPlotResultIsPlotting(bool plotting); MSDFitModel *m_msdFittingModel; diff --git a/qt/scientific_interfaces/Indirect/MSDFit.ui b/qt/scientific_interfaces/Indirect/MSDFit.ui index 596ace916810822fb0a54492d082ead60e946627..05bce87208598f2ad9c0c5cc4f9bb2bb57b5b8bd 100644 --- a/qt/scientific_interfaces/Indirect/MSDFit.ui +++ b/qt/scientific_interfaces/Indirect/MSDFit.ui @@ -42,12 +42,6 @@ </item> <item> <widget class="QFrame" name="frame"> - <property name="frameShape"> - <enum>QFrame::StyledPanel</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> - </property> <layout class="QVBoxLayout" name="verticalLayout_3"> <property name="spacing"> <number>0</number> diff --git a/qt/scientific_interfaces/Indirect/Quasi.cpp b/qt/scientific_interfaces/Indirect/Quasi.cpp index 69ceb393d189b45e3664ed0218bcdf6ea3ba1cb8..7ed13fc675746b643f4b38996d0a40dd5697a57d 100644 --- a/qt/scientific_interfaces/Indirect/Quasi.cpp +++ b/qt/scientific_interfaces/Indirect/Quasi.cpp @@ -71,10 +71,8 @@ Quasi::Quasi(QWidget *parent) : IndirectBayesTab(parent), m_previewSpec(0) { connect(m_uiForm.pbPlotPreview, SIGNAL(clicked()), this, SLOT(plotCurrentPreview())); - // Post saving + connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); connect(m_uiForm.pbSave, SIGNAL(clicked()), this, SLOT(saveClicked())); - - // Post plotting connect(m_uiForm.pbPlot, SIGNAL(clicked()), this, SLOT(plotClicked())); } @@ -146,24 +144,6 @@ bool Quasi::validate() { * Run the BayesQuasi algorithm */ void Quasi::run() { - - auto saveDirectory = Mantid::Kernel::ConfigService::Instance().getString( - "defaultsave.directory"); - if (saveDirectory.compare("") == 0) { - const char *textMessage = - "BayesQuasi requires a default save directory and " - "one is not currently set." - " If run, the algorithm will default to saving files " - "to the current working directory." - " Would you still like to run the algorithm?"; - int result = QMessageBox::question(nullptr, tr("Save Directory"), - tr(textMessage), QMessageBox::Yes, - QMessageBox::No, QMessageBox::NoButton); - if (result == QMessageBox::No) { - return; - } - } - bool elasticPeak = false; bool sequence = false; @@ -173,9 +153,9 @@ void Quasi::run() { bool useResNorm = false; std::string resNormFile(""); - std::string sampleName = + std::string const sampleName = m_uiForm.dsSample->getCurrentDataName().toStdString(); - std::string resName = + std::string const resName = m_uiForm.dsResolution->getCurrentDataName().toStdString(); std::string program = m_uiForm.cbProgram->currentText().toStdString(); @@ -187,7 +167,8 @@ void Quasi::run() { } // Collect input from fit options section - std::string background = m_uiForm.cbBackground->currentText().toStdString(); + std::string const background = + m_uiForm.cbBackground->currentText().toStdString(); if (m_uiForm.chkElasticPeak->isChecked()) { elasticPeak = true; @@ -207,11 +188,11 @@ void Quasi::run() { } // Collect input from the properties browser - double eMin = m_properties["EMin"]->valueText().toDouble(); - double eMax = m_properties["EMax"]->valueText().toDouble(); + double const eMin = m_properties["EMin"]->valueText().toDouble(); + double const eMax = m_properties["EMax"]->valueText().toDouble(); - long sampleBins = m_properties["SampleBinning"]->valueText().toLong(); - long resBins = m_properties["ResBinning"]->valueText().toLong(); + long const sampleBins = m_properties["SampleBinning"]->valueText().toLong(); + long const resBins = m_properties["ResBinning"]->valueText().toLong(); IAlgorithm_sptr runAlg = AlgorithmManager::Instance().create("BayesQuasi"); runAlg->initialize(); @@ -244,13 +225,12 @@ void Quasi::run() { * Enable plotting and saving and fit curves on the mini plot. */ void Quasi::algorithmComplete(bool error) { - if (error) - return; - else { + setRunIsRunning(false); + if (!error) updateMiniPlot(); - m_uiForm.cbPlot->setEnabled(true); - m_uiForm.pbPlot->setEnabled(true); - m_uiForm.pbSave->setEnabled(true); + else { + setPlotResultEnabled(false); + setSaveResultEnabled(false); } } @@ -449,33 +429,69 @@ void Quasi::saveClicked() { QString saveDirectory = QString::fromStdString( Mantid::Kernel::ConfigService::Instance().getString( "defaultsave.directory")); - const auto fitWS = m_QuasiAlg->getPropertyValue("OutputWorkspaceFit"); + auto const fitWS = m_QuasiAlg->getPropertyValue("OutputWorkspaceFit"); IndirectTab::checkADSForPlotSaveWorkspace(fitWS, false); - QString QfitWS = QString::fromStdString(fitWS); - const auto fitPath = saveDirectory + QfitWS + ".nxs"; + QString const QfitWS = QString::fromStdString(fitWS); + auto const fitPath = saveDirectory + QfitWS + ".nxs"; addSaveWorkspaceToQueue(QfitWS, fitPath); - const auto resultWS = m_QuasiAlg->getPropertyValue("OutputWorkspaceResult"); + auto const resultWS = m_QuasiAlg->getPropertyValue("OutputWorkspaceResult"); IndirectTab::checkADSForPlotSaveWorkspace(resultWS, false); - QString QresultWS = QString::fromStdString(resultWS); - const auto resultPath = saveDirectory + QresultWS + ".nxs"; + QString const QresultWS = QString::fromStdString(resultWS); + auto const resultPath = saveDirectory + QresultWS + ".nxs"; addSaveWorkspaceToQueue(QresultWS, resultPath); m_batchAlgoRunner->executeBatchAsync(); } +void Quasi::runClicked() { + if (validateTab()) { + auto const saveDirectory = + Mantid::Kernel::ConfigService::Instance().getString( + "defaultsave.directory"); + displayMessageAndRun(saveDirectory); + } +} + +void Quasi::displayMessageAndRun(std::string const &saveDirectory) { + if (saveDirectory.empty()) { + int const result = displaySaveDirectoryMessage(); + if (result != QMessageBox::No) { + setRunIsRunning(true); + runTab(); + } + } else { + setRunIsRunning(true); + runTab(); + } +} + +int Quasi::displaySaveDirectoryMessage() { + char const *textMessage = + "BayesQuasi requires a default save directory and " + "one is not currently set." + " If run, the algorithm will default to saving files " + "to the current working directory." + " Would you still like to run the algorithm?"; + return QMessageBox::question(nullptr, tr("Save Directory"), tr(textMessage), + QMessageBox::Yes, QMessageBox::No, + QMessageBox::NoButton); +} + /** * Handles plotting the selected plot when plot is clicked */ void Quasi::plotClicked() { + setPlotResultIsPlotting(true); + // Output options - std::string plot = m_uiForm.cbPlot->currentText().toStdString(); - QString program = m_uiForm.cbProgram->currentText(); - const auto resultName = m_QuasiAlg->getPropertyValue("OutputWorkspaceResult"); + std::string const plot = m_uiForm.cbPlot->currentText().toStdString(); + QString const program = m_uiForm.cbProgram->currentText(); + auto const resultName = m_QuasiAlg->getPropertyValue("OutputWorkspaceResult"); if ((plot == "Prob" || plot == "All") && (program == "Lorentzians")) { - const auto probWS = m_QuasiAlg->getPropertyValue("OutputWorkspaceProb"); + auto const probWS = m_QuasiAlg->getPropertyValue("OutputWorkspaceProb"); // Check workspace exists IndirectTab::checkADSForPlotSaveWorkspace(probWS, true); - QString QprobWS = QString::fromStdString(probWS); + QString const QprobWS = QString::fromStdString(probWS); IndirectTab::plotSpectrum(QprobWS, 1, 2); } if (plot == "Fit" || plot == "All") { @@ -483,27 +499,27 @@ void Quasi::plotClicked() { fitName.pop_back(); fitName.append("_0"); IndirectTab::checkADSForPlotSaveWorkspace(fitName, true); - QString QfitWS = QString::fromStdString(fitName); + QString const QfitWS = QString::fromStdString(fitName); if (program == "Lorentzians") IndirectTab::plotSpectra(QfitWS, {0, 1, 2, 4}); else IndirectTab::plotSpectra(QfitWS, {0, 1, 2}); } - MatrixWorkspace_sptr resultWS = + auto const resultWS = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(resultName); - int numSpectra = (int)resultWS->getNumberHistograms(); + int const numSpectra = (int)resultWS->getNumberHistograms(); IndirectTab::checkADSForPlotSaveWorkspace(resultName, true); - QString QresultWS = QString::fromStdString(resultName); - auto paramNames = {"Amplitude", "FWHM", "Beta"}; - for (std::string paramName : paramNames) { + QString const QresultWS = QString::fromStdString(resultName); + auto const paramNames = {"Amplitude", "FWHM", "Beta"}; + for (std::string const ¶mName : paramNames) { if (plot == paramName || plot == "All") { std::vector<int> spectraIndices = {}; for (int i = 0; i < numSpectra; i++) { auto axisLabel = resultWS->getAxis(1)->label(i); - auto found = axisLabel.find(paramName); + auto const found = axisLabel.find(paramName); if (found != std::string::npos) { spectraIndices.push_back(i); @@ -517,6 +533,34 @@ void Quasi::plotClicked() { } } } + setPlotResultIsPlotting(false); +} + +void Quasi::setRunEnabled(bool enabled) { m_uiForm.pbRun->setEnabled(enabled); } + +void Quasi::setPlotResultEnabled(bool enabled) { + m_uiForm.pbPlot->setEnabled(enabled); + m_uiForm.cbPlot->setEnabled(enabled); +} + +void Quasi::setSaveResultEnabled(bool enabled) { + m_uiForm.pbSave->setEnabled(enabled); +} + +void Quasi::setButtonsEnabled(bool enabled) { + setRunEnabled(enabled); + setPlotResultEnabled(enabled); + setSaveResultEnabled(enabled); +} + +void Quasi::setRunIsRunning(bool running) { + m_uiForm.pbRun->setText(running ? "Running..." : "Run"); + setButtonsEnabled(!running); +} + +void Quasi::setPlotResultIsPlotting(bool plotting) { + m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot"); + setButtonsEnabled(!plotting); } } // namespace CustomInterfaces diff --git a/qt/scientific_interfaces/Indirect/Quasi.h b/qt/scientific_interfaces/Indirect/Quasi.h index 02eb29f41491617e5bd4ad10faac7dc9da74905b..a8dd6e8af8aa17e0669963f83c7efe37421caef2 100644 --- a/qt/scientific_interfaces/Indirect/Quasi.h +++ b/qt/scientific_interfaces/Indirect/Quasi.h @@ -44,14 +44,23 @@ private slots: void updateMiniPlot(); /// Handles what happen after the algorithm is run void algorithmComplete(bool error); - // Handles saving of workspace - void saveClicked(); - // Handles plotting + + void runClicked(); void plotClicked(); - // Handles plotting current preview void plotCurrentPreview(); + void saveClicked(); private: + void displayMessageAndRun(std::string const &saveDirectory); + int displaySaveDirectoryMessage(); + + void setRunEnabled(bool enabled); + void setPlotResultEnabled(bool enabled); + void setSaveResultEnabled(bool enabled); + void setButtonsEnabled(bool enabled); + void setRunIsRunning(bool running); + void setPlotResultIsPlotting(bool plotting); + /// Current preview spectrum int m_previewSpec; /// The ui form diff --git a/qt/scientific_interfaces/Indirect/Quasi.ui b/qt/scientific_interfaces/Indirect/Quasi.ui index 1afe4818179401a145e4322ec8396a32e31fed28..e360191b09420430001b07a36f3b9837df53ab9a 100644 --- a/qt/scientific_interfaces/Indirect/Quasi.ui +++ b/qt/scientific_interfaces/Indirect/Quasi.ui @@ -327,89 +327,170 @@ </layout> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_44"> - <item> - <widget class="QGroupBox" name="gbOutput"> - <property name="title"> - <string>Output Options</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QLabel" name="lblPlotResult"> - <property name="text"> - <string>Plot Result: </string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="cbPlot"> - <property name="enabled"> - <bool>false</bool> - </property> - <item> - <property name="text"> - <string>Amplitude</string> + <widget class="QFrame" name="frame"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QGroupBox" name="gbRun"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>45</height> + </size> + </property> + <property name="title"> + <string>Run</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>7</number> + </property> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> - </item> - <item> - <property name="text"> - <string>FWHM</string> + <property name="sizeHint" stdset="0"> + <size> + <width>385</width> + <height>20</height> + </size> </property> - </item> - <item> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbRun"> <property name="text"> - <string>Fit</string> + <string>Run</string> </property> - </item> - <item> - <property name="text"> - <string>Prob</string> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> - </item> - <item> - <property name="text"> - <string>All</string> + <property name="sizeHint" stdset="0"> + <size> + <width>384</width> + <height>20</height> + </size> </property> - </item> - </widget> - </item> - <item> - <widget class="QPushButton" name="pbPlot"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Plot</string> - </property> - </widget> - </item> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_44"> <item> - <spacer name="horizontalSpacer_18"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="pbSave"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Save</string> + <widget class="QGroupBox" name="gbOutput"> + <property name="title"> + <string>Output Options</string> </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="lblPlotResult"> + <property name="text"> + <string>Plot Result: </string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="cbPlot"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>75</width> + <height>0</height> + </size> + </property> + <item> + <property name="text"> + <string>Amplitude</string> + </property> + </item> + <item> + <property name="text"> + <string>FWHM</string> + </property> + </item> + <item> + <property name="text"> + <string>Fit</string> + </property> + </item> + <item> + <property name="text"> + <string>Prob</string> + </property> + </item> + <item> + <property name="text"> + <string>All</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QPushButton" name="pbPlot"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Plot</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_18"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbSave"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Save</string> + </property> + </widget> + </item> + </layout> </widget> </item> </layout> - </widget> - </item> - </layout> + </item> + </layout> + </widget> </item> </layout> </widget> diff --git a/qt/scientific_interfaces/Indirect/ResNorm.cpp b/qt/scientific_interfaces/Indirect/ResNorm.cpp index 1eed5eecbf6b1ca1ca7495637adffb7ed098eacc..3b26d2f5a2be3424ef6d569377e2ed30f3063aca 100644 --- a/qt/scientific_interfaces/Indirect/ResNorm.cpp +++ b/qt/scientific_interfaces/Indirect/ResNorm.cpp @@ -51,6 +51,7 @@ ResNorm::ResNorm(QWidget *parent) : IndirectBayesTab(parent), m_previewSpec(0) { SLOT(handleAlgorithmComplete(bool))); // Post Plot and Save + connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); connect(m_uiForm.pbSave, SIGNAL(clicked()), this, SLOT(saveClicked())); connect(m_uiForm.pbPlot, SIGNAL(clicked()), this, SLOT(plotClicked())); connect(m_uiForm.pbPlotCurrent, SIGNAL(clicked()), this, @@ -153,16 +154,14 @@ void ResNorm::run() { * @param error If the algorithm failed */ void ResNorm::handleAlgorithmComplete(bool error) { - if (error) - return; - - // Enable plot and save - m_uiForm.cbPlot->setEnabled(true); - m_uiForm.pbPlot->setEnabled(true); - m_uiForm.pbSave->setEnabled(true); - - // Update preview plot - previewSpecChanged(m_previewSpec); + setRunIsRunning(false); + if (!error) + // Update preview plot + previewSpecChanged(m_previewSpec); + else { + setPlotResultEnabled(false); + setSaveResultEnabled(false); + } } /** @@ -337,10 +336,16 @@ void ResNorm::plotCurrentPreview() { plotMultipleSpectra(plotWorkspaces, plotIndices); } +void ResNorm::runClicked() { + if (validateTab()) { + setRunIsRunning(true); + runTab(); + } +} + /** * Handles saving when button is clicked */ - void ResNorm::saveClicked() { const auto resWsName(m_uiForm.dsResolution->getCurrentDataName()); @@ -358,8 +363,8 @@ void ResNorm::saveClicked() { /** * Handles plotting when button is clicked */ - void ResNorm::plotClicked() { + setPlotResultIsPlotting(true); WorkspaceGroup_sptr fitWorkspaces = AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>( m_pythonExportWsName + "_Fit_Workspaces"); @@ -377,6 +382,37 @@ void ResNorm::plotClicked() { plotSpectrum(QString::fromStdString(m_pythonExportWsName) + "_Stretch"); if (plotOptions == "Fit" || plotOptions == "All") plotSpectrum(fitWsName, 0, 1); + + setPlotResultIsPlotting(false); +} + +void ResNorm::setRunEnabled(bool enabled) { + m_uiForm.pbRun->setEnabled(enabled); +} + +void ResNorm::setPlotResultEnabled(bool enabled) { + m_uiForm.pbPlot->setEnabled(enabled); + m_uiForm.cbPlot->setEnabled(enabled); +} + +void ResNorm::setSaveResultEnabled(bool enabled) { + m_uiForm.pbSave->setEnabled(enabled); +} + +void ResNorm::setButtonsEnabled(bool enabled) { + setRunEnabled(enabled); + setPlotResultEnabled(enabled); + setSaveResultEnabled(enabled); +} + +void ResNorm::setRunIsRunning(bool running) { + m_uiForm.pbRun->setText(running ? "Running..." : "Run"); + setButtonsEnabled(!running); +} + +void ResNorm::setPlotResultIsPlotting(bool plotting) { + m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot"); + setButtonsEnabled(!plotting); } } // namespace CustomInterfaces diff --git a/qt/scientific_interfaces/Indirect/ResNorm.h b/qt/scientific_interfaces/Indirect/ResNorm.h index ed7ebdecfd746d350dc28a99b1df00c52ad0656f..2def3ba0697f04e94fdec0108afa02388b6fecbe 100644 --- a/qt/scientific_interfaces/Indirect/ResNorm.h +++ b/qt/scientific_interfaces/Indirect/ResNorm.h @@ -41,11 +41,19 @@ private slots: /// Slot to handle the preview spectrum being changed void previewSpecChanged(int value); /// Slots to handle plot and save + void runClicked(); void saveClicked(); void plotClicked(); void plotCurrentPreview(); private: + void setRunEnabled(bool enabled); + void setPlotResultEnabled(bool enabled); + void setSaveResultEnabled(bool enabled); + void setButtonsEnabled(bool enabled); + void setRunIsRunning(bool running); + void setPlotResultIsPlotting(bool plotting); + /// Current preview spectrum int m_previewSpec; /// The ui form diff --git a/qt/scientific_interfaces/Indirect/ResNorm.ui b/qt/scientific_interfaces/Indirect/ResNorm.ui index 177527f671bcc1310aea81ff411c4cb5e77d12c5..b7b1bbdc8af40a388cf8fcf05c1b445f0ff7ed72 100644 --- a/qt/scientific_interfaces/Indirect/ResNorm.ui +++ b/qt/scientific_interfaces/Indirect/ResNorm.ui @@ -154,79 +154,160 @@ </layout> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_44"> - <item> - <widget class="QGroupBox" name="gbOutput"> - <property name="title"> - <string>Output Options</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QLabel" name="lblPlot"> - <property name="text"> - <string>Plot Result: </string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="cbPlot"> - <property name="enabled"> - <bool>false</bool> - </property> - <item> - <property name="text"> - <string>Intensity</string> + <widget class="QFrame" name="frame"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QGroupBox" name="gbRun"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>45</height> + </size> + </property> + <property name="title"> + <string>Run</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>7</number> + </property> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> - </item> - <item> - <property name="text"> - <string>Stretch</string> + <property name="sizeHint" stdset="0"> + <size> + <width>385</width> + <height>20</height> + </size> </property> - </item> - <item> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbRun"> <property name="text"> - <string>All</string> + <string>Run</string> </property> - </item> - </widget> - </item> - <item> - <widget class="QPushButton" name="pbPlot"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Plot</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_18"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>384</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_44"> <item> - <widget class="QPushButton" name="pbSave"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Save</string> + <widget class="QGroupBox" name="gbOutput"> + <property name="title"> + <string>Output Options</string> </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="lblPlot"> + <property name="text"> + <string>Plot Result: </string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="cbPlot"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>75</width> + <height>0</height> + </size> + </property> + <item> + <property name="text"> + <string>Intensity</string> + </property> + </item> + <item> + <property name="text"> + <string>Stretch</string> + </property> + </item> + <item> + <property name="text"> + <string>All</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QPushButton" name="pbPlot"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Plot</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_18"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbSave"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Save</string> + </property> + </widget> + </item> + </layout> </widget> </item> </layout> - </widget> - </item> - </layout> + </item> + </layout> + </widget> </item> </layout> </widget> diff --git a/qt/scientific_interfaces/Indirect/Stretch.cpp b/qt/scientific_interfaces/Indirect/Stretch.cpp index 4dbea7b9948cf1d34b6a9f66b9e45291dd278cff..73b9aa9d1d70cd3582abd574c27714e994745c01 100644 --- a/qt/scientific_interfaces/Indirect/Stretch.cpp +++ b/qt/scientific_interfaces/Indirect/Stretch.cpp @@ -71,6 +71,7 @@ Stretch::Stretch(QWidget *parent) m_uiForm.spPreviewSpectrum->setMaximum(0); // Connect the plot and save push buttons + connect(m_uiForm.pbRun, SIGNAL(clicked()), this, SLOT(runClicked())); connect(m_uiForm.pbPlot, SIGNAL(clicked()), this, SLOT(plotWorkspaces())); connect(m_uiForm.pbSave, SIGNAL(clicked()), this, SLOT(saveWorkspaces())); connect(m_uiForm.pbPlotPreview, SIGNAL(clicked()), this, @@ -105,47 +106,30 @@ bool Stretch::validate() { void Stretch::run() { // Workspace input - const auto sampleName = m_uiForm.dsSample->getCurrentDataName().toStdString(); - const auto resName = + auto const sampleName = m_uiForm.dsSample->getCurrentDataName().toStdString(); + auto const resName = m_uiForm.dsResolution->getCurrentDataName().toStdString(); - auto saveDirectory = Mantid::Kernel::ConfigService::Instance().getString( - "defaultsave.directory"); - if (saveDirectory.compare("") == 0) { - const char *textMessage = - "BayesStretch requires a default save directory and " - "one is not currently set." - " If run, the algorithm will default to saving files " - "to the current working directory." - " Would you still like to run the algorithm?"; - int result = QMessageBox::question(nullptr, tr("Save Directory"), - tr(textMessage), QMessageBox::Yes, - QMessageBox::No, QMessageBox::NoButton); - if (result == QMessageBox::No) { - return; - } - } - // Obtain save and plot state m_plotType = m_uiForm.cbPlot->currentText().toStdString(); // Collect input from options section - const auto background = m_uiForm.cbBackground->currentText().toStdString(); + auto const background = m_uiForm.cbBackground->currentText().toStdString(); // Collect input from the properties browser - const auto eMin = m_properties["EMin"]->valueText().toDouble(); - const auto eMax = m_properties["EMax"]->valueText().toDouble(); - const auto beta = m_properties["Beta"]->valueText().toLong(); - const auto sigma = m_properties["Sigma"]->valueText().toLong(); - const auto nBins = m_properties["SampleBinning"]->valueText().toLong(); + auto const eMin = m_properties["EMin"]->valueText().toDouble(); + auto const eMax = m_properties["EMax"]->valueText().toDouble(); + auto const beta = m_properties["Beta"]->valueText().toLong(); + auto const sigma = m_properties["Sigma"]->valueText().toLong(); + auto const nBins = m_properties["SampleBinning"]->valueText().toLong(); // Bool options - const auto elasticPeak = m_uiForm.chkElasticPeak->isChecked(); - const auto sequence = m_uiForm.chkSequentialFit->isChecked(); + auto const elasticPeak = m_uiForm.chkElasticPeak->isChecked(); + auto const sequence = m_uiForm.chkSequentialFit->isChecked(); // Construct OutputNames - auto cutIndex = sampleName.find_last_of("_"); - auto baseName = sampleName.substr(0, cutIndex); + auto const cutIndex = sampleName.find_last_of("_"); + auto const baseName = sampleName.substr(0, cutIndex); m_fitWorkspaceName = baseName + "_Stretch_Fit"; m_contourWorkspaceName = baseName + "_Stretch_Contour"; @@ -169,7 +153,6 @@ void Stretch::run() { SLOT(algorithmComplete(bool))); m_batchAlgoRunner->executeBatchAsync(); - m_uiForm.cbPlot->setEnabled(true); m_plotType = m_uiForm.cbPlot->currentText().toStdString(); } @@ -180,13 +163,11 @@ void Stretch::algorithmComplete(const bool &error) { disconnect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this, SLOT(algorithmComplete(bool))); - if (error) - return; - - // Enables plot and save - m_uiForm.cbPlot->setEnabled(true); - m_uiForm.pbPlot->setEnabled(true); - m_uiForm.pbSave->setEnabled(true); + setRunIsRunning(false); + if (error) { + setPlotResultEnabled(false); + setSaveResultEnabled(false); + } } /** @@ -212,11 +193,45 @@ void Stretch::saveWorkspaces() { m_batchAlgoRunner->executeBatchAsync(); } +void Stretch::runClicked() { + if (validateTab()) { + auto const saveDirectory = + Mantid::Kernel::ConfigService::Instance().getString( + "defaultsave.directory"); + displayMessageAndRun(saveDirectory); + } +} + +void Stretch::displayMessageAndRun(std::string const &saveDirectory) { + if (saveDirectory.empty()) { + int const result = displaySaveDirectoryMessage(); + if (result != QMessageBox::No) { + setRunIsRunning(true); + runTab(); + } + } else { + setRunIsRunning(true); + runTab(); + } +} + +int Stretch::displaySaveDirectoryMessage() { + char const *textMessage = + "BayesQuasi requires a default save directory and " + "one is not currently set." + " If run, the algorithm will default to saving files " + "to the current working directory." + " Would you still like to run the algorithm?"; + return QMessageBox::question(nullptr, tr("Save Directory"), tr(textMessage), + QMessageBox::Yes, QMessageBox::No, + QMessageBox::NoButton); +} + /** * Handles the plotting of workspace post algorithm completion */ void Stretch::plotWorkspaces() { - + setPlotResultIsPlotting(true); WorkspaceGroup_sptr fitWorkspace; fitWorkspace = AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>( m_fitWorkspaceName); @@ -246,6 +261,7 @@ void Stretch::plotWorkspaces() { g_log.error( "Beta and Sigma workspace were not found and could not be plotted."); } + setPlotResultIsPlotting(false); } /** @@ -349,5 +365,34 @@ void Stretch::updateProperties(QtProperty *prop, double val) { } } +void Stretch::setRunEnabled(bool enabled) { + m_uiForm.pbRun->setEnabled(enabled); +} + +void Stretch::setPlotResultEnabled(bool enabled) { + m_uiForm.pbPlot->setEnabled(enabled); + m_uiForm.cbPlot->setEnabled(enabled); +} + +void Stretch::setSaveResultEnabled(bool enabled) { + m_uiForm.pbSave->setEnabled(enabled); +} + +void Stretch::setButtonsEnabled(bool enabled) { + setRunEnabled(enabled); + setPlotResultEnabled(enabled); + setSaveResultEnabled(enabled); +} + +void Stretch::setRunIsRunning(bool running) { + m_uiForm.pbRun->setText(running ? "Running..." : "Run"); + setButtonsEnabled(!running); +} + +void Stretch::setPlotResultIsPlotting(bool plotting) { + m_uiForm.pbPlot->setText(plotting ? "Plotting..." : "Plot"); + setButtonsEnabled(!plotting); +} + } // namespace CustomInterfaces } // namespace MantidQt diff --git a/qt/scientific_interfaces/Indirect/Stretch.h b/qt/scientific_interfaces/Indirect/Stretch.h index 81d61e78c412aa85262bec726b6f2e810b33d9bb..99d6a2746b6d88e14e6e649bbd07af5747266a37 100644 --- a/qt/scientific_interfaces/Indirect/Stretch.h +++ b/qt/scientific_interfaces/Indirect/Stretch.h @@ -37,13 +37,24 @@ private slots: void handleSampleInputReady(const QString &filename); /// Save the workspaces produces from the algorithm void saveWorkspaces(); - /// Plot the workspaces specified by the interface + + void runClicked(); void plotWorkspaces(); void algorithmComplete(const bool &error); void plotCurrentPreview(); void previewSpecChanged(int value); private: + void displayMessageAndRun(std::string const &saveDirectory); + int displaySaveDirectoryMessage(); + + void setRunEnabled(bool enabled); + void setPlotResultEnabled(bool enabled); + void setSaveResultEnabled(bool enabled); + void setButtonsEnabled(bool enabled); + void setRunIsRunning(bool running); + void setPlotResultIsPlotting(bool plotting); + /// Current preview spectrum int m_previewSpec; // The ui form diff --git a/qt/scientific_interfaces/Indirect/Stretch.ui b/qt/scientific_interfaces/Indirect/Stretch.ui index 4f406ffc16f03973689fbe909e4d8b8acb127ab3..79420a4ca60214dfceb9722c3576a9909e647c29 100644 --- a/qt/scientific_interfaces/Indirect/Stretch.ui +++ b/qt/scientific_interfaces/Indirect/Stretch.ui @@ -223,79 +223,160 @@ </layout> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_44"> - <item> - <widget class="QGroupBox" name="gbOutput"> - <property name="title"> - <string>Output Options</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QLabel" name="lblPlotResult"> - <property name="text"> - <string>Plot Result: </string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="cbPlot"> - <property name="enabled"> - <bool>false</bool> - </property> - <item> - <property name="text"> - <string>Sigma</string> + <widget class="QFrame" name="frame"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QGroupBox" name="gbRun"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>45</height> + </size> + </property> + <property name="title"> + <string>Run</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>7</number> + </property> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> - </item> - <item> - <property name="text"> - <string>Beta</string> + <property name="sizeHint" stdset="0"> + <size> + <width>385</width> + <height>20</height> + </size> </property> - </item> - <item> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbRun"> <property name="text"> - <string>All</string> + <string>Run</string> </property> - </item> - </widget> - </item> - <item> - <widget class="QPushButton" name="pbPlot"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Plot</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_18"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>384</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_44"> <item> - <widget class="QPushButton" name="pbSave"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Save Result</string> + <widget class="QGroupBox" name="gbOutput"> + <property name="title"> + <string>Output Options</string> </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="lblPlotResult"> + <property name="text"> + <string>Plot Result: </string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="cbPlot"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>75</width> + <height>0</height> + </size> + </property> + <item> + <property name="text"> + <string>Sigma</string> + </property> + </item> + <item> + <property name="text"> + <string>Beta</string> + </property> + </item> + <item> + <property name="text"> + <string>All</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QPushButton" name="pbPlot"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Plot</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_18"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pbSave"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Save Result</string> + </property> + </widget> + </item> + </layout> </widget> </item> </layout> - </widget> - </item> - </layout> + </item> + </layout> + </widget> </item> </layout> </widget> diff --git a/qt/scientific_interfaces/Indirect/test/CMakeLists.txt b/qt/scientific_interfaces/Indirect/test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..d90a6e4b033257ac23cb73607d4d39fa291930f8 --- /dev/null +++ b/qt/scientific_interfaces/Indirect/test/CMakeLists.txt @@ -0,0 +1,40 @@ +########################################################################### +# Testing +########################################################################### +set ( TEST_FILES + IndirectFitDataTest.h + IndirectFitOutputTest.h + IndirectFitPlotModelTest.h + IndirectFittingModelTest.h +) + +mtd_add_qt_tests (TARGET_NAME MantidQtInterfacesIndirectTest + QT_VERSION 4 + SRC ${TEST_FILES} + INCLUDE_DIRS + ../../../../Framework/CurveFitting/inc + ../../../../Framework/DataObjects/inc + ../../../../Framework/TestHelpers/inc + ../ + TEST_HELPER_SRCS + ../../../../Framework/TestHelpers/src/ComponentCreationHelper.cpp + ../../../../Framework/TestHelpers/src/IndirectFitDataCreationHelper.cpp + ../../../../Framework/TestHelpers/src/InstrumentCreationHelper.cpp + ../../../../Framework/TestHelpers/src/WorkspaceCreationHelper.cpp + ../../../../Framework/TestHelpers/src/TearDownWorld.cpp + LINK_LIBS + ${TCMALLOC_LIBRARIES_LINKTIME} + ${CORE_MANTIDLIBS} + CurveFitting + DataObjects + ${GMOCK_LIBRARIES} + ${GTEST_LIBRARIES} + ${POCO_LIBRARIES} + ${Boost_LIBRARIES} + QT4_LINK_LIBS + Qwt5 + MTD_QT_LINK_LIBS + MantidScientificInterfacesIndirect + PARENT_DEPENDENCIES + GUITests +) diff --git a/qt/scientific_interfaces/Indirect/test/IndirectFitDataTest.h b/qt/scientific_interfaces/Indirect/test/IndirectFitDataTest.h new file mode 100644 index 0000000000000000000000000000000000000000..68348bf503539085fc25761c28d5be53126abcb8 --- /dev/null +++ b/qt/scientific_interfaces/Indirect/test/IndirectFitDataTest.h @@ -0,0 +1,465 @@ +#ifndef MANTID_INDIRECTFITDATATEST_H_ +#define MANTID_INDIRECTFITDATATEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "IndirectFitData.h" +#include "MantidAPI/MatrixWorkspace.h" +#include "MantidDataObjects/Workspace2D.h" +#include "MantidTestHelpers/IndirectFitDataCreationHelper.h" +#include "MantidTestHelpers/WorkspaceCreationHelper.h" + +#include <iostream> + +using namespace Mantid::API; +using namespace Mantid::DataObjects; +using namespace MantidQt::CustomInterfaces::IDA; +using namespace Mantid::IndirectFitDataCreationHelper; + +namespace { + +std::unique_ptr<IndirectFitData> +getIndirectFitData(int const &numberOfSpectra) { + auto const workspace = createWorkspace(numberOfSpectra); + Spectra const spec = std::make_pair(0u, workspace->getNumberHistograms() - 1); + IndirectFitData data(workspace, spec); + return std::make_unique<IndirectFitData>(data); +} + +} // namespace + +class IndirectFitDataTest : public CxxTest::TestSuite { +public: + static IndirectFitDataTest *createSuite() { + return new IndirectFitDataTest(); + } + + static void destroySuite(IndirectFitDataTest *suite) { delete suite; } + + void tearDown() override { AnalysisDataService::Instance().clear(); } + + void test_data_is_instantiated() { + auto const workspace = createWorkspace(1); + Spectra const spec = + std::make_pair(0u, workspace->getNumberHistograms() - 1); + + workspace->setTitle("Test Title"); + IndirectFitData const data(workspace, spec); + + TS_ASSERT_EQUALS(data.workspace(), workspace); + TS_ASSERT_EQUALS(data.workspace()->getTitle(), "Test Title"); + TS_ASSERT_EQUALS(data.workspace()->getNumberHistograms(), 1); + } + + void test_that_DiscontinuousSpectra_is_set_up_correctly() { + DiscontinuousSpectra<std::size_t> const spectra = + DiscontinuousSpectra<std::size_t>("0-5,8,10"); + + std::string const spectraString = "0-5,8,10"; + std::vector<std::size_t> const spectraVec{0, 1, 2, 3, 4, 5, 8, 10}; + + TS_ASSERT_EQUALS(spectra.getString(), spectraString); + for (auto it = spectra.begin(); it < spectra.end(); ++it) + TS_ASSERT_EQUALS(*it, spectraVec[it - spectra.begin()]); + } + + void + test_that_DiscontinuousSpectra_is_sorted_before_being_stored_when_the_input_string_contains_overlapping_spectra() { + auto data = getIndirectFitData(11); + + std::string const inputString = "8,0-7,6,10"; + Spectra const spectra = DiscontinuousSpectra<std::size_t>("0-8,10"); + data->setSpectra(inputString); + + TS_ASSERT( + boost::apply_visitor(AreSpectraEqual(), data->spectra(), spectra)); + } + + void + test_that_DiscontinuousSpectra_is_sorted_before_being_stored_when_the_input_string_contains_an_invalid_spectra_range() { + auto data = getIndirectFitData(11); + + std::string const inputString = "1,2,4-3,10"; + Spectra const spectra = DiscontinuousSpectra<std::size_t>("1-4,10"); + data->setSpectra(inputString); + + TS_ASSERT( + boost::apply_visitor(AreSpectraEqual(), data->spectra(), spectra)); + } + + void + test_that_DiscontinuousSpectra_is_sorted_before_being_stored_when_the_input_string_contains_large_spaces() { + auto data = getIndirectFitData(11); + + std::string const inputString = " 8,10, 7"; + Spectra const spectra = DiscontinuousSpectra<std::size_t>("7-8,10"); + data->setSpectra(inputString); + + TS_ASSERT( + boost::apply_visitor(AreSpectraEqual(), data->spectra(), spectra)); + } + + void test_data_is_stored_in_the_ADS() { + auto const data = getIndirectFitData(1); + SetUpADSWithWorkspace ads("WorkspaceName", data->workspace()); + + TS_ASSERT(ads.doesExist("WorkspaceName")); + auto workspace = ads.retrieveWorkspace("WorkspaceName"); + TS_ASSERT_EQUALS(workspace->getNumberHistograms(), 1); + } + + void + test_displayName_returns_a_valid_name_when_provided_a_rangeDelimiter_and_spectrum_number() { + auto const data = getIndirectFitData(1); + + std::vector<std::string> const formatStrings{ + "%1%_s%2%_Result", "%1%_f%2%,s%2%_Parameter", "%1%_s%2%_Parameter"}; + std::string const rangeDelimiter = "_to_"; + std::size_t const spectrum = 1; + + TS_ASSERT_EQUALS(data->displayName(formatStrings[0], rangeDelimiter), + "_s0_Result"); + TS_ASSERT_EQUALS(data->displayName(formatStrings[1], rangeDelimiter), + "_f0+s0_Parameter"); + TS_ASSERT_EQUALS(data->displayName(formatStrings[2], spectrum), + "_s1_Parameter"); + } + + void test_displayName_removes_red_part_of_a_workspace_name() { + auto const data = getIndirectFitData(1); + + SetUpADSWithWorkspace ads("Workspace_3456_red", data->workspace()); + std::string const formatString = "%1%_s%2%_Result"; + std::string const rangeDelimiter = "_to_"; + + TS_ASSERT_EQUALS(data->displayName(formatString, rangeDelimiter), + "Workspace_3456_s0_Result"); + } + + void + test_that_the_number_of_spectra_returned_matches_the_instantiated_value() { + auto const data = getIndirectFitData(10); + TS_ASSERT_EQUALS(data->numberOfSpectra(), 10); + } + + void test_that_getSpectrum_returns_the_expected_spectrum_numbers() { + auto const data = getIndirectFitData(4); + + for (auto i = 0u; i < data->numberOfSpectra(); ++i) { + std::size_t const spectrumNum = data->getSpectrum(i); + TS_ASSERT_EQUALS(spectrumNum, i); + } + } + + void + test_that_true_is_returned_from_zeroSpectra_if_data_contains_empty_workspace() { + auto workspace = boost::make_shared<Workspace2D>(); + Spectra const spec = std::make_pair(0u, 0u); + IndirectFitData const data(workspace, spec); + + TS_ASSERT_EQUALS(data.zeroSpectra(), true); + } + + void + test_that_true_is_returned_from_zeroSpectra_if_data_contains_empty_spectra() { + auto const workspace = createWorkspace(1); + DiscontinuousSpectra<std::size_t> const spec(""); + IndirectFitData const data(workspace, spec); + + TS_ASSERT_EQUALS(data.zeroSpectra(), true); + } + + void + test_that_false_is_returned_from_zeroSpectra_if_data_contains_one_or_more_spectra() { + for (auto i = 1u; i < 10; ++i) { + auto const data = getIndirectFitData(i); + TS_ASSERT_EQUALS(data->zeroSpectra(), false); + } + } + + void + test_that_correct_excludeRegion_is_returned_when_regions_are_in_correct_order() { + /// When each pair of numbers in the string are in order, then the whole + /// string is in the correct order(unordered: 10,11 9,7 ordered:10,11,7,9) + auto data = getIndirectFitData(4); + + data->setExcludeRegionString("1,8", 0); + data->setExcludeRegionString("2,5", 1); + data->setExcludeRegionString("1,2,5,6,3,4", 2); + + TS_ASSERT_EQUALS(data->getExcludeRegion(0), "1.0,8.0"); + TS_ASSERT_EQUALS(data->getExcludeRegion(1), "2.0,5.0"); + TS_ASSERT_EQUALS(data->getExcludeRegion(2), "1.0,2.0,5.0,6.0,3.0,4.0"); + TS_ASSERT_EQUALS(data->getExcludeRegion(3), ""); + } + + void + test_that_correct_excludeRegionVector_is_returned_when_regions_are_in_correct_order() { + /// When each pair of numbers in the string are in order, then the whole + /// string is in the correct order(unordered: 10,11 9,7 ordered:10,11,7,9) + auto data = getIndirectFitData(4); + + data->setExcludeRegionString("1,8", 0); + data->setExcludeRegionString("2,5", 1); + std::vector<double> const regionVector1{1.0, 8.0}; + std::vector<double> const regionVector2{2.0, 5.0}; + + TS_ASSERT_EQUALS(data->excludeRegionsVector(0), regionVector1); + TS_ASSERT_EQUALS(data->excludeRegionsVector(1), regionVector2); + TS_ASSERT_EQUALS(data->excludeRegionsVector(3).empty(), true); + } + + void test_that_excludeRegion_pairs_are_stored_in_an_order_of_low_to_high() { + /// Example: unordered: 10,11 9,7 ordered: 10,11,7,9 + auto data = getIndirectFitData(3); + + data->setExcludeRegionString("6,2", 0); + data->setExcludeRegionString("6,2,1,2,3,4,7,6", 1); + data->setExcludeRegionString("1,2,2,3,20,18,21,22,7,8", 2); + + std::vector<double> const regionVector{2.0, 6.0}; + + TS_ASSERT_EQUALS(data->getExcludeRegion(0), "2.0,6.0"); + TS_ASSERT_EQUALS(data->getExcludeRegion(1), + "2.0,6.0,1.0,2.0,3.0,4.0,6.0,7.0"); + TS_ASSERT_EQUALS(data->getExcludeRegion(2), + "1.0,2.0,2.0,3.0,18.0,20.0,21.0,22.0,7.0,8.0"); + TS_ASSERT_EQUALS(data->excludeRegionsVector(0), regionVector); + } + + void + test_that_excludeRegion_is_stored_without_spaces_when_there_are_many_spaces_in_input_string() { + auto data = getIndirectFitData(3); + + data->setExcludeRegionString(" 6, 2", 0); + data->setExcludeRegionString("6, 2,1 ,2, 3,4 ,7,6", 1); + data->setExcludeRegionString("1,2 ,2,3, 20, 18,21,22,7, 8 ", 2); + + TS_ASSERT_EQUALS(data->getExcludeRegion(0), "2.0,6.0"); + TS_ASSERT_EQUALS(data->getExcludeRegion(1), + "2.0,6.0,1.0,2.0,3.0,4.0,6.0,7.0"); + TS_ASSERT_EQUALS(data->getExcludeRegion(2), + "1.0,2.0,2.0,3.0,18.0,20.0,21.0,22.0,7.0,8.0"); + } + + void + test_that_setExcludeRegion_rounds_the_numbers_in_the_input_string_to_the_appropriate_decimal_place() { + auto data = getIndirectFitData(2); + + data->setExcludeRegionString("6.29,2.93", 0); + data->setExcludeRegionString("2.6,2.3,1.99,3.01", 1); + + TS_ASSERT_EQUALS(data->getExcludeRegion(0), "2.9,6.3"); + TS_ASSERT_EQUALS(data->getExcludeRegion(1), "2.3,2.6,2.0,3.0"); + } + + void test_throws_when_setSpectra_is_provided_an_out_of_range_spectra() { + auto data = getIndirectFitData(10); + + std::vector<Spectra> const spectraPairs{ + std::make_pair(0u, 11u), std::make_pair(0u, 1000000000000000000u), + std::make_pair(10u, 10u)}; + std::vector<std::string> const spectraStrings{ + "10", "100000000000000000000000000000", "1,5,10", "1,2,3,4,5,6,22"}; + + for (auto i = 0u; i < spectraPairs.size(); ++i) + TS_ASSERT_THROWS(data->setSpectra(spectraPairs[i]), std::runtime_error); + for (auto i = 0u; i < spectraStrings.size(); ++i) + TS_ASSERT_THROWS(data->setSpectra(spectraStrings[i]), std::runtime_error); + } + + void test_no_throw_when_setSpectra_is_provided_a_valid_spectra() { + auto data = getIndirectFitData(10); + + std::vector<Spectra> const spectraPairs{ + std::make_pair(0u, 9u), std::make_pair(4u, 4u), std::make_pair(7u, 4u)}; + std::vector<std::string> const spectraStrings{"0", "9", "0,9,6,4,5", + "1,2,3,4,5,6"}; + + for (auto i = 0u; i < spectraPairs.size(); ++i) + TS_ASSERT_THROWS_NOTHING(data->setSpectra(spectraPairs[i])); + for (auto i = 0u; i < spectraStrings.size(); ++i) + TS_ASSERT_THROWS_NOTHING(data->setSpectra(spectraStrings[i])); + } + + void + test_no_throw_when_setStartX_is_provided_a_valid_xValue_and_spectrum_number() { + auto data = getIndirectFitData(10); + + TS_ASSERT_THROWS_NOTHING(data->setStartX(0.0, 0)); + TS_ASSERT_THROWS_NOTHING(data->setStartX(-5.0, 0)); + TS_ASSERT_THROWS_NOTHING(data->setStartX(5000000, 10)); + } + + void test_the_provided_startX_is_stored_in_range_after_using_setStartX() { + auto data = getIndirectFitData(3); + + data->setStartX(-5.0, 0); + data->setStartX(6.53, 1); + data->setStartX(100000000000000.0, 2); + + TS_ASSERT_EQUALS(data->getRange(0).first, -5.0); + TS_ASSERT_EQUALS(data->getRange(1).first, 6.53); + TS_ASSERT_EQUALS(data->getRange(2).first, 100000000000000.0); + } + + void + test_no_throw_when_setEndX_is_provided_a_valid_xValue_and_spectrum_number() { + auto data = getIndirectFitData(10); + + TS_ASSERT_THROWS_NOTHING(data->setStartX(0.0, 0)); + TS_ASSERT_THROWS_NOTHING(data->setStartX(-5.0, 0)); + TS_ASSERT_THROWS_NOTHING(data->setStartX(5000000, 10)); + } + + void test_the_provided_endX_is_stored_in_range_after_using_setEndX() { + auto data = getIndirectFitData(3); + + data->setEndX(-5.0, 0); + data->setEndX(6.53, 1); + data->setEndX(100000000000000.0, 2); + + TS_ASSERT_EQUALS(data->getRange(0).second, -5.0); + TS_ASSERT_EQUALS(data->getRange(1).second, 6.53); + TS_ASSERT_EQUALS(data->getRange(2).second, 100000000000000.0); + } + + void + test_that_the_startX_of_two_data_sets_are_combined_when_relating_to_two_seperate_spectra() { + auto data1 = getIndirectFitData(2); + auto data2 = getIndirectFitData(2); + + data1->setStartX(6.53, 0); + data2->setStartX(5.0, 1); + auto const combinedData = data2->combine(*data1); + + TS_ASSERT_EQUALS(combinedData.getRange(0).first, 6.53); + TS_ASSERT_EQUALS(combinedData.getRange(1).first, 5.0); + } + + void + test_that_the_endX_of_two_datasets_are_combined_when_relating_to_two_seperate_spectra() { + auto data1 = getIndirectFitData(2); + auto data2 = getIndirectFitData(2); + + data1->setEndX(2.34, 0); + data2->setEndX(5.9, 1); + auto const combinedData = data2->combine(*data1); + + TS_ASSERT_EQUALS(combinedData.getRange(0).second, 2.34); + TS_ASSERT_EQUALS(combinedData.getRange(1).second, 5.9); + } + + void + test_that_the_excludeRegion_of_two_datasets_are_combined_when_relating_to_two_seperate_spectra() { + auto data1 = getIndirectFitData(2); + auto data2 = getIndirectFitData(2); + + data1->setExcludeRegionString("1,2,6,5", 0); + data1->setExcludeRegionString("6,2", 1); + auto const combinedData = data2->combine(*data1); + + TS_ASSERT_EQUALS(combinedData.getExcludeRegion(0), "1.0,2.0,5.0,6.0"); + TS_ASSERT_EQUALS(combinedData.getExcludeRegion(1), "2.0,6.0"); + } + + void + test_that_the_spectra_pair_of_two_datasets_are_combined_correctly_when_spectra_do_not_overlap() { + auto data1 = getIndirectFitData(10); + auto data2 = getIndirectFitData(10); + + data1->setSpectra(std::make_pair(0u, 4u)); + data2->setSpectra(std::make_pair(5u, 9u)); + auto const combinedData = data2->combine(*data1); + Spectra const spec(std::make_pair(0u, 9u)); + + TS_ASSERT( + boost::apply_visitor(AreSpectraEqual(), combinedData.spectra(), spec)); + } + + void + test_that_the_spectra_pair_of_two_datasets_are_combined_correctly_when_spectra_are_discontinuous() { + auto data1 = getIndirectFitData(10); + auto data2 = getIndirectFitData(10); + + data1->setSpectra(std::make_pair(0u, 4u)); + data2->setSpectra(std::make_pair(8u, 9u)); + auto const combinedData = data2->combine(*data1); + Spectra const spec(DiscontinuousSpectra<std::size_t>("0-4,8-9")); + + TS_ASSERT( + boost::apply_visitor(AreSpectraEqual(), combinedData.spectra(), spec)); + } + + void + test_that_the_spectra_pair_of_two_datasets_are_combined_correctly_when_spectra_overlap() { + auto data1 = getIndirectFitData(10); + auto data2 = getIndirectFitData(10); + + data1->setSpectra(std::make_pair(0u, 8u)); + data2->setSpectra(std::make_pair(4u, 9u)); + auto const combinedData = data2->combine(*data1); + Spectra const spec(DiscontinuousSpectra<std::size_t>("0-9")); + + TS_ASSERT( + boost::apply_visitor(AreSpectraEqual(), combinedData.spectra(), spec)); + } + + void + test_that_the_DiscontinuousSpectra_of_two_datasets_are_combined_correctly_when_spectra_do_not_overlap() { + auto data1 = getIndirectFitData(10); + auto data2 = getIndirectFitData(10); + + data1->setSpectra(DiscontinuousSpectra<std::size_t>("0-4")); + data2->setSpectra(DiscontinuousSpectra<std::size_t>("5-9")); + auto const combinedData = data2->combine(*data1); + Spectra const spec(DiscontinuousSpectra<std::size_t>("0-9")); + + TS_ASSERT( + boost::apply_visitor(AreSpectraEqual(), combinedData.spectra(), spec)); + } + + void + test_that_the_DiscontinuousSpectra_of_two_datasets_are_combined_correctly_when_spectra_overlap() { + auto data1 = getIndirectFitData(10); + auto data2 = getIndirectFitData(10); + + data1->setSpectra(DiscontinuousSpectra<std::size_t>("0-7")); + data2->setSpectra(DiscontinuousSpectra<std::size_t>("2-9")); + auto const combinedData = data2->combine(*data1); + Spectra const spec(DiscontinuousSpectra<std::size_t>("0-9")); + + TS_ASSERT( + boost::apply_visitor(AreSpectraEqual(), combinedData.spectra(), spec)); + } + + void + test_that_a_Spectra_pair_and_DiscontinuousSpectra_dataset_are_combined_correctly_when_spectra_do_not_overlap() { + auto data1 = getIndirectFitData(10); + auto data2 = getIndirectFitData(10); + + data1->setSpectra(DiscontinuousSpectra<std::size_t>("0-4")); + data2->setSpectra(std::make_pair(5u, 9u)); + auto const combinedData = data2->combine(*data1); + Spectra const spec(DiscontinuousSpectra<std::size_t>("0-9")); + + TS_ASSERT( + boost::apply_visitor(AreSpectraEqual(), combinedData.spectra(), spec)); + } + + void + test_that_a_Spectra_pair_and_DiscontinuousSpectra_dataset_are_combined_correctly_when_spectra_overlap() { + auto data1 = getIndirectFitData(10); + auto data2 = getIndirectFitData(10); + + data1->setSpectra(DiscontinuousSpectra<std::size_t>("0-7")); + data2->setSpectra(std::make_pair(4u, 9u)); + auto const combinedData = data2->combine(*data1); + Spectra const spec(DiscontinuousSpectra<std::size_t>("0-9")); + + TS_ASSERT( + boost::apply_visitor(AreSpectraEqual(), combinedData.spectra(), spec)); + } +}; + +#endif diff --git a/qt/scientific_interfaces/Indirect/test/IndirectFitOutputTest.h b/qt/scientific_interfaces/Indirect/test/IndirectFitOutputTest.h new file mode 100644 index 0000000000000000000000000000000000000000..6cb93ee3c83e5d07119bf73ba41fc57e1a50ebd1 --- /dev/null +++ b/qt/scientific_interfaces/Indirect/test/IndirectFitOutputTest.h @@ -0,0 +1,355 @@ +#ifndef MANTID_INDIRECTFITOUTPUTTEST_H_ +#define MANTID_INDIRECTFITOUTPUTTEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "IndirectFitOutput.h" +#include "MantidAPI/AlgorithmManager.h" +#include "MantidAPI/FrameworkManager.h" +#include "MantidAPI/ITableWorkspace.h" +#include "MantidAPI/MatrixWorkspace.h" +#include "MantidAPI/TableRow.h" +#include "MantidAPI/WorkspaceFactory.h" +#include "MantidAPI/WorkspaceGroup.h" +#include "MantidTestHelpers/IndirectFitDataCreationHelper.h" + +using namespace Mantid::API; +using namespace Mantid::IndirectFitDataCreationHelper; +using namespace MantidQt::CustomInterfaces::IDA; + +namespace { + +MatrixWorkspace_sptr +createPopulatedworkspace(std::vector<double> const &xValues, + std::vector<double> const &yValues, + int const numberOfSpectra, + std::vector<std::string> const &verticalAxisNames) { + auto createWorkspaceAlgorithm = + AlgorithmManager::Instance().createUnmanaged("CreateWorkspace"); + createWorkspaceAlgorithm->initialize(); + createWorkspaceAlgorithm->setChild(true); + createWorkspaceAlgorithm->setLogging(false); + createWorkspaceAlgorithm->setProperty("DataX", xValues); + createWorkspaceAlgorithm->setProperty("DataY", yValues); + createWorkspaceAlgorithm->setProperty("NSpec", numberOfSpectra); + createWorkspaceAlgorithm->setProperty("VerticalAxisUnit", "Text"); + createWorkspaceAlgorithm->setProperty("VerticalAxisValues", + verticalAxisNames); + createWorkspaceAlgorithm->setProperty("OutputWorkspace", "workspace"); + createWorkspaceAlgorithm->execute(); + return createWorkspaceAlgorithm->getProperty("OutputWorkspace"); +} + +MatrixWorkspace_sptr createPopulatedworkspace(int const &numberOfSpectra) { + std::vector<double> xValues{1.0, 2.0, 3.0, 4.0, 5.0}; + std::vector<double> yValues{1.0, 2.0, 3.0, 4.0, 5.0}; + std::vector<std::string> const verticalAxisNames{ + "Height", "Height_Err", "Msd", "Msd_Err", "Chi_squared"}; + return createPopulatedworkspace(xValues, yValues, numberOfSpectra, + verticalAxisNames); +} + +IndirectFitData getIndirectFitData(int const &numberOfSpectra) { + auto const workspace = createWorkspace(numberOfSpectra); + Spectra const spec = std::make_pair(0u, workspace->getNumberHistograms() - 1); + IndirectFitData data(workspace, spec); + return data; +} + +ITableWorkspace_sptr getEmptyTableWorkspace() { + auto table = WorkspaceFactory::Instance().createTable(); + std::vector<std::string> columnHeadings{"Height", "Height_Err", "Msd", + "Msd_Err", "Chi_squared"}; + for (auto i = 0u; i < columnHeadings.size(); ++i) + table->addColumn("double", columnHeadings[i]); + return table; +} + +ITableWorkspace_sptr getPopulatedTable(std::size_t const &size) { + auto table = getEmptyTableWorkspace(); + for (auto i = 0u; i < size; ++i) { + TableRow row = table->appendRow(); + row << 14.675 << 0.047 << 0.001 << 0.514 << 0.0149; + } + return table; +} + +WorkspaceGroup_sptr getPopulatedGroup(std::size_t const &size) { + WorkspaceGroup_sptr group = boost::make_shared<WorkspaceGroup>(); + for (auto i = 0u; i < size; ++i) + group->addWorkspace(createPopulatedworkspace(5)); + return group; +} + +/// Store workspaces in ADS and won't destruct the ADS when leaving scope +void storeWorkspacesInADS(WorkspaceGroup_sptr group, + ITableWorkspace_sptr table) { + SetUpADSWithWorkspace ads("ResultGroup", group); + ads.addOrReplace("ResultWorkspaces", group); + ads.addOrReplace("ParameterTable", table); +} + +std::unique_ptr<IndirectFitOutput> +createFitOutput(WorkspaceGroup_sptr resultGroup, + ITableWorkspace_sptr parameterTable, + WorkspaceGroup_sptr resultWorkspace, IndirectFitData *fitData, + std::size_t spectrum) { + return std::make_unique<IndirectFitOutput>( + resultGroup, parameterTable, resultWorkspace, fitData, spectrum); +} + +/// This will return fit output with workspaces still stored in the ADS +std::unique_ptr<IndirectFitOutput> getFitOutputData() { + auto const group = getPopulatedGroup(2); + auto const table = getPopulatedTable(2); + IndirectFitData *data = new IndirectFitData(getIndirectFitData(5)); + + storeWorkspacesInADS(group, table); + + return createFitOutput(group, table, group, data, 0); +} + +std::unordered_map<std::string, std::string> +getNewParameterNames(std::vector<std::string> const ¤tNames) { + std::unordered_map<std::string, std::string> newParameterNames; + newParameterNames[currentNames[0]] = "Width_Err"; + newParameterNames[currentNames[1]] = "MSD_Err"; + return newParameterNames; +} + +} // namespace + +class IndirectFitOutputTest : public CxxTest::TestSuite { +public: + /// WorkflowAlgorithms do not appear in the FrameworkManager without this line + IndirectFitOutputTest() { FrameworkManager::Instance(); } + + static IndirectFitOutputTest *createSuite() { + return new IndirectFitOutputTest(); + } + + static void destroySuite(IndirectFitOutputTest *suite) { delete suite; } + + void tearDown() override { AnalysisDataService::Instance().clear(); } + + void + test_that_IndirectFitOutput_constructor_will_set_the_values_of_the_output_data() { + auto const output = getFitOutputData(); + + TS_ASSERT(output->getLastResultGroup()); + TS_ASSERT(output->getLastResultWorkspace()); + TS_ASSERT_EQUALS(output->getLastResultGroup()->getNumberOfEntries(), 2); + TS_ASSERT_EQUALS(output->getLastResultWorkspace()->getNumberOfEntries(), 2); + TS_ASSERT_EQUALS(output->getResultParameterNames().size(), 5); + } + + void + test_that_the_group_workspaces_stored_are_equal_to_the_workspaces_inputed() { + auto const group = getPopulatedGroup(2); + auto const table = getPopulatedTable(2); + IndirectFitData *data = new IndirectFitData(getIndirectFitData(5)); + storeWorkspacesInADS(group, table); + + auto const output = createFitOutput(group, table, group, data, 0); + + TS_ASSERT_EQUALS(output->getLastResultGroup(), group); + TS_ASSERT_EQUALS(output->getLastResultWorkspace(), group); + } + + void + test_that_isSpectrumFit_returns_false_if_the_spectrum_has_not_been_previously_fit() { + auto const group = getPopulatedGroup(2); + auto const table = getPopulatedTable(2); + IndirectFitData *data = new IndirectFitData(getIndirectFitData(5)); + storeWorkspacesInADS(group, table); + + auto const output = createFitOutput(group, table, group, data, 0); + + TS_ASSERT(!output->isSpectrumFit(data, 7)); + } + + void + test_that_isSpectrumFit_returns_true_if_the_spectrum_has_been_previously_fit() { + auto const group = getPopulatedGroup(2); + auto const table = getPopulatedTable(2); + IndirectFitData *data = new IndirectFitData(getIndirectFitData(5)); + storeWorkspacesInADS(group, table); + + auto const output = createFitOutput(group, table, group, data, 0); + + TS_ASSERT(output->isSpectrumFit(data, 0)); + } + + void + test_that_getParameters_returns_an_empty_map_when_the_spectrum_number_provided_is_out_of_range() { + auto const group = getPopulatedGroup(2); + auto const table = getPopulatedTable(2); + IndirectFitData *data = new IndirectFitData(getIndirectFitData(5)); + storeWorkspacesInADS(group, table); + + auto const output = createFitOutput(group, table, group, data, 0); + + TS_ASSERT(output->getParameters(data, 7).empty()); + } + + void + test_that_getParameters_returns_the_correct_parameter_values_when_the_spectrum_number_and_IndirectFitData_provided_is_valid() { + auto const group = getPopulatedGroup(2); + auto const table = getPopulatedTable(2); + IndirectFitData *data = new IndirectFitData(getIndirectFitData(5)); + storeWorkspacesInADS(group, table); + + auto const output = createFitOutput(group, table, group, data, 0); + + auto const parameters = output->getParameters(data, 0); + TS_ASSERT_EQUALS(parameters.size(), 2); + TS_ASSERT_EQUALS(parameters.at("Height_Err").value, 0.047); + TS_ASSERT_EQUALS(parameters.at("Msd_Err").value, 0.514); + } + + void + test_that_getResultLocation_returns_none_when_the_spectrum_number_provided_is_out_of_range() { + auto const group = getPopulatedGroup(2); + auto const table = getPopulatedTable(2); + IndirectFitData *data = new IndirectFitData(getIndirectFitData(5)); + storeWorkspacesInADS(group, table); + + auto const output = createFitOutput(group, table, group, data, 0); + + TS_ASSERT(!output->getResultLocation(data, 7)); + } + + void + test_that_getResultLocation_returns_the_ResultLocation_when_the_spectrum_number_and_IndirectFitData_provided_is_valid() { + auto const group = getPopulatedGroup(2); + auto const table = getPopulatedTable(2); + IndirectFitData *data = new IndirectFitData(getIndirectFitData(5)); + storeWorkspacesInADS(group, table); + + auto const output = createFitOutput(group, table, group, data, 0); + + auto const resultLocation = output->getResultLocation(data, 0); + TS_ASSERT(resultLocation); + TS_ASSERT_EQUALS(resultLocation->result.lock(), group); + } + + void + test_that_getResultParameterNames_gets_the_parameter_names_which_were_provided_as_input_data() { + auto const group = getPopulatedGroup(2); + auto const table = getPopulatedTable(2); + IndirectFitData *data = new IndirectFitData(getIndirectFitData(5)); + storeWorkspacesInADS(group, table); + + auto const output = createFitOutput(group, table, group, data, 0); + std::vector<std::string> const expectedParameters{ + "Height", "Height_Err", "Msd", "Msd_Err", "Chi_squared"}; + auto const parameters = output->getResultParameterNames(); + + TS_ASSERT_EQUALS(parameters.size(), 5); + for (auto i = 0u; i < parameters.size(); ++i) + TS_ASSERT_EQUALS(parameters[i], expectedParameters[i]); + } + + void + test_that_getResultParameterNames_returns_an_empty_vector_if_the_result_workspace_cannot_be_found() { + /// The fact that the result workspace has been removed from the ADS means + /// the parameter names won't be available any longer. + auto const output = getFitOutputData(); + + AnalysisDataService::Instance().clear(); + + TS_ASSERT(output->getResultParameterNames().empty()); + } + + void + test_that_mapParameterNames_will_remap_the_parameters_to_correspond_to_the_provided_parameter_names() { + auto const group = getPopulatedGroup(2); + auto const table = getPopulatedTable(2); + IndirectFitData *data = new IndirectFitData(getIndirectFitData(5)); + storeWorkspacesInADS(group, table); + + auto const output = createFitOutput(group, table, group, data, 0); + auto const newParameterNames = + getNewParameterNames({"Height_Err", "Msd_Err"}); + output->mapParameterNames(newParameterNames, data); + + auto const parameters = output->getParameters(data, 0); + TS_ASSERT_EQUALS(parameters.size(), 2); + TS_ASSERT_EQUALS(parameters.at("Width_Err").value, 0.047); + TS_ASSERT_EQUALS(parameters.at("MSD_Err").value, 0.514); + } + + void + test_that_mapParameterNames_will_not_remap_the_parameters_when_the_provided_old_parameter_names_do_not_exist() { + auto const group = getPopulatedGroup(2); + auto const table = getPopulatedTable(2); + IndirectFitData *data = new IndirectFitData(getIndirectFitData(5)); + storeWorkspacesInADS(group, table); + + auto const output = createFitOutput(group, table, group, data, 0); + auto const newParameterNames = getNewParameterNames({"None1", "None2"}); + output->mapParameterNames(newParameterNames, data); + + auto const parameters = output->getParameters(data, 0); + TS_ASSERT(parameters.at("Height_Err").value); + TS_ASSERT(parameters.at("Msd_Err").value); + } + + void + test_that_addOutput_will_add_new_fitData_without_overwriting_existing_data() { + auto const group = getPopulatedGroup(2); + auto const table = getPopulatedTable(2); + IndirectFitData *data1 = new IndirectFitData(getIndirectFitData(5)); + storeWorkspacesInADS(group, table); + + auto const output = createFitOutput(group, table, group, data1, 0); + IndirectFitData const *data2 = new IndirectFitData(getIndirectFitData(2)); + output->addOutput(group, table, group, data2, 0); + + TS_ASSERT(!output->getParameters(data1, 0).empty()); + TS_ASSERT(!output->getParameters(data2, 0).empty()); + } + + void test_that_removeOutput_will_erase_the_provided_fitData() { + auto const group = getPopulatedGroup(2); + auto const table = getPopulatedTable(2); + IndirectFitData *data = new IndirectFitData(getIndirectFitData(5)); + storeWorkspacesInADS(group, table); + + auto const output = createFitOutput(group, table, group, data, 0); + output->removeOutput(data); + + TS_ASSERT(output->getParameters(data, 0).empty()); + TS_ASSERT(!output->getResultLocation(data, 0)); + } + + void test_that_removeOutput_will_not_delete_fitData_which_is_not_specified() { + auto const group = getPopulatedGroup(2); + auto const table = getPopulatedTable(2); + IndirectFitData *data1 = new IndirectFitData(getIndirectFitData(5)); + storeWorkspacesInADS(group, table); + + auto const output = createFitOutput(group, table, group, data1, 0); + IndirectFitData const *data2 = new IndirectFitData(getIndirectFitData(2)); + output->addOutput(group, table, group, data2, 0); + output->removeOutput(data2); + + TS_ASSERT(!output->getParameters(data1, 0).empty()); + TS_ASSERT(output->getParameters(data2, 0).empty()); + } + + void + test_that_removeOutput_does_not_throw_when_provided_fitData_which_does_not_exist() { + auto const group = getPopulatedGroup(2); + auto const table = getPopulatedTable(2); + IndirectFitData *data1 = new IndirectFitData(getIndirectFitData(5)); + storeWorkspacesInADS(group, table); + + auto const output = createFitOutput(group, table, group, data1, 0); + IndirectFitData const *data2 = new IndirectFitData(getIndirectFitData(2)); + + TS_ASSERT_THROWS_NOTHING(output->removeOutput(data2)); + } +}; + +#endif // MANTID_INDIRECTFITOUTPUTTEST_H diff --git a/qt/scientific_interfaces/Indirect/test/IndirectFitPlotModelTest.h b/qt/scientific_interfaces/Indirect/test/IndirectFitPlotModelTest.h new file mode 100644 index 0000000000000000000000000000000000000000..8b501d3f8d28f22c452c4642d5fc047b600b50fc --- /dev/null +++ b/qt/scientific_interfaces/Indirect/test/IndirectFitPlotModelTest.h @@ -0,0 +1,419 @@ +#ifndef MANTID_INDIRECTFITPLOTMODELTEST_H_ +#define MANTID_INDIRECTFITPLOTMODELTEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "IndirectFitPlotModel.h" +#include "MantidAPI/FrameworkManager.h" +#include "MantidAPI/FunctionFactory.h" +#include "MantidAPI/TextAxis.h" +#include "MantidCurveFitting/Algorithms/ConvolutionFit.h" +#include "MantidCurveFitting/Algorithms/QENSFitSequential.h" +#include "MantidTestHelpers/IndirectFitDataCreationHelper.h" + +using namespace Mantid::API; +using namespace Mantid::CurveFitting; +using namespace MantidQt::CustomInterfaces::IDA; +using namespace Mantid::IndirectFitDataCreationHelper; + +using ConvolutionFitSequential = + Algorithms::ConvolutionFit<Algorithms::QENSFitSequential>; + +namespace { + +/// The name of the conjoined input and guess workspaces +std::string const INPUT_AND_GUESS_NAME = "__QENSInputAndGuess"; + +std::string getFittingFunctionString(std::string const &workspaceName) { + return "name=LinearBackground,A0=0,A1=0,ties=(A0=0.000000,A1=0.0);" + "(composite=Convolution,FixResolution=true,NumDeriv=true;" + "name=Resolution,Workspace=" + + workspaceName + + ",WorkspaceIndex=0;((composite=ProductFunction,NumDeriv=" + "false;name=Lorentzian,Amplitude=1,PeakCentre=1,FWHM=0." + "0175)))"; +} + +IFunction_sptr getFunction(std::string const &functionString) { + return FunctionFactory::Instance().createInitialized(functionString); +} + +/// A dummy class used to create a model to pass to IndirectFitPlotModel's +/// constructor +class DummyModel + : public MantidQt::CustomInterfaces::IDA::IndirectFittingModel { +public: + ~DummyModel(){}; + +private: + std::string sequentialFitOutputName() const override { return ""; }; + std::string simultaneousFitOutputName() const override { return ""; }; + std::string singleFitOutputName(std::size_t index, + std::size_t spectrum) const override { + (void)index; + (void)spectrum; + return ""; + }; +}; + +void setFittingFunction(IndirectFittingModel *model, + std::string const &functionString, + bool setFitFunction) { + if (setFitFunction) + model->setFitFunction(getFunction(functionString)); +} + +IndirectFittingModel *getEmptyDummyModel() { return new DummyModel(); } + +IndirectFittingModel * +createModelWithSingleWorkspace(std::string const &workspaceName, + int const &numberOfSpectra, + bool setFitFunction) { + auto model = getEmptyDummyModel(); + SetUpADSWithWorkspace ads(workspaceName, createWorkspace(numberOfSpectra)); + model->addWorkspace(workspaceName); + setFittingFunction(model, getFittingFunctionString(workspaceName), + setFitFunction); + return model; +} + +void addWorkspacesToModel(IndirectFittingModel *model, + int const &numberOfSpectra) { + (void)model; + (void)numberOfSpectra; +} + +template <typename Name, typename... Names> +void addWorkspacesToModel(IndirectFittingModel *model, + int const &numberOfSpectra, Name const &workspaceName, + Names const &... workspaceNames) { + Mantid::API::AnalysisDataService::Instance().addOrReplace( + workspaceName, createWorkspace(numberOfSpectra)); + model->addWorkspace(workspaceName); + addWorkspacesToModel(model, numberOfSpectra, workspaceNames...); +} + +template <typename Name, typename... Names> +IndirectFittingModel *createModelWithMultipleWorkspaces( + int const &numberOfSpectra, bool setFitFunction, Name const &workspaceName, + Names const &... workspaceNames) { + auto model = createModelWithSingleWorkspace(workspaceName, numberOfSpectra, + setFitFunction); + addWorkspacesToModel(model, numberOfSpectra, workspaceNames...); + return model; +} + +IndirectFittingModel *createModelWithSingleInstrumentWorkspace( + std::string const &workspaceName, int const &xLength, int const &yLength) { + auto model = getEmptyDummyModel(); + SetUpADSWithWorkspace ads(workspaceName, + createWorkspaceWithInstrument(xLength, yLength)); + model->addWorkspace(workspaceName); + return model; +} + +IAlgorithm_sptr setupFitAlgorithm(MatrixWorkspace_sptr workspace, + std::string const &functionString) { + auto alg = boost::make_shared<ConvolutionFitSequential>(); + alg->initialize(); + alg->setProperty("InputWorkspace", workspace); + alg->setProperty("Function", functionString); + alg->setProperty("StartX", 0.0); + alg->setProperty("EndX", 3.0); + alg->setProperty("SpecMin", 0); + alg->setProperty("SpecMax", 5); + alg->setProperty("ConvolveMembers", true); + alg->setProperty("Minimizer", "Levenberg-Marquardt"); + alg->setProperty("MaxIterations", 500); + alg->setProperty("OutputWorkspace", "output"); + alg->setLogging(false); + return alg; +} + +IAlgorithm_sptr getSetupFitAlgorithm(IndirectFittingModel *model, + MatrixWorkspace_sptr workspace, + std::string const &workspaceName) { + setFittingFunction(model, getFittingFunctionString(workspaceName), true); + auto alg = + setupFitAlgorithm(workspace, getFittingFunctionString(workspaceName)); + return alg; +} + +IAlgorithm_sptr getExecutedFitAlgorithm(IndirectFittingModel *model, + MatrixWorkspace_sptr workspace, + std::string const &workspaceName) { + auto const alg = getSetupFitAlgorithm(model, workspace, workspaceName); + alg->execute(); + return alg; +} + +IndirectFittingModel *getModelWithFitOutputData() { + auto model = createModelWithSingleInstrumentWorkspace("__ConvFit", 6, 5); + auto const modelWorkspace = model->getWorkspace(0); + + auto const alg = getExecutedFitAlgorithm(model, modelWorkspace, "__ConvFit"); + model->addOutput(alg); + return model; +} + +IndirectFitPlotModel getFitPlotModel(bool setFitFunction = true) { + return IndirectFitPlotModel(createModelWithMultipleWorkspaces( + 10, setFitFunction, "Workspace1", "Workspace2")); +} + +IndirectFitPlotModel getFitPlotModelWithFitData() { + return IndirectFitPlotModel(getModelWithFitOutputData()); +} + +} // namespace + +class IndirectFitPlotModelTest : public CxxTest::TestSuite { +public: + /// WorkflowAlgorithms do not appear in the FrameworkManager without this line + IndirectFitPlotModelTest() { FrameworkManager::Instance(); } + + static IndirectFitPlotModelTest *createSuite() { + return new IndirectFitPlotModelTest(); + } + + static void destroySuite(IndirectFitPlotModelTest *suite) { delete suite; } + + void tearDown() override { AnalysisDataService::Instance().clear(); } + + void + test_that_IndirectFittingModel_instantiates_a_model_with_the_correct_starting_member_variables() { + auto const model = getFitPlotModel(); + + TS_ASSERT_EQUALS(model.getActiveDataIndex(), 0); + TS_ASSERT_EQUALS(model.getActiveSpectrum(), 0); + TS_ASSERT_EQUALS(model.numberOfWorkspaces(), 2); + } + + void + test_that_getWorkspace_returns_a_workspace_with_the_correct_number_of_spectra() { + auto const model = getFitPlotModel(); + TS_ASSERT_EQUALS(model.getWorkspace()->getNumberHistograms(), 10); + } + + void + test_that_getGuessWorkspace_will_create_and_then_return_a_guess_workspace_with_the_correct_number_of_spectra() { + /// Only creates a guess for the active spectra of the selected workspace + auto const model = getFitPlotModel(); + + TS_ASSERT(model.getGuessWorkspace()); + TS_ASSERT_EQUALS(model.getGuessWorkspace()->getNumberHistograms(), 1); + } + + void + test_that_getResultWorkspace_returns_a_nullptr_if_a_fit_has_not_yet_been_run() { + auto const model = getFitPlotModel(); + TS_ASSERT(!model.getResultWorkspace()); + } + + void + test_that_getResultWorkspace_returns_a_workspace_when_data_has_been_fit() { + auto const model = getFitPlotModelWithFitData(); + TS_ASSERT(model.getResultWorkspace()); + } + + void + test_that_getSpectra_returns_the_same_spectra_range_which_was_provided_as_input() { + auto const model = getFitPlotModel(); + + Spectra const spectra = std::make_pair(0u, 9u); + Spectra const storedSpectra = model.getSpectra(); + + TS_ASSERT(boost::apply_visitor(AreSpectraEqual(), storedSpectra, spectra)); + } + + void + test_that_appendGuessToInput_returns_a_workspace_that_is_the_combination_of_the_input_and_guess_workspaces() { + auto const model = getFitPlotModel(); + auto const guess = model.getGuessWorkspace(); + + auto const resultWorkspace = model.appendGuessToInput(guess); + + TS_ASSERT(AnalysisDataService::Instance().doesExist(INPUT_AND_GUESS_NAME)); + TS_ASSERT_EQUALS(resultWorkspace->getAxis(1)->label(0), "Sample"); + TS_ASSERT_EQUALS(resultWorkspace->getAxis(1)->label(1), "Guess"); + /// Only two spectra because the guessWorkspace will only ever have one + /// spectra, and then spectra are extracted from the input workspace between + /// m_activeSpectrum and m_activeSpectrum and so only 1 spectrum is + /// extracted. 1 + 1 = 2 + TS_ASSERT_EQUALS(resultWorkspace->getNumberHistograms(), 2); + } + + void + test_that_getActiveDataIndex_returns_the_index_which_it_has_been_set_to() { + auto model = getFitPlotModel(); + + model.setActiveIndex(2); + + TS_ASSERT_EQUALS(model.getActiveDataIndex(), 2); + } + + void + test_that_getActiveSpectrum_returns_the_spectrum_which_it_has_been_set_to() { + auto model = getFitPlotModel(); + + model.setActiveSpectrum(3); + + TS_ASSERT_EQUALS(model.getActiveSpectrum(), 3); + } + + void test_that_getFitDataName_returns_the_correctly_calculated_name() { + auto const model = getFitPlotModel(); + + TS_ASSERT_EQUALS(model.getFitDataName(), "Workspace1 (0-9)"); + TS_ASSERT_EQUALS(model.getFitDataName(1), "Workspace2 (0-9)"); + } + + void + test_that_getFitDataName_does_not_throw_when_provided_an_out_of_range_index() { + auto const model = getFitPlotModel(); + TS_ASSERT_THROWS_NOTHING(model.getFitDataName(10000000)); + } + + void + test_that_getLastFitDataName_returns_the_name_for_the_last_workspace_in_the_model() { + auto const model = getFitPlotModel(); + TS_ASSERT_EQUALS(model.getLastFitDataName(), "Workspace2 (0-9)"); + } + + void test_that_getRange_returns_the_range_which_is_set() { + auto model = getFitPlotModel(); + + model.setStartX(2.2); + model.setEndX(8.8); + + TS_ASSERT_EQUALS(model.getRange().first, 2.2); + TS_ASSERT_EQUALS(model.getRange().second, 8.8); + } + + void + test_that_setStartX_does_not_set_the_StartX_when_the_provided_value_is_larger_than_the_EndX() { + auto model = getFitPlotModel(); + + model.setEndX(2.2); + model.setStartX(8.8); + + TS_ASSERT_EQUALS(model.getRange().first, 0.0); + TS_ASSERT_EQUALS(model.getRange().second, 2.2); + } + + void + test_that_setEndX_does_not_set_the_EndX_when_the_provided_value_is_smaller_than_the_StartX() { + auto model = getFitPlotModel(); + + model.setStartX(8.8); + model.setEndX(2.2); + + TS_ASSERT_EQUALS(model.getRange().first, 8.8); + TS_ASSERT_EQUALS(model.getRange().second, 10.0); + } + + void test_that_getWorkspaceRange_returns_the_defaulted_values_before_a_fit() { + auto const model = getFitPlotModel(); + + TS_ASSERT_EQUALS(model.getWorkspaceRange().first, 0.0); + TS_ASSERT_EQUALS(model.getWorkspaceRange().second, 10.0); + } + + void + test_that_getResultRange_returns_the_different_values_to_the_values_before_the_fit() { + auto const model = getFitPlotModelWithFitData(); + + TS_ASSERT_DIFFERS(model.getResultRange().first, 0.0); + TS_ASSERT_DIFFERS(model.getResultRange().second, 10.0); + } + + void + test_that_getFirstHWHM_returns_half_the_value_of_the_FWHM_in_the_fitting_function() { + auto const model = getFitPlotModel(); + TS_ASSERT_EQUALS(model.getFirstHWHM(), 0.0175 / 2); + } + + void + test_that_getFirstPeakCentre_returns_the_value_of_the_first_PeakCentre_in_the_fitting_function() { + auto const model = getFitPlotModel(); + TS_ASSERT_EQUALS(model.getFirstPeakCentre(), 1.0); + } + + void + test_that_getFirstBackgroundLevel_returns_the_value_of_the_first_background_level_in_the_fitting_function() { + auto const model = getFitPlotModel(); + TS_ASSERT_EQUALS(model.getFirstBackgroundLevel(), 0.0); + } + + void test_that_calculateHWHMMaximum_returns_the_value_expected() { + auto const model = getFitPlotModel(); + + auto const hwhm = model.getFirstHWHM(); + auto const peakCentre = model.getFirstPeakCentre().get_value_or(0.); + + auto const minimum = peakCentre + *hwhm; + TS_ASSERT_EQUALS(model.calculateHWHMMaximum(minimum), 0.99125); + } + + void test_that_calculateHWHMMinimum_returns_the_value_expected() { + auto const model = getFitPlotModel(); + + auto const hwhm = model.getFirstHWHM(); + auto const peakCentre = model.getFirstPeakCentre().get_value_or(0.); + + auto const maximum = peakCentre - *hwhm; + TS_ASSERT_EQUALS(model.calculateHWHMMinimum(maximum), 1.00875); + } + + void + test_that_canCalculateGuess_returns_false_when_there_is_no_fitting_function() { + auto const model = getFitPlotModel(false); + TS_ASSERT(!model.canCalculateGuess()); + } + + void + test_that_canCalculateGuess_returns_true_when_there_is_a_fitting_function_and_a_model_with_a_workspace() { + auto const model = getFitPlotModel(); + TS_ASSERT(model.canCalculateGuess()); + } + + void + test_that_setFWHM_will_change_the_value_of_the_FWHM_in_the_fitting_function() { + auto model = getFitPlotModel(); + + auto const fwhm = 1.1; + model.setFWHM(fwhm); + + TS_ASSERT_EQUALS(model.getFirstHWHM(), fwhm / 2); + } + + void + test_that_setBackground_will_change_the_value_of_A0_in_the_fitting_function() { + auto model = getFitPlotModel(); + + auto const background = 0.12; + model.setBackground(background); + + TS_ASSERT_EQUALS(model.getFirstBackgroundLevel(), background); + } + + void + test_that_deleteExternalGuessWorkspace_removes_the_guess_workspace_from_the_ADS() { + auto model = getFitPlotModel(); + + auto const guess = model.getGuessWorkspace(); + (void)model.appendGuessToInput(guess); + + TS_ASSERT(AnalysisDataService::Instance().doesExist(INPUT_AND_GUESS_NAME)); + model.deleteExternalGuessWorkspace(); + TS_ASSERT(!AnalysisDataService::Instance().doesExist(INPUT_AND_GUESS_NAME)); + } + + void + test_that_deleteExternalGuessWorkspace_does_not_throw_if_the_guess_workspace_does_not_exist() { + auto model = getFitPlotModel(); + TS_ASSERT_THROWS_NOTHING(model.deleteExternalGuessWorkspace()); + } +}; + +#endif diff --git a/qt/scientific_interfaces/Indirect/test/IndirectFittingModelTest.h b/qt/scientific_interfaces/Indirect/test/IndirectFittingModelTest.h new file mode 100644 index 0000000000000000000000000000000000000000..287a3f78cd1f5a773d7449a75fc3514235e7b207 --- /dev/null +++ b/qt/scientific_interfaces/Indirect/test/IndirectFittingModelTest.h @@ -0,0 +1,735 @@ +#ifndef MANTID_INDIRECTFITTINGMODELTEST_H_ +#define MANTID_INDIRECTFITTINGMODELTEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "IndirectFittingModel.h" +#include "MantidAPI/FrameworkManager.h" +#include "MantidAPI/FunctionFactory.h" +#include "MantidAPI/MatrixWorkspace.h" +#include "MantidCurveFitting/Algorithms/ConvolutionFit.h" +#include "MantidCurveFitting/Algorithms/QENSFitSequential.h" +#include "MantidDataObjects/Workspace2D.h" +#include "MantidTestHelpers/IndirectFitDataCreationHelper.h" + +using namespace Mantid::API; +using namespace Mantid::CurveFitting; +using namespace Mantid::DataObjects; +using namespace MantidQt::CustomInterfaces::IDA; +using namespace Mantid::IndirectFitDataCreationHelper; + +using ConvolutionFitSequential = + Algorithms::ConvolutionFit<Algorithms::QENSFitSequential>; + +namespace { + +IFunction_sptr getFunction(std::string const &functionString) { + return FunctionFactory::Instance().createInitialized(functionString); +} + +/// A dummy model used to inherit the methods which need testing +class DummyModel + : public MantidQt::CustomInterfaces::IDA::IndirectFittingModel { +public: + ~DummyModel(){}; + +private: + std::string sequentialFitOutputName() const override { return ""; }; + std::string simultaneousFitOutputName() const override { return ""; }; + std::string singleFitOutputName(std::size_t index, + std::size_t spectrum) const override { + (void)index; + (void)spectrum; + return ""; + }; +}; + +std::unique_ptr<DummyModel> getEmptyModel() { + return std::make_unique<DummyModel>(); +} + +std::unique_ptr<DummyModel> +createModelWithSingleWorkspace(std::string const &workspaceName, + int const &numberOfSpectra) { + auto model = getEmptyModel(); + SetUpADSWithWorkspace ads(workspaceName, createWorkspace(numberOfSpectra)); + model->addWorkspace(workspaceName); + return model; +} + +void addWorkspacesToModel(std::unique_ptr<DummyModel> &model, + int const &numberOfSpectra) { + (void)model; + (void)numberOfSpectra; +} + +template <typename Name, typename... Names> +void addWorkspacesToModel(std::unique_ptr<DummyModel> &model, + int const &numberOfSpectra, Name const &workspaceName, + Names const &... workspaceNames) { + Mantid::API::AnalysisDataService::Instance().addOrReplace( + workspaceName, createWorkspace(numberOfSpectra)); + model->addWorkspace(workspaceName); + addWorkspacesToModel(model, numberOfSpectra, workspaceNames...); +} + +template <typename Name, typename... Names> +std::unique_ptr<DummyModel> +createModelWithMultipleWorkspaces(int const &numberOfSpectra, + Name const &workspaceName, + Names const &... workspaceNames) { + auto model = createModelWithSingleWorkspace(workspaceName, numberOfSpectra); + addWorkspacesToModel(model, numberOfSpectra, workspaceNames...); + return model; +} + +std::unique_ptr<DummyModel> createModelWithSingleInstrumentWorkspace( + std::string const &workspaceName, int const &xLength, int const &yLength) { + auto model = getEmptyModel(); + SetUpADSWithWorkspace ads(workspaceName, + createWorkspaceWithInstrument(xLength, yLength)); + model->addWorkspace(workspaceName); + return model; +} + +void setFittingFunction(std::unique_ptr<DummyModel> &model, + std::string const &functionString) { + model->setFitFunction(getFunction(functionString)); +} + +IAlgorithm_sptr setupFitAlgorithm(MatrixWorkspace_sptr workspace, + std::string const &functionString) { + auto alg = boost::make_shared<ConvolutionFitSequential>(); + alg->initialize(); + alg->setProperty("InputWorkspace", workspace); + alg->setProperty("Function", functionString); + alg->setProperty("StartX", 0.0); + alg->setProperty("EndX", 3.0); + alg->setProperty("SpecMin", 0); + alg->setProperty("SpecMax", 5); + alg->setProperty("ConvolveMembers", true); + alg->setProperty("Minimizer", "Levenberg-Marquardt"); + alg->setProperty("MaxIterations", 500); + alg->setProperty("OutputWorkspace", "output"); + alg->setLogging(false); + return alg; +} + +IAlgorithm_sptr getSetupFitAlgorithm(std::unique_ptr<DummyModel> &model, + MatrixWorkspace_sptr workspace, + std::string const &workspaceName) { + std::string const function = + "name=LinearBackground,A0=0,A1=0,ties=(A0=0.000000,A1=0.0);" + "(composite=Convolution,FixResolution=true,NumDeriv=true;" + "name=Resolution,Workspace=" + + workspaceName + + ",WorkspaceIndex=0;((composite=ProductFunction,NumDeriv=" + "false;name=Lorentzian,Amplitude=1,PeakCentre=0,FWHM=0." + "0175)))"; + setFittingFunction(model, function); + auto alg = setupFitAlgorithm(workspace, function); + return alg; +} + +IAlgorithm_sptr getExecutedFitAlgorithm(std::unique_ptr<DummyModel> &model, + MatrixWorkspace_sptr workspace, + std::string const &workspaceName) { + auto const alg = getSetupFitAlgorithm(model, workspace, workspaceName); + alg->execute(); + return alg; +} + +std::unique_ptr<DummyModel> getModelWithFitOutputData() { + auto model = createModelWithSingleInstrumentWorkspace("__ConvFit", 6, 5); + auto const modelWorkspace = model->getWorkspace(0); + + auto const alg = getExecutedFitAlgorithm(model, modelWorkspace, "__ConvFit"); + model->addOutput(alg); + return model; +} + +} // namespace + +class IndirectFittingModelTest : public CxxTest::TestSuite { +public: + /// WorkflowAlgorithms do not appear in the FrameworkManager without this line + IndirectFittingModelTest() { FrameworkManager::Instance(); } + + static IndirectFittingModelTest *createSuite() { + return new IndirectFittingModelTest(); + } + + static void destroySuite(IndirectFittingModelTest *suite) { delete suite; } + + void tearDown() override { AnalysisDataService::Instance().clear(); } + + void test_model_is_instantiated_correctly() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 3); + + TS_ASSERT(model->getWorkspace(0)); + TS_ASSERT_EQUALS(model->numberOfWorkspaces(), 1); + TS_ASSERT_EQUALS(model->getNumberOfSpectra(0), 3); + } + + void test_that_a_workspace_is_stored_correctly_in_the_ADS() { + SetUpADSWithWorkspace ads("WorkspaceName", createWorkspace(3)); + + TS_ASSERT(ads.doesExist("WorkspaceName")); + auto const storedWorkspace = ads.retrieveWorkspace("WorkspaceName"); + TS_ASSERT_EQUALS(storedWorkspace->getNumberHistograms(), 3); + } + + void + test_that_addWorkspace_will_add_a_workspace_to_the_fittingData_using_the_workspace_name() { + auto model = getEmptyModel(); + auto const workspace = createWorkspace(3); + SetUpADSWithWorkspace ads("WorkspaceName", workspace); + + model->addWorkspace("WorkspaceName"); + + TS_ASSERT_EQUALS(model->getWorkspace(0), workspace); + } + + void + test_that_addWorkspace_throws_when_provided_a_workspace_name_and_an_empty_spectraString() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 3); + + std::string const spectraString(""); + + TS_ASSERT_THROWS(model->addWorkspace("WorkspaceName", spectraString), + std::runtime_error); + } + + void + test_that_addWorkspace_combines_an_input_workspace_with_a_workspace_that_already_exists_if_the_workspaces_have_the_same_name() { + auto model = createModelWithMultipleWorkspaces(3, "Name", "Name"); + + TS_ASSERT(model->getWorkspace(0)); + TS_ASSERT(!model->getWorkspace(1)); + } + + void + test_that_addWorkspace_does_not_combine_an_input_workspace_with_a_workspace_that_already_exists_if_the_workspaces_are_differently_named() { + auto model = getEmptyModel(); + auto const workspace1 = createWorkspace(3); + auto const workspace2 = createWorkspace(3); + SetUpADSWithWorkspace ads("WorkspaceName1", workspace1); + ads.addOrReplace("WorkspaceName2", workspace2); + + model->addWorkspace("WorkspaceName1"); + model->addWorkspace("WorkspaceName2"); + + TS_ASSERT_EQUALS(model->getWorkspace(0), workspace1); + TS_ASSERT_EQUALS(model->getWorkspace(1), workspace2); + } + + void + test_that_getWorkspace_returns_a_nullptr_when_getWorkspace_is_provided_an_out_of_range_index() { + auto const model = createModelWithSingleWorkspace("WorkspaceName", 3); + TS_ASSERT_EQUALS(model->getWorkspace(1), nullptr); + } + + void + test_that_getSpectra_returns_a_correct_spectra_when_the_index_provided_is_valid() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 3); + + Spectra const inputSpectra = DiscontinuousSpectra<std::size_t>("0-1"); + model->setSpectra(inputSpectra, 0); + Spectra const spectra = model->getSpectra(0); + + TS_ASSERT(boost::apply_visitor(AreSpectraEqual(), spectra, inputSpectra)); + } + + void + test_that_getSpectra_returns_an_empty_DiscontinuousSpectra_when_provided_an_out_of_range_index() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 3); + + Spectra const emptySpectra(DiscontinuousSpectra<std::size_t>("")); + Spectra const spectra = model->getSpectra(3); + + TS_ASSERT(boost::apply_visitor(AreSpectraEqual(), spectra, emptySpectra)); + } + + void + test_that_getFittingRange_returns_correct_range_when_provided_a_valid_index_and_spectrum() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 1); + + model->setStartX(1.2, 0, 0); + model->setEndX(5.6, 0, 0); + + TS_ASSERT_EQUALS(model->getFittingRange(0, 0).first, 1.2); + TS_ASSERT_EQUALS(model->getFittingRange(0, 0).second, 5.6); + } + + void + test_that_getFittingRange_returns_empty_range_when_provided_an_out_of_range_dataIndex() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 1); + + model->setStartX(1.2, 0, 0); + model->setEndX(5.6, 0, 0); + + TS_ASSERT_EQUALS(model->getFittingRange(1, 0).first, 0.0); + TS_ASSERT_EQUALS(model->getFittingRange(1, 0).second, 0.0); + } + + void + test_that_getFittingRange_returns_empty_range_when_there_are_zero_spectra() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 1); + + model->setStartX(1.2, 0, 0); + model->setEndX(5.6, 0, 0); + DiscontinuousSpectra<std::size_t> const emptySpec(""); + model->setSpectra(emptySpec, 0); + + TS_ASSERT_EQUALS(model->getFittingRange(0, 0).first, 0.0); + TS_ASSERT_EQUALS(model->getFittingRange(0, 0).second, 0.0); + } + + void + test_that_getExcludeRegion_returns_correct_range_when_provided_a_valid_index_and_spectrum() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 1); + + model->setExcludeRegion("0,1,3,4", 0, 0); + + TS_ASSERT_EQUALS(model->getExcludeRegion(0, 0), "0.0,1.0,3.0,4.0"); + } + + void + test_that_getExcludeRegion_returns_empty_range_when_provided_an_out_of_range_dataIndex() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 1); + + model->setExcludeRegion("0,1,3,4", 0, 0); + + TS_ASSERT_EQUALS(model->getExcludeRegion(1, 0), ""); + } + + void + test_that_getExcludeRegion_returns_empty_range_when_there_are_zero_spectra() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 1); + + model->setExcludeRegion("0,1,3,4", 0, 0); + DiscontinuousSpectra<std::size_t> const emptySpec(""); + model->setSpectra(emptySpec, 0); + + TS_ASSERT_EQUALS(model->getExcludeRegion(1, 0), ""); + } + + void + test_that_getExcludeRegion_returns_a_region_where_each_range_is_in_order_after_setExcludeRegion_is_given_an_unordered_region_string() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 1); + + model->setExcludeRegion("0,1,6,4", 0, 0); + + TS_ASSERT_EQUALS(model->getExcludeRegion(0, 0), "0.0,1.0,4.0,6.0"); + } + + void + test_that_createDisplayName_returns_valid_string_when_provided_an_in_range_dataIndex() { + auto const model = createModelWithSingleWorkspace("WorkspaceName", 1); + + std::string const formatString = "%1%_s%2%_Gaussian"; + std::string const rangeDelimiter = "_to_"; + + TS_ASSERT_EQUALS(model->createOutputName(formatString, rangeDelimiter, 0), + "WorkspaceName_s0_Gaussian_Result"); + } + + void + test_that_createDisplayName_returns_string_with_red_removed_from_the_workspace_name() { + auto const model = createModelWithSingleWorkspace("Workspace_3456_red", 1); + + std::string const formatString = "%1%_s%2%_Gaussian"; + std::string const rangeDelimiter = "_to_"; + + TS_ASSERT_EQUALS(model->createOutputName(formatString, rangeDelimiter, 0), + "Workspace_3456_s0_Gaussian_Result"); + } + + void + test_that_createDisplayName_returns_correct_name_when_provided_a_valid_rangeDelimiter_and_formatString() { + auto const model = createModelWithSingleWorkspace("Workspace_3456_red", 1); + + std::vector<std::string> const formatStrings{ + "%1%_s%2%_Gaussian", "%1%_f%2%,s%2%_MSD", "%1%_s%2%_TeixeiraWater"}; + std::string const rangeDelimiter = "_to_"; + + TS_ASSERT_EQUALS( + model->createOutputName(formatStrings[0], rangeDelimiter, 0), + "Workspace_3456_s0_Gaussian_Result"); + TS_ASSERT_EQUALS( + model->createOutputName(formatStrings[1], rangeDelimiter, 0), + "Workspace_3456_f0+s0_MSD_Result"); + TS_ASSERT_EQUALS( + model->createOutputName(formatStrings[2], rangeDelimiter, 0), + "Workspace_3456_s0_TeixeiraWater_Result"); + } + + void + test_that_isMultiFit_returns_true_when_there_are_more_than_one_workspaces_stored_in_the_model() { + auto const model = + createModelWithMultipleWorkspaces(3, "Workspace1", "Workspace2"); + TS_ASSERT(model->isMultiFit()); + } + + void + test_that_isMultiFit_returns_false_when_there_is_one_workspace_stored_in_the_model() { + auto const model = createModelWithSingleWorkspace("Workspace1", 1); + TS_ASSERT(!model->isMultiFit()); + } + + void + test_that_isPreviouslyFit_returns_false_if_there_is_no_previous_fit_output_data() { + auto const model = createModelWithSingleWorkspace("WorkspaceName", 1); + TS_ASSERT(!model->isPreviouslyFit(0, 0)); + } + + void + test_that_isPreviouslyFit_returns_false_if_the_dataIndex_is_out_of_range() { + auto const model = createModelWithSingleWorkspace("WorkspaceName", 1); + TS_ASSERT(!model->isPreviouslyFit(4, 0)); + } + + void + test_that_setFitFunction_will_alter_the_activeFunction_to_the_function_specified() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 3); + + auto const function = getFunction("name=Convolution;name=Resolution"); + model->setFitFunction(function); + + TS_ASSERT_EQUALS(model->getFittingFunction(), function); + } + + void test_that_ConvolutionSequentialFit_algorithm_initializes() { + auto model = createModelWithSingleInstrumentWorkspace("Name", 6, 5); + auto const modelWorkspace = model->getWorkspace(0); + + auto const alg = getSetupFitAlgorithm(model, modelWorkspace, "Name"); + + TS_ASSERT(alg->isInitialized()); + } + + void test_that_ConvolutionSequentialFit_algorithm_executes_without_error() { + auto model = createModelWithSingleInstrumentWorkspace("Name", 6, 5); + auto const modelWorkspace = model->getWorkspace(0); + + auto const alg = getSetupFitAlgorithm(model, modelWorkspace, "Name"); + + TS_ASSERT_THROWS_NOTHING(alg->execute()); + TS_ASSERT(alg->isExecuted()); + } + + void test_that_addOutput_adds_the_output_of_a_fit_into_the_model() { + auto model = createModelWithSingleInstrumentWorkspace("__ConvFit", 6, 5); + auto const modelWorkspace = model->getWorkspace(0); + + auto const alg = + getExecutedFitAlgorithm(model, modelWorkspace, "__ConvFit"); + model->addOutput(alg); + + TS_ASSERT(model->getResultWorkspace()); + TS_ASSERT(model->getResultGroup()); + } + + void + test_that_isPreviouslyFit_returns_true_if_the_spectrum_has_been_fitted_previously() { + auto const model = getModelWithFitOutputData(); + TS_ASSERT(model->isPreviouslyFit(0, 0)); + } + + void test_that_hasZeroSpectra_returns_true_if_workspace_has_zero_spectra() { + auto model = getEmptyModel(); + auto const workspace = boost::make_shared<Workspace2D>(); + SetUpADSWithWorkspace ads("WorkspaceEmpty", workspace); + + model->addWorkspace("WorkspaceEmpty"); + + TS_ASSERT(model->hasZeroSpectra(0)); + } + + void + test_that_hasZeroSpectra_returns_true_if_the_dataIndex_provided_is_out_of_range() { + auto const model = createModelWithSingleWorkspace("WorkspaceName", 1); + TS_ASSERT(model->hasZeroSpectra(1)); + } + + void + test_that_hasZeroSpectra_returns_false_if_workspace_contains_one_or_more_spectra() { + auto const model = createModelWithSingleWorkspace("WorkspaceName", 1); + TS_ASSERT(!model->hasZeroSpectra(0)); + } + + void + test_that_isInvalidFunction_returns_a_message_when_no_activeFunction_exists() { + auto const model = createModelWithSingleWorkspace("WorkspaceName", 1); + TS_ASSERT(model->isInvalidFunction()); + } + + void + test_that_isInvalidFunction_returns_a_message_when_the_activeFunction_contains_zero_parameters_or_functions() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 3); + + auto const function = getFunction("name=Convolution;name=Resolution"); + model->setFitFunction(function); + + TS_ASSERT(model->isInvalidFunction()); + } + + void test_isInvalidFunction_returns_none_if_the_activeFunction_is_valid() { + auto model = createModelWithSingleInstrumentWorkspace("Name", 6, 5); + auto const modelWorkspace = model->getWorkspace(0); + + (void)getSetupFitAlgorithm(model, modelWorkspace, "Name"); + + TS_ASSERT(!model->isInvalidFunction()); + } + + void + test_that_numberOfWorkspace_returns_the_number_of_workspace_stored_by_model() { + auto const model = createModelWithMultipleWorkspaces( + 3, "Workspace1", "Workspace2", "Workspace3"); + TS_ASSERT_EQUALS(model->numberOfWorkspaces(), 3); + } + + void test_that_getNumberOfSpectra_throws_if_dataIndex_is_out_of_range() { + auto const model = createModelWithSingleWorkspace("WorkspaceName", 3); + TS_ASSERT_THROWS(model->getNumberOfSpectra(1), std::runtime_error); + } + + void + test_that_getNumberOfSpectra_returns_the_number_of_spectra_stored_in_the_workspace_given() { + auto const model = createModelWithSingleWorkspace("WorkspaceName", 3); + TS_ASSERT_EQUALS(model->getNumberOfSpectra(0), 3); + } + + void + test_that_getFitParameterNames_returns_an_empty_vector_if_the_fitOutput_is_empty() { + auto const model = createModelWithSingleWorkspace("WorkspaceName", 3); + TS_ASSERT_EQUALS(model->getFitParameterNames(), std::vector<std::string>()); + } + + void + test_that_getFitParameterNames_returns_a_vector_of_fit_parameters_if_the_fitOutput_contains_parameters() { + auto model = createModelWithSingleInstrumentWorkspace("__ConvFit", 6, 5); + auto const modelWorkspace = model->getWorkspace(0); + + auto const alg = + getExecutedFitAlgorithm(model, modelWorkspace, "__ConvFit"); + model->addOutput(alg); + + TS_ASSERT(!model->getFitParameterNames().empty()); + } + + void test_getFittingFunction_returns_null_if_there_is_no_fitting_function() { + auto const model = createModelWithSingleWorkspace("WorkspaceName", 3); + TS_ASSERT_EQUALS(model->getFittingFunction(), nullptr); + } + + void + test_that_setFittingData_will_set_the_fittingData_to_the_data_provided() { + auto const model = createModelWithSingleWorkspace("WorkspaceName", 3); + TS_ASSERT_THROWS_NOTHING(model->setFittingData(model->clearWorkspaces())); + } + + void + test_that_setSpectra_will_set_the_spectra_to_the_provided_inputSpectra() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 10); + + Spectra const inputSpectra = DiscontinuousSpectra<std::size_t>("2,4,6-8"); + model->setSpectra(inputSpectra, 0); + Spectra const spectra = model->getSpectra(0); + + TS_ASSERT(boost::apply_visitor(AreSpectraEqual(), spectra, inputSpectra)); + } + + void + test_that_setSpectra_will_set_the_spectra_when_provided_a_spectra_pair() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 10); + + Spectra const inputSpectra = std::make_pair(0u, 5u); + model->setSpectra(inputSpectra, 0); + Spectra const spectra = model->getSpectra(0); + + TS_ASSERT(boost::apply_visitor(AreSpectraEqual(), spectra, inputSpectra)); + } + + void + test_that_setSpectra_does_not_throw_when_provided_an_out_of_range_dataIndex() { + auto const model = createModelWithSingleWorkspace("WorkspaceName", 5); + TS_ASSERT_THROWS_NOTHING(model->getSpectra(1)); + } + + void + test_that_setStartX_will_set_the_startX_at_the_first_dataIndex_when_the_fit_is_sequential() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 5); + + model->setStartX(4.0, 3, 0); + + TS_ASSERT_EQUALS(model->getFittingRange(0, 0).first, 4.0); + } + + void + test_that_setEndX_will_set_the_endX_at_the_first_dataIndex_when_the_fit_is_sequential() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 5); + + model->setEndX(4.0, 3, 0); + + TS_ASSERT_EQUALS(model->getFittingRange(0, 0).second, 4.0); + } + + void + test_that_setExcludeRegion_set_the_excludeRegion_at_the_first_dataIndex_when_the_fit_is_sequential() { + auto model = createModelWithSingleWorkspace("WorkspaceName", 5); + + model->setExcludeRegion("0,1,3,4", 3, 0); + + TS_ASSERT_EQUALS(model->getExcludeRegion(0, 0), "0.0,1.0,3.0,4.0"); + } + + void + test_that_removeWorkspace_will_remove_the_workspace_specified_in_the_model() { + auto model = createModelWithMultipleWorkspaces(3, "Ws1", "Ws2", "Ws3"); + + model->removeWorkspace(2); + + TS_ASSERT(model->getWorkspace(0)); + TS_ASSERT(model->getWorkspace(1)); + TS_ASSERT(!model->getWorkspace(2)); + } + + void + test_that_removeWorkspace_throws_when_provided_an_out_of_range_dataIndex() { + auto model = createModelWithMultipleWorkspaces(3, "Ws1", "Ws2"); + TS_ASSERT_THROWS(model->removeWorkspace(2), std::runtime_error); + } + + void test_that_clearWorkspaces_will_empty_the_fittingData() { + auto model = createModelWithMultipleWorkspaces(3, "Ws1", "Ws2"); + + model->clearWorkspaces(); + + TS_ASSERT(!model->getWorkspace(0)); + TS_ASSERT(!model->getWorkspace(1)); + TS_ASSERT_EQUALS(model->numberOfWorkspaces(), 0); + } + + void + test_that_setDefaultParameterValue_will_set_the_value_of_the_provided_parameter() { + auto model = createModelWithSingleWorkspace("Name", 5); + auto const modelWorkspace = model->getWorkspace(0); + + (void)getSetupFitAlgorithm(model, modelWorkspace, "Name"); + model->setDefaultParameterValue("Amplitude", 1.5, 0); + + auto const parameters = model->getDefaultParameters(0); + TS_ASSERT_EQUALS(parameters.at("f1.f1.f0.Amplitude").value, 1.5); + } + + void + test_that_getParameterValues_returns_an_empty_map_if_the_dataIndex_is_out_of_range() { + auto const model = getModelWithFitOutputData(); + TS_ASSERT(model->getParameterValues(1, 0).empty()); + } + + void + test_that_getParameterValues_returns_the_default_parameters_if_there_are_no_fit_parameters() { + auto model = createModelWithSingleInstrumentWorkspace("__ConvFit", 6, 5); + auto const modelWorkspace = model->getWorkspace(0); + + (void)getSetupFitAlgorithm(model, modelWorkspace, "__ConvFit"); + model->setDefaultParameterValue("Amplitude", 1.5, 0); + + auto const parameters = model->getParameterValues(0, 0); + TS_ASSERT_EQUALS(parameters.at("f1.f1.f0.Amplitude").value, 1.5); + } + + void + test_that_getParameterValues_returns_the_fit_parameters_after_a_fit_has_been_executed() { + auto const model = getModelWithFitOutputData(); + + auto const parameters = model->getParameterValues(0, 0); + TS_ASSERT_DELTA(parameters.at("f1.f1.f0.Amplitude").value, 1.0, 0.0001); + TS_ASSERT_DELTA(parameters.at("f1.f1.f0.FWHM").value, 0.0175, 0.0001); + TS_ASSERT(!parameters.empty()); + } + + void test_getFitParameters_returns_an_empty_map_when_there_is_no_fitOutput() { + auto model = createModelWithSingleInstrumentWorkspace("__ConvFit", 6, 5); + auto const modelWorkspace = model->getWorkspace(0); + + (void)getSetupFitAlgorithm(model, modelWorkspace, "__ConvFit"); + + TS_ASSERT(model->getFitParameters(0, 0).empty()); + } + + void test_getFitParameters_returns_the_fitParameters_after_a_fit() { + auto const model = getModelWithFitOutputData(); + + auto const parameters = model->getFitParameters(0, 0); + TS_ASSERT_DELTA(parameters.at("f1.f1.f0.Amplitude").value, 1.0, 0.0001); + TS_ASSERT_DELTA(parameters.at("f1.f1.f0.FWHM").value, 0.0175, 0.0001); + TS_ASSERT(!parameters.empty()); + } + + void + test_getDefaultParameters_returns_an_empty_map_when_the_dataIndex_is_out_of_range() { + auto const model = getModelWithFitOutputData(); + TS_ASSERT(model->getDefaultParameters(1).empty()); + } + + void + test_getDefaultParameters_returns_the_default_parameters_which_have_been_set() { + auto const model = getModelWithFitOutputData(); + + model->setDefaultParameterValue("Amplitude", 1.5, 0); + + auto const parameters = model->getDefaultParameters(0); + TS_ASSERT(!parameters.empty()); + TS_ASSERT_DELTA(parameters.at("f1.f1.f0.Amplitude").value, 1.5, 0.0001); + } + + void test_that_getResultLocation_returns_a_location_for_the_output_data() { + auto const model = getModelWithFitOutputData(); + TS_ASSERT(model->getResultLocation(0, 0)); + } + + void test_that_saveResult_does_not_throw_when_saving_data_from_a_fit() { + auto const model = getModelWithFitOutputData(); + TS_ASSERT_THROWS_NOTHING(model->saveResult()); + } + + void + test_that_cleanFailedRun_removes_the_temporary_workspace_from_the_ADS_when_a_fit_fails() { + /// Fails the fit algorithm on purpose by providing an invalid function + auto model = createModelWithSingleInstrumentWorkspace("Name", 6, 5); + auto const modelWorkspace = model->getWorkspace(0); + SetUpADSWithWorkspace ads("Name", modelWorkspace); + + std::string const functionString = + "name=Convolution;name=Resolution,Workspace=Name,WorkspaceIndex=0;"; + auto alg = setupFitAlgorithm(modelWorkspace, functionString); + alg->execute(); + + TS_ASSERT(ads.doesExist("__ConvolutionFitSequential_ws1")); + model->cleanFailedRun(alg); + TS_ASSERT(!ads.doesExist("__ConvolutionFitSequential_ws1")); + } + + void + test_that_cleanFailedSingleRun_removes_the_temporary_workspace_from_the_ADS_when_a_fit_fails_for_a_specific_workspaceIndex() { + /// Fails the fit algorithm on purpose by providing an invalid function + auto model = createModelWithSingleInstrumentWorkspace("Name", 6, 5); + auto const modelWorkspace = model->getWorkspace(0); + SetUpADSWithWorkspace ads("Name", modelWorkspace); + + std::string const functionString = + "name=Convolution;name=Resolution,Workspace=Name,WorkspaceIndex=0;"; + auto alg = setupFitAlgorithm(modelWorkspace, functionString); + alg->execute(); + + TS_ASSERT(ads.doesExist("__ConvolutionFitSequential_ws1")); + model->cleanFailedSingleRun(alg, 0); + TS_ASSERT(!ads.doesExist("__ConvolutionFitSequential_ws1")); + } +}; + +#endif // MANTID_INDIRECTFITTINGMODELTEST_H diff --git a/qt/widgets/common/CMakeLists.txt b/qt/widgets/common/CMakeLists.txt index ad2c5c0fd6120eae7dd5fbb5af3f60e6c7cf9ef3..5fb28c2bcf8be47c26ad9de1341ee1684628927f 100644 --- a/qt/widgets/common/CMakeLists.txt +++ b/qt/widgets/common/CMakeLists.txt @@ -18,6 +18,9 @@ set ( QT5_SRC_FILES src/FunctionBrowser.cpp src/GenericDialog.cpp src/HelpWindow.cpp + # todo: move this to the instrument view library when the slice + # viewer library is removed + src/InputController.cpp src/InterfaceFactory.cpp src/InterfaceManager.cpp src/LineEditWithClear.cpp @@ -90,6 +93,7 @@ set ( QT5_MOC_FILES inc/MantidQtWidgets/Common/FindReplaceDialog.h inc/MantidQtWidgets/Common/FunctionBrowser.h inc/MantidQtWidgets/Common/GenericDialog.h + inc/MantidQtWidgets/Common/InputController.h inc/MantidQtWidgets/Common/InterfaceManager.h inc/MantidQtWidgets/Common/LineEditWithClear.h inc/MantidQtWidgets/Common/ListPropertyWidget.h @@ -685,12 +689,10 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsCommon USE_QTWEBKIT INCLUDE_DIRS inc - ${PYTHON_INCLUDE_PATH} LINK_LIBS ${TARGET_LIBRARIES} QT4_LINK_LIBS Qt4::QtHelp - Qt4::QtOpenGL Qt4::QtNetwork Qt4::QtWebKit Qt4::Qscintilla diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/IndirectFitPropertyBrowser.h b/qt/widgets/common/inc/MantidQtWidgets/Common/IndirectFitPropertyBrowser.h index 13fa954bd03bd09d9eb4674e6994f83c0704ac0f..6b592ad34d1a6c3f30ace6aea872bb66dee107ca 100644 --- a/qt/widgets/common/inc/MantidQtWidgets/Common/IndirectFitPropertyBrowser.h +++ b/qt/widgets/common/inc/MantidQtWidgets/Common/IndirectFitPropertyBrowser.h @@ -78,6 +78,7 @@ public: void addComboBoxFunctionGroup( const QString &groupName, const std::vector<Mantid::API::IFunction_sptr> &functions); + void clearFitTypeComboBox(); void setBackgroundOptions(const QStringList &backgrounds); diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/TSVSerialiser.h b/qt/widgets/common/inc/MantidQtWidgets/Common/TSVSerialiser.h index e2c6351cb7f2dc784655127779e603cb9d7b2150..5ee66ddd3a0f3d8ef86dd68d4241387745587694 100644 --- a/qt/widgets/common/inc/MantidQtWidgets/Common/TSVSerialiser.h +++ b/qt/widgets/common/inc/MantidQtWidgets/Common/TSVSerialiser.h @@ -20,6 +20,10 @@ #include <QPoint> #include <QRect> +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) +#error "TSVSerialiser os only available in Qt 4 builds" +#endif + /** Parses the formatting used in MantidPlot project files @author Harry Jeffery, ISIS, RAL diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/WorkspacePresenter/WorkspaceTreeWidgetSimple.h b/qt/widgets/common/inc/MantidQtWidgets/Common/WorkspacePresenter/WorkspaceTreeWidgetSimple.h index 8b3f926a01898b768e5599e0503f7fb589cbb051..b5b15dbdd0f25b28bd9b09ccef26a016b6a84e03 100644 --- a/qt/widgets/common/inc/MantidQtWidgets/Common/WorkspacePresenter/WorkspaceTreeWidgetSimple.h +++ b/qt/widgets/common/inc/MantidQtWidgets/Common/WorkspacePresenter/WorkspaceTreeWidgetSimple.h @@ -42,11 +42,13 @@ public: void popupContextMenu() override; signals: - void plotSpectrumClicked(const QStringList &workspaceName); - void overplotSpectrumClicked(const QStringList &workspaceName); - void plotSpectrumWithErrorsClicked(const QStringList &workspaceName); - void overplotSpectrumWithErrorsClicked(const QStringList &workspaceName); - void plotColorfillClicked(const QStringList &workspaceName); + void plotSpectrumClicked(const QStringList &workspaceNames); + void overplotSpectrumClicked(const QStringList &workspaceNames); + void plotSpectrumWithErrorsClicked(const QStringList &workspaceNames); + void overplotSpectrumWithErrorsClicked(const QStringList &workspaceNames); + void plotColorfillClicked(const QStringList &workspaceNames); + void sampleLogsClicked(const QStringList &workspaceName); + void showInstrumentClicked(const QStringList &workspaceNames); private slots: void onPlotSpectrumClicked(); @@ -54,10 +56,13 @@ private slots: void onPlotSpectrumWithErrorsClicked(); void onOverplotSpectrumWithErrorsClicked(); void onPlotColorfillClicked(); + void onSampleLogsClicked(); + void onShowInstrumentClicked(); private: QAction *m_plotSpectrum, *m_overplotSpectrum, *m_plotSpectrumWithErrs, - *m_overplotSpectrumWithErrs, *m_plotColorfill; + *m_overplotSpectrumWithErrs, *m_plotColorfill, *m_sampleLogs, + *m_showInstrument; }; } // namespace MantidWidgets } // namespace MantidQt diff --git a/qt/widgets/common/src/DataProcessorUI/GenerateNotebook.cpp b/qt/widgets/common/src/DataProcessorUI/GenerateNotebook.cpp index 6ec53bf9cb44c9c60162db42dc04ecf1b2383d67..38e34a9e7f908fcf76104a8658f9b64993835306 100644 --- a/qt/widgets/common/src/DataProcessorUI/GenerateNotebook.cpp +++ b/qt/widgets/common/src/DataProcessorUI/GenerateNotebook.cpp @@ -14,7 +14,6 @@ #include "MantidQtWidgets/Common/ParseKeyValueString.h" #include <boost/algorithm/string.hpp> -#include <boost/regex.hpp> #include <boost/tokenizer.hpp> #include <fstream> #include <memory> diff --git a/qt/widgets/common/src/FileDialogHandler.cpp b/qt/widgets/common/src/FileDialogHandler.cpp index 534fa30e020a3f1ddc142a7970de7f77bb3abdc3..16a9466dbc8ea02b68e845fcf8f571a2d2949f9d 100644 --- a/qt/widgets/common/src/FileDialogHandler.cpp +++ b/qt/widgets/common/src/FileDialogHandler.cpp @@ -12,35 +12,24 @@ #include <sstream> namespace { // anonymous namespace -const boost::regex FILE_EXT_REG_EXP{R"(^.+\s+\((\S+)\)$)"}; const QString ALL_FILES("All Files (*)"); QString getExtensionFromFilter(const QString &selectedFilter) { - // empty returns empty - if (selectedFilter.isEmpty()) { - return QString(""); - } - + QString extension; // search for single extension + static const boost::regex FILE_EXT_REG_EXP{R"(\*\.[[:word:]]+)"}; boost::smatch result; - if (boost::regex_search(selectedFilter.toStdString(), result, - FILE_EXT_REG_EXP) && - result.size() == 2) { + const auto filter = selectedFilter.toStdString(); + if (boost::regex_search(filter, result, FILE_EXT_REG_EXP)) { // clang fails to cast result[1] to std::string. - std::string output = result[1]; - auto extension = QString::fromStdString(output); + const std::string output = result.str(0); + extension = QString::fromStdString(output); if (extension.startsWith("*")) - return extension.remove(0, 1); - else - return extension; - } else { - // failure to match suggests multi-extension filter - std::stringstream msg; - msg << "Failed to determine single extension from \"" - << selectedFilter.toStdString() << "\""; - throw std::runtime_error(msg.str()); + extension.remove(0, 1); } + return extension; } + } // anonymous namespace namespace MantidQt { @@ -60,10 +49,9 @@ QString getSaveFileName(QWidget *parent, QString selectedFilter; // create the file browser - QString filename = QFileDialog::getSaveFileName( + const QString filename = QFileDialog::getSaveFileName( parent, caption, AlgorithmInputHistory::Instance().getPreviousDirectory(), filter, &selectedFilter, options); - return addExtension(filename, selectedFilter); } @@ -91,22 +79,22 @@ QString getFilter(const Mantid::Kernel::Property *baseProp) { // multiple file version const auto *multiProp = dynamic_cast<const Mantid::API::MultipleFileProperty *>(baseProp); - if (bool(multiProp)) + if (multiProp) return getFilter(multiProp->getExts()); // regular file version const auto *singleProp = dynamic_cast<const Mantid::API::FileProperty *>(baseProp); // The allowed values in this context are file extensions - if (bool(singleProp)) + if (singleProp) return getFilter(singleProp->allowedValues()); // otherwise only the all files exists return ALL_FILES; } -/** For file dialogs. Have each filter on a separate line with the default as - * the first. +/** For file dialogs. Have each filter on a separate line with Data Files + * as the first and All Files as the last * * @param exts :: vector of extensions * @return a string that filters files by extenstions diff --git a/qt/widgets/common/src/FindDialog.cpp b/qt/widgets/common/src/FindDialog.cpp index 1e7a89ae007d6ab7a72c061ffb72f2fa9596b3c3..06d377dc13296ba47ab9cbfcdb0ed49621e3c88e 100644 --- a/qt/widgets/common/src/FindDialog.cpp +++ b/qt/widgets/common/src/FindDialog.cpp @@ -16,7 +16,6 @@ #include <QGroupBox> #include <QLabel> #include <QPushButton> -#include <QRegExp> #include <QVBoxLayout> FindDialog::FindDialog(ScriptEditor *editor, Qt::WindowFlags flags) diff --git a/qt/widgets/common/src/FindFilesThreadPoolManager.cpp b/qt/widgets/common/src/FindFilesThreadPoolManager.cpp index 2572218ecfe484b3c35ee0d91a4fee68a43a8ee8..d266eb139e626494b5a57951a484fa4427efa828 100644 --- a/qt/widgets/common/src/FindFilesThreadPoolManager.cpp +++ b/qt/widgets/common/src/FindFilesThreadPoolManager.cpp @@ -18,7 +18,6 @@ #include <QCoreApplication> #include <QSharedPointer> #include <boost/algorithm/string.hpp> -#include <boost/regex.hpp> using namespace Mantid::Kernel; using namespace Mantid::API; diff --git a/qt/widgets/common/src/FindFilesWorker.cpp b/qt/widgets/common/src/FindFilesWorker.cpp index 7e8c1097486304e8173fb8d1d05f7e91a10156ea..f4938a424d67026d4e9d603c3b0e3fdf3d6081de 100644 --- a/qt/widgets/common/src/FindFilesWorker.cpp +++ b/qt/widgets/common/src/FindFilesWorker.cpp @@ -16,7 +16,6 @@ #include <Poco/File.h> #include <QApplication> #include <boost/algorithm/string.hpp> -#include <boost/regex.hpp> #include <utility> diff --git a/qt/widgets/common/src/IndirectFitPropertyBrowser.cpp b/qt/widgets/common/src/IndirectFitPropertyBrowser.cpp index b2b289bab165a036f0e47fc07963e4b0cda96db7..582abb6ce538dc15dfb0158b7d98ed0c570afb19 100644 --- a/qt/widgets/common/src/IndirectFitPropertyBrowser.cpp +++ b/qt/widgets/common/src/IndirectFitPropertyBrowser.cpp @@ -711,6 +711,14 @@ void IndirectFitPropertyBrowser::addComboBoxFunctionGroup( addCustomFunctionGroup(groupName, functions); } +/** + * Removes all current Fit Type options from the fit type combo-box in this + * property browser. + */ +void IndirectFitPropertyBrowser::clearFitTypeComboBox() { + m_enumManager->setEnumNames(m_functionsInComboBox, {"None"}); +} + /** * Adds a custom function group to this fit property browser, with the specified * name and the associated specified functions. diff --git a/qt/widgets/common/src/MdSettings.cpp b/qt/widgets/common/src/MdSettings.cpp index 43f5fc2f6caa2aee63ab4c80e59d818429273f72..188d06b5a3646fd13ae1c0bd910995e3418720bb 100644 --- a/qt/widgets/common/src/MdSettings.cpp +++ b/qt/widgets/common/src/MdSettings.cpp @@ -6,7 +6,6 @@ // SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/Common/MdSettings.h" #include "MantidQtWidgets/Common/MdConstants.h" -#include "boost/scoped_ptr.hpp" #include <QSettings> #include <QString> diff --git a/qt/widgets/common/src/SelectFunctionDialog.cpp b/qt/widgets/common/src/SelectFunctionDialog.cpp index e945525748e3bd4b7ee666f90f004465d4adcb9b..cbfc0ea4e1113a357f5f936a6d0a26c1d74dcf10 100644 --- a/qt/widgets/common/src/SelectFunctionDialog.cpp +++ b/qt/widgets/common/src/SelectFunctionDialog.cpp @@ -22,7 +22,6 @@ #include <QGroupBox> #include <QLabel> #include <QPushButton> -#include <QRegExp> #include <QVBoxLayout> /** diff --git a/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidgetSimple.cpp b/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidgetSimple.cpp index a42cd96e5d7178f3d37fe79d90690c20b24814a9..8fd56b9853c0ce44d74bff26faff8c9d1e5696a6 100644 --- a/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidgetSimple.cpp +++ b/qt/widgets/common/src/WorkspacePresenter/WorkspaceTreeWidgetSimple.cpp @@ -31,7 +31,9 @@ WorkspaceTreeWidgetSimple::WorkspaceTreeWidgetSimple(QWidget *parent) m_plotSpectrumWithErrs(new QAction("spectrum with errors...", this)), m_overplotSpectrumWithErrs( new QAction("overplot spectrum with errors...", this)), - m_plotColorfill(new QAction("colorfill", this)) { + m_plotColorfill(new QAction("colorfill", this)), + m_sampleLogs(new QAction("Sample Logs", this)), + m_showInstrument(new QAction("Show Instrument", this)) { // connections connect(m_plotSpectrum, SIGNAL(triggered()), this, SLOT(onPlotSpectrumClicked())); @@ -43,6 +45,9 @@ WorkspaceTreeWidgetSimple::WorkspaceTreeWidgetSimple(QWidget *parent) SLOT(onOverplotSpectrumWithErrorsClicked())); connect(m_plotColorfill, SIGNAL(triggered()), this, SLOT(onPlotColorfillClicked())); + connect(m_sampleLogs, SIGNAL(triggered()), this, SLOT(onSampleLogsClicked())); + connect(m_showInstrument, SIGNAL(triggered()), this, + SLOT(onShowInstrumentClicked())); } WorkspaceTreeWidgetSimple::~WorkspaceTreeWidgetSimple() {} @@ -83,9 +88,12 @@ void WorkspaceTreeWidgetSimple::popupContextMenu() { plotSubMenu->addAction(m_plotColorfill); menu->addMenu(plotSubMenu); menu->addSeparator(); + menu->addAction(m_showInstrument); + menu->addSeparator(); } menu->addAction(m_rename); menu->addAction(m_saveNexus); + menu->addAction(m_sampleLogs); menu->addSeparator(); menu->addAction(m_delete); @@ -115,5 +123,13 @@ void WorkspaceTreeWidgetSimple::onPlotColorfillClicked() { emit plotColorfillClicked(getSelectedWorkspaceNamesAsQList()); } +void WorkspaceTreeWidgetSimple::onSampleLogsClicked() { + emit sampleLogsClicked(getSelectedWorkspaceNamesAsQList()); +} + +void WorkspaceTreeWidgetSimple::onShowInstrumentClicked() { + emit showInstrumentClicked(getSelectedWorkspaceNamesAsQList()); +} + } // namespace MantidWidgets } // namespace MantidQt diff --git a/qt/widgets/common/test/AlgorithmHintStrategyTest.h b/qt/widgets/common/test/AlgorithmHintStrategyTest.h index 39117fe151485fdc7f25dfa42f2def44dc904aea..7dad9a8fdfb8d74f171621d126513391281842d7 100644 --- a/qt/widgets/common/test/AlgorithmHintStrategyTest.h +++ b/qt/widgets/common/test/AlgorithmHintStrategyTest.h @@ -16,8 +16,6 @@ #include "MantidQtWidgets/Common/HintStrategy.h" #include <cxxtest/TestSuite.h> -#include <boost/scoped_ptr.hpp> - using namespace MantidQt::MantidWidgets; using namespace Mantid::API; diff --git a/qt/widgets/common/test/DataProcessorUI/GenericDataProcessorPresenterTest.h b/qt/widgets/common/test/DataProcessorUI/GenericDataProcessorPresenterTest.h index ff0fde37d86509305921dd87ec0af606c8bf0ddb..4a9e06dcc03466a81752d89aeacda62b21e48ec6 100644 --- a/qt/widgets/common/test/DataProcessorUI/GenericDataProcessorPresenterTest.h +++ b/qt/widgets/common/test/DataProcessorUI/GenericDataProcessorPresenterTest.h @@ -261,7 +261,7 @@ private: << "1.6" << "0.04" << "1" - << "ProcessingInstructions='0'"; + << "ProcessingInstructions='1'"; row = ws->appendRow(); row << "0" << "12346" @@ -271,7 +271,7 @@ private: << "2.9" << "0.04" << "1" - << "ProcessingInstructions='0'"; + << "ProcessingInstructions='1'"; row = ws->appendRow(); row << "1" << "24681" @@ -281,7 +281,7 @@ private: << "1.6" << "0.04" << "1" - << "ProcessingInstructions='0'"; + << "ProcessingInstructions='1'"; row = ws->appendRow(); row << "1" << "24682" @@ -291,7 +291,7 @@ private: << "2.9" << "0.04" << "1" - << "ProcessingInstructions='0'"; + << "ProcessingInstructions='1'"; return ws; } @@ -1652,7 +1652,7 @@ public: << "1.6" << "0.04" << "1" - << "ProcessingInstructions='0'"; + << "ProcessingInstructions='1'"; row = ws->appendRow(); row << "1" << "dataB" @@ -1662,7 +1662,7 @@ public: << "2.9" << "0.04" << "1" - << "ProcessingInstructions='0'"; + << "ProcessingInstructions='1'"; createTOFWorkspace("dataA"); createTOFWorkspace("dataB"); @@ -2421,7 +2421,7 @@ public: rowlist[0].insert(1); const auto expected = QString( - "0\t12346\t1.5\t\t1.4\t2.9\t0.04\t1\tProcessingInstructions='0'\t"); + "0\t12346\t1.5\t\t1.4\t2.9\t0.04\t1\tProcessingInstructions='1'\t"); // The user hits "copy selected" with the second and third rows selected EXPECT_CALL(mockDataProcessorView, setClipboard(expected)); @@ -2470,10 +2470,10 @@ public: rowlist[1].insert(1); const auto expected = QString( - "0\t12345\t0.5\t\t0.1\t1.6\t0.04\t1\tProcessingInstructions='0'\t\n" - "0\t12346\t1.5\t\t1.4\t2.9\t0.04\t1\tProcessingInstructions='0'\t\n" - "1\t24681\t0.5\t\t0.1\t1.6\t0.04\t1\tProcessingInstructions='0'\t\n" - "1\t24682\t1.5\t\t1.4\t2.9\t0.04\t1\tProcessingInstructions='0'\t"); + "0\t12345\t0.5\t\t0.1\t1.6\t0.04\t1\tProcessingInstructions='1'\t\n" + "0\t12346\t1.5\t\t1.4\t2.9\t0.04\t1\tProcessingInstructions='1'\t\n" + "1\t24681\t0.5\t\t0.1\t1.6\t0.04\t1\tProcessingInstructions='1'\t\n" + "1\t24682\t1.5\t\t1.4\t2.9\t0.04\t1\tProcessingInstructions='1'\t"); // The user hits "copy selected" with the second and third rows selected EXPECT_CALL(mockDataProcessorView, setClipboard(expected)); @@ -2501,7 +2501,7 @@ public: rowlist[0].insert(1); const auto expected = QString( - "0\t12346\t1.5\t\t1.4\t2.9\t0.04\t1\tProcessingInstructions='0'\t"); + "0\t12346\t1.5\t\t1.4\t2.9\t0.04\t1\tProcessingInstructions='1'\t"); // The user hits "copy selected" with the second and third rows selected EXPECT_CALL(mockDataProcessorView, setClipboard(expected)); @@ -2542,9 +2542,9 @@ public: rowlist[1].insert(0); const auto expected = QString( - "0\t12345\t0.5\t\t0.1\t1.6\t0.04\t1\tProcessingInstructions='0'\t\n" - "0\t12346\t1.5\t\t1.4\t2.9\t0.04\t1\tProcessingInstructions='0'\t\n" - "1\t24681\t0.5\t\t0.1\t1.6\t0.04\t1\tProcessingInstructions='0'\t"); + "0\t12345\t0.5\t\t0.1\t1.6\t0.04\t1\tProcessingInstructions='1'\t\n" + "0\t12346\t1.5\t\t1.4\t2.9\t0.04\t1\tProcessingInstructions='1'\t\n" + "1\t24681\t0.5\t\t0.1\t1.6\t0.04\t1\tProcessingInstructions='1'\t"); // The user hits "copy selected" with the second and third rows selected EXPECT_CALL(mockDataProcessorView, setClipboard(expected)); diff --git a/qt/widgets/common/test/FileDialogHandlerTest.h b/qt/widgets/common/test/FileDialogHandlerTest.h index 480c6ededec10c06ea1ddde77995b9873a63809f..81af5b9af54ec5ae64db4d4bcdcc8fa03fd4f224 100644 --- a/qt/widgets/common/test/FileDialogHandlerTest.h +++ b/qt/widgets/common/test/FileDialogHandlerTest.h @@ -17,37 +17,46 @@ public: const QString singleExt(".nxs (*.nxs)"); const QString nexusResult("/tmp/testing.nxs"); - auto result1 = MantidQt::API::FileDialogHandler::addExtension( + auto result = MantidQt::API::FileDialogHandler::addExtension( QString::fromStdString("/tmp/testing"), singleExt); - TS_ASSERT_EQUALS(nexusResult.toStdString(), result1.toStdString()); + TS_ASSERT_EQUALS(nexusResult.toStdString(), result.toStdString()); - auto result2 = MantidQt::API::FileDialogHandler::addExtension( + result = MantidQt::API::FileDialogHandler::addExtension( QString::fromStdString("/tmp/testing."), singleExt); - TS_ASSERT_EQUALS(nexusResult.toStdString(), result2.toStdString()); + TS_ASSERT_EQUALS(nexusResult.toStdString(), result.toStdString()); - auto result3 = + result = MantidQt::API::FileDialogHandler::addExtension(nexusResult, singleExt); - TS_ASSERT_EQUALS(nexusResult.toStdString(), result3.toStdString()); + TS_ASSERT_EQUALS(nexusResult.toStdString(), result.toStdString()); // don't override if it is already specified const QString singleH5("/tmp/testing.h5"); - auto result4 = + result = MantidQt::API::FileDialogHandler::addExtension(singleH5, singleExt); - TS_ASSERT_EQUALS(singleH5.toStdString(), result4.toStdString()); + TS_ASSERT_EQUALS(singleH5.toStdString(), result.toStdString()); // --- double extensions const QString doubleExt("JPEG (*.jpg *.jpeg)"); const QString jpegResult("/tmp/testing.jpg"); - // this can't work because you can't determine one extension - TS_ASSERT_THROWS(MantidQt::API::FileDialogHandler::addExtension( - QString::fromStdString("/tmp/testing"), doubleExt), - std::runtime_error); + // this picks the first extension in doubleExt + result = MantidQt::API::FileDialogHandler::addExtension( + QString::fromStdString("/tmp/testing"), doubleExt); + TS_ASSERT_EQUALS(jpegResult.toStdString(), result.toStdString()); // this shouldn't do anything - auto result5 = + result = MantidQt::API::FileDialogHandler::addExtension(jpegResult, doubleExt); - TS_ASSERT_EQUALS(jpegResult.toStdString(), result5.toStdString()); + TS_ASSERT_EQUALS(jpegResult.toStdString(), result.toStdString()); + + // Just the wildcard * + const QString wildcardExt("All files (*)"); + const QString wildcardResult("/tmp/testing"); + + // this shouldn't do anything + result = MantidQt::API::FileDialogHandler::addExtension( + QString::fromStdString("/tmp/testing"), wildcardExt); + TS_ASSERT_EQUALS(wildcardResult.toStdString(), result.toStdString()); } void test_getFileDialogFilter() { diff --git a/qt/widgets/instrumentview/CMakeLists.txt b/qt/widgets/instrumentview/CMakeLists.txt index f391ac91f30aacba5a0b9cb54c34513e802332c1..8034f4fbc310306c15f286920ae1b57f61c3eb63 100644 --- a/qt/widgets/instrumentview/CMakeLists.txt +++ b/qt/widgets/instrumentview/CMakeLists.txt @@ -1,47 +1,51 @@ set ( SRC_FILES - src/BankRenderingHelpers.cpp - src/BankTextureBuilder.cpp - src/BinDialog.cpp - src/CollapsiblePanel.cpp - src/DetXMLFile.cpp - src/GLColor.cpp - src/GLObject.cpp - src/InstrumentActor.cpp - src/InstrumentTreeModel.cpp - src/InstrumentTreeWidget.cpp - src/InstrumentWidget.cpp - src/InstrumentWidgetMaskTab.cpp - src/InstrumentWidgetPickTab.cpp - src/InstrumentWidgetRenderTab.cpp - src/InstrumentRenderer.cpp - src/InstrumentWidgetTab.cpp - src/InstrumentWidgetTreeTab.cpp - src/MantidGLWidget.cpp - src/MaskBinsData.cpp - src/OpenGLError.cpp - src/PanelsSurface.cpp - src/PeakMarker2D.cpp - src/PeakOverlay.cpp - src/Projection3D.cpp - src/ProjectionSurface.cpp - src/RectF.cpp - src/RotationSurface.cpp - src/Shape2D.cpp - src/Shape2DCollection.cpp - src/SimpleWidget.cpp - src/UCorrectionDialog.cpp - src/UnwrappedCylinder.cpp - src/UnwrappedDetector.cpp - src/UnwrappedSphere.cpp - src/UnwrappedSurface.cpp - src/Viewport.cpp - src/XIntegrationControl.cpp + src/BankRenderingHelpers.cpp + src/BankTextureBuilder.cpp + src/BinDialog.cpp + src/CollapsiblePanel.cpp + src/DetXMLFile.cpp + src/GLColor.cpp + src/GLObject.cpp + src/InstrumentActor.cpp + src/InstrumentTreeModel.cpp + src/InstrumentTreeWidget.cpp + src/InstrumentWidget.cpp + src/InstrumentWidgetMaskTab.cpp + src/InstrumentWidgetPickTab.cpp + src/InstrumentWidgetRenderTab.cpp + src/InstrumentRenderer.cpp + src/InstrumentWidgetTab.cpp + src/InstrumentWidgetTreeTab.cpp + src/MantidGLWidget.cpp + src/MaskBinsData.cpp + src/OpenGLError.cpp + src/PanelsSurface.cpp + src/PeakMarker2D.cpp + src/PeakOverlay.cpp + src/Projection3D.cpp + src/ProjectionSurface.cpp + src/RectF.cpp + src/RotationSurface.cpp + src/Shape2D.cpp + src/Shape2DCollection.cpp + src/SimpleWidget.cpp + src/UCorrectionDialog.cpp + src/UnwrappedCylinder.cpp + src/UnwrappedDetector.cpp + src/UnwrappedSphere.cpp + src/UnwrappedSurface.cpp + src/Viewport.cpp + src/XIntegrationControl.cpp ) set ( QT4_SRC_FILES src/MiniPlotQwt.cpp ) +set ( QT5_SRC_FILES + src/MiniPlotMpl.cpp +) + set ( MOC_FILES inc/MantidQtWidgets/InstrumentView/BinDialog.h inc/MantidQtWidgets/InstrumentView/CollapsiblePanel.h @@ -68,51 +72,52 @@ set ( QT4_MOC_FILES inc/MantidQtWidgets/InstrumentView/MiniPlotQwt.h ) -set ( INC_FILES - inc/MantidQtWidgets/InstrumentView/BankRenderingHelpers.h - inc/MantidQtWidgets/InstrumentView/BankTextureBuilder.h - inc/MantidQtWidgets/InstrumentView/BinDialog.h - inc/MantidQtWidgets/InstrumentView/CollapsiblePanel.h - inc/MantidQtWidgets/InstrumentView/DetXMLFile.h - inc/MantidQtWidgets/InstrumentView/DllOption.h - inc/MantidQtWidgets/InstrumentView/GLColor.h - inc/MantidQtWidgets/InstrumentView/GLObject.h - inc/MantidQtWidgets/InstrumentView/GridTextureFace.h - inc/MantidQtWidgets/InstrumentView/InstrumentActor.h - inc/MantidQtWidgets/InstrumentView/InstrumentTreeModel.h - inc/MantidQtWidgets/InstrumentView/InstrumentTreeWidget.h - inc/MantidQtWidgets/InstrumentView/InstrumentWidget.h - inc/MantidQtWidgets/InstrumentView/InstrumentRenderer.h - inc/MantidQtWidgets/InstrumentView/InstrumentWidgetMaskTab.h - inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h - inc/MantidQtWidgets/InstrumentView/InstrumentWidgetRenderTab.h - inc/MantidQtWidgets/InstrumentView/InstrumentWidgetTab.h - inc/MantidQtWidgets/InstrumentView/InstrumentWidgetTreeTab.h - inc/MantidQtWidgets/InstrumentView/InstrumentWidgetTypes.h - inc/MantidQtWidgets/InstrumentView/MantidGLWidget.h - inc/MantidQtWidgets/InstrumentView/MaskBinsData.h - inc/MantidQtWidgets/InstrumentView/OpenGLError.h - inc/MantidQtWidgets/InstrumentView/PanelsSurface.h - inc/MantidQtWidgets/InstrumentView/PeakMarker2D.h - inc/MantidQtWidgets/InstrumentView/PeakOverlay.h - inc/MantidQtWidgets/InstrumentView/Projection3D.h - inc/MantidQtWidgets/InstrumentView/ProjectionSurface.h - inc/MantidQtWidgets/InstrumentView/RectF.h - inc/MantidQtWidgets/InstrumentView/RotationSurface.h - inc/MantidQtWidgets/InstrumentView/Shape2D.h - inc/MantidQtWidgets/InstrumentView/Shape2DCollection.h - inc/MantidQtWidgets/InstrumentView/SimpleWidget.h - inc/MantidQtWidgets/InstrumentView/UCorrectionDialog.h - inc/MantidQtWidgets/InstrumentView/UnwrappedCylinder.h - inc/MantidQtWidgets/InstrumentView/UnwrappedDetector.h - inc/MantidQtWidgets/InstrumentView/UnwrappedSphere.h - inc/MantidQtWidgets/InstrumentView/UnwrappedSurface.h - inc/MantidQtWidgets/InstrumentView/Viewport.h - inc/MantidQtWidgets/InstrumentView/XIntegrationControl.h +set ( QT5_MOC_FILES + inc/MantidQtWidgets/InstrumentView/MiniPlotMpl.h ) -set ( QT4_INC_FILES +set ( INC_FILES + inc/MantidQtWidgets/InstrumentView/BankRenderingHelpers.h + inc/MantidQtWidgets/InstrumentView/BankTextureBuilder.h + inc/MantidQtWidgets/InstrumentView/BinDialog.h + inc/MantidQtWidgets/InstrumentView/CollapsiblePanel.h inc/MantidQtWidgets/InstrumentView/ColorMap.h + inc/MantidQtWidgets/InstrumentView/DetXMLFile.h + inc/MantidQtWidgets/InstrumentView/DllOption.h + inc/MantidQtWidgets/InstrumentView/GLColor.h + inc/MantidQtWidgets/InstrumentView/GLObject.h + inc/MantidQtWidgets/InstrumentView/GridTextureFace.h + inc/MantidQtWidgets/InstrumentView/InstrumentActor.h + inc/MantidQtWidgets/InstrumentView/InstrumentTreeModel.h + inc/MantidQtWidgets/InstrumentView/InstrumentTreeWidget.h + inc/MantidQtWidgets/InstrumentView/InstrumentWidget.h + inc/MantidQtWidgets/InstrumentView/InstrumentRenderer.h + inc/MantidQtWidgets/InstrumentView/InstrumentWidgetMaskTab.h + inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h + inc/MantidQtWidgets/InstrumentView/InstrumentWidgetRenderTab.h + inc/MantidQtWidgets/InstrumentView/InstrumentWidgetTab.h + inc/MantidQtWidgets/InstrumentView/InstrumentWidgetTreeTab.h + inc/MantidQtWidgets/InstrumentView/InstrumentWidgetTypes.h + inc/MantidQtWidgets/InstrumentView/MantidGLWidget.h + inc/MantidQtWidgets/InstrumentView/MaskBinsData.h + inc/MantidQtWidgets/InstrumentView/OpenGLError.h + inc/MantidQtWidgets/InstrumentView/PanelsSurface.h + inc/MantidQtWidgets/InstrumentView/PeakMarker2D.h + inc/MantidQtWidgets/InstrumentView/PeakOverlay.h + inc/MantidQtWidgets/InstrumentView/Projection3D.h + inc/MantidQtWidgets/InstrumentView/ProjectionSurface.h + inc/MantidQtWidgets/InstrumentView/RectF.h + inc/MantidQtWidgets/InstrumentView/RotationSurface.h + inc/MantidQtWidgets/InstrumentView/Shape2D.h + inc/MantidQtWidgets/InstrumentView/Shape2DCollection.h + inc/MantidQtWidgets/InstrumentView/SimpleWidget.h + inc/MantidQtWidgets/InstrumentView/UCorrectionDialog.h + inc/MantidQtWidgets/InstrumentView/UnwrappedCylinder.h + inc/MantidQtWidgets/InstrumentView/UnwrappedDetector.h + inc/MantidQtWidgets/InstrumentView/UnwrappedSphere.h + inc/MantidQtWidgets/InstrumentView/UnwrappedSurface.h + inc/MantidQtWidgets/InstrumentView/Viewport.h + inc/MantidQtWidgets/InstrumentView/XIntegrationControl.h ) set ( UI_FILES @@ -132,6 +137,8 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsInstrumentView ${INC_FILES} ${QT4_INC_FILES} UI ${UI_FILES} + RES + ../../../images/instrumentview.qrc DEFS IN_MANTIDQT_INSTRUMENTVIEW INCLUDE_DIRS @@ -158,3 +165,45 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsInstrumentView LINUX_INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}" ) + +find_package ( BoostPython REQUIRED ) +mtd_add_qt_library (TARGET_NAME MantidQtWidgetsInstrumentView + QT_VERSION 5 + SRC + ${SRC_FILES} + ${QT5_SRC_FILES} + MOC + ${MOC_FILES} + ${QT5_MOC_FILES} + NOMOC + ${INC_FILES} + ${QT5_INC_FILES} + UI ${UI_FILES} + RES + ../../../images/instrumentview.qrc + DEFS + IN_MANTIDQT_INSTRUMENTVIEW + INCLUDE_DIRS + inc + LINK_LIBS + ${TCMALLOC_LIBRARIES_LINKTIME} + ${CORE_MANTIDLIBS} + PythonInterfaceCore + ${POCO_LIBRARIES} + ${Boost_LIBRARIES} + ${PYTHON_LIBRARIES} + ${OPENGL_gl_LIBRARY} + ${OPENGL_glu_LIBRARY} + QT5_LINK_LIBS + Qt5::OpenGL + MTD_QT_LINK_LIBS + MantidQtWidgetsCommon + MantidQtWidgetsMplCpp + INSTALL_DIR + ${LIB_DIR} + OSX_INSTALL_RPATH + @loader_path/../MacOS + @loader_path/../Libraries + LINUX_INSTALL_RPATH + "\$ORIGIN/../${LIB_DIR}" +) diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/BankTextureBuilder.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/BankTextureBuilder.h index f57e6d4400442db61c5d2ca364396190f4560250..ece5631cc8c28369f7fb0af9d7f5b7fccf42af57 100644 --- a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/BankTextureBuilder.h +++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/BankTextureBuilder.h @@ -1,3 +1,9 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef BANKTEXTUREBUILDER_H #define BANKTEXTUREBUILDER_H @@ -22,6 +28,7 @@ class BankTextureBuilder { public: BankTextureBuilder(const Mantid::Geometry::ComponentInfo &compInfo, size_t index); + ~BankTextureBuilder(); void buildColorTextures(const std::vector<GLColor> &colors, bool isUsingLayer = false, size_t layer = 0); void buildPickTextures(const std::vector<GLColor> &colors, diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/ColorBar.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/ColorBar.h new file mode 100644 index 0000000000000000000000000000000000000000..988d35a72903ff664c98e3bec9d1454a6e563230 --- /dev/null +++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/ColorBar.h @@ -0,0 +1,28 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTIDQT_WIDGETS_INSTRUMENTVIEW_COLORBAR_H +#define MANTIDQT_WIDGETS_INSTRUMENTVIEW_COLORBAR_H + +#include <QtGlobal> + +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +#include "MantidQtWidgets/LegacyQwt/DraggableColorBarWidget.h" +#elif QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) +#include "MantidQtWidgets/MplCpp/ColorbarWidget.h" +#endif + +namespace MantidQt { +namespace MantidWidgets { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +using ColorBar = DraggableColorBarWidget; +#elif QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) +using ColorBar = MantidQt::Widgets::MplCpp::ColorbarWidget; +#endif +} +} // namespace MantidQt + +#endif // MANTIDQT_WIDGETS_INSTRUMENTVIEW_COLORBAR_H diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/ColorMap.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/ColorMap.h index e566526962e11fb90763e8ef835c142394b05d80..b8e464e9a8e1a9d82ab8a8121a1e5cf596e30720 100644 --- a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/ColorMap.h +++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/ColorMap.h @@ -11,15 +11,18 @@ #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #include "MantidQtWidgets/LegacyQwt/MantidColorMap.h" +#elif QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) +#include "MantidQtWidgets/MplCpp/MantidColorMap.h" +#endif namespace MantidQt { namespace MantidWidgets { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) using ColorMap = MantidColorMap; -#else -#error No type defined for ColorMap for Qt >=5! +#elif QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) +using ColorMap = MantidQt::Widgets::MplCpp::MantidColorMap; #endif - -} // namespace MantidWidgets +} } // namespace MantidQt #endif // MANTIDQT_WIDGETS_INSTRUMENTVIEW_COLORMAP_H diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/GridTextureFace.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/GridTextureFace.h index bb663c5f8ef23b73463d87593fab8903f331753b..1b717783bddc675f12c5c7473d31b3bcb7bce0dd 100644 --- a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/GridTextureFace.h +++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/GridTextureFace.h @@ -1,3 +1,9 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef GRIDTEXTUREFACE_H #define GRIDTEXTUREFACE_H namespace MantidQt { diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentActor.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentActor.h index 250a514d386460df4a52b3fd4fa9020ae7e20670..fa73621c44c2cdc875375d615366feb04ed7c46a 100644 --- a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentActor.h +++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentActor.h @@ -7,9 +7,9 @@ #ifndef INSTRUMENTACTOR_H_ #define INSTRUMENTACTOR_H_ -#include "ColorMap.h" -#include "DllOption.h" -#include "GLColor.h" +#include "MantidQtWidgets/InstrumentView/ColorMap.h" +#include "MantidQtWidgets/InstrumentView/DllOption.h" +#include "MantidQtWidgets/InstrumentView/GLColor.h" #include "MantidAPI/MatrixWorkspace_fwd.h" #include "MantidAPI/SpectraDetectorTypes.h" @@ -17,6 +17,8 @@ #include "MantidGeometry/Rendering/OpenGL_Headers.h" #include "MaskBinsData.h" +#include <QObject> + #include <boost/weak_ptr.hpp> #include <vector> @@ -260,7 +262,7 @@ private: /// Flag to show the guide and other components. Loaded and saved in settings. bool m_showGuides; /// Color map scale type: linear or log - GraphOptions::ScaleType m_scaleType; + ColorMap::ScaleType m_scaleType; /// Position to refer to when detector not found const Mantid::Kernel::V3D m_defaultPos; /// Flag which stores whether or not a 3D GridBank is present diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentRenderer.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentRenderer.h index 2e4221d1159b80cd09fc00700a7500905bd5ad5b..a9106b111f1b24cebf74d4d90d3c4665d7b4eda3 100644 --- a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentRenderer.h +++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentRenderer.h @@ -13,9 +13,10 @@ #include "MantidGeometry/Rendering/OpenGL_Headers.h" #include "MantidQtWidgets/InstrumentView/BankTextureBuilder.h" -#include "MantidQtWidgets/LegacyQwt/MantidColorMap.h" +#include "MantidQtWidgets/InstrumentView/ColorMap.h" #include <QString> #include <map> +#include <vector> namespace MantidQt { namespace MantidWidgets { @@ -30,7 +31,6 @@ private: mutable bool m_useDisplayList[2]; mutable std::vector<detail::BankTextureBuilder> m_textures; mutable std::map<size_t, size_t> m_reverseTextureIndexMap; - std::vector<double> m_specIntegrs; ColorMap m_colorMap; bool m_isUsingLayers; size_t m_layer; @@ -45,7 +45,7 @@ public: void reset(); - void changeScaleType(int type); + void changeScaleType(ColorMap::ScaleType type); void changeNthPower(double nth_power); diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidget.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidget.h index c40c51ddd09839d8736487ff7340a8e839826bd5..894fbf23d90fac94384d5e5c514506ac7ddfddb2 100644 --- a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidget.h +++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidget.h @@ -118,7 +118,7 @@ public: void setColorMapMaxValue(double maxValue); void setColorMapRange(double minValue, double maxValue); void selectComponent(const QString &name); - void setScaleType(GraphOptions::ScaleType type); + void setScaleType(ColorMap::ScaleType type); void setExponent(double nth_power); void setViewType(const QString &type); const InstrumentActor &getInstrumentActor() const { @@ -222,7 +222,7 @@ private slots: protected: void init(bool resetGeometry, bool autoscaling, double scaleMin, - double scaleMax, bool setDefaultView); + double scaleMax, bool setDefaultView, bool resetActor = true); /// Set newly created projection surface void setSurface(ProjectionSurface *surface); QWidget *createInstrumentTreeTab(QTabWidget *ControlsTab); diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h index 4f237ee57d4819d12f5f02a646bb0521ca929f88..e52a888e2e50285b9cf6a1cefbdf256e600e8b0a 100644 --- a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h +++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h @@ -9,6 +9,7 @@ #include "MantidQtWidgets/InstrumentView/InstrumentWidgetTab.h" #include "MantidQtWidgets/InstrumentView/MantidGLWidget.h" +#include "MantidQtWidgets/InstrumentView/MiniPlot.h" #include "MantidAPI/MatrixWorkspace_fwd.h" #include "MantidGeometry/Crystal/IPeak.h" @@ -30,7 +31,6 @@ namespace MantidQt { namespace MantidWidgets { class InstrumentActor; class CollapsiblePanel; -class MiniPlotQwt; class ProjectionSurface; class ComponentInfoController; class DetectorPlotController; @@ -124,7 +124,7 @@ private: QColor getShapeBorderColor() const; /* Pick tab controls */ - MiniPlotQwt *m_plot; ///< Miniplot to display data in the detectors + MiniPlot *m_plot; ///< Miniplot to display data in the detectors QLabel *m_activeTool; ///< Displays a tip on which tool is currently selected QPushButton *m_zoom; ///< Button switching on navigation mode QPushButton *m_one; ///< Button switching on single detector selection mode @@ -236,7 +236,7 @@ public: }; DetectorPlotController(InstrumentWidgetPickTab *tab, - InstrumentWidget *instrWidget, MiniPlotQwt *plot); + InstrumentWidget *instrWidget, MiniPlot *plot); void setEnabled(bool on) { m_enabled = on; } void setPlotData(size_t pickID); void setPlotData(const std::vector<size_t> &detIndices); @@ -276,7 +276,7 @@ private: InstrumentWidgetPickTab *m_tab; InstrumentWidget *m_instrWidget; - MiniPlotQwt *m_plot; + MiniPlot *m_plot; PlotType m_plotType; bool m_enabled; diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetRenderTab.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetRenderTab.h index f61dac2d62584f66159f4f70d36b553e59ea5981..9dbce826c9508ad28556dc454918dc63bb76e35f 100644 --- a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetRenderTab.h +++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetRenderTab.h @@ -7,11 +7,11 @@ #ifndef INSTRUMENTWIDGETRENDERTAB_H_ #define INSTRUMENTWIDGETRENDERTAB_H_ +#include "ColorBar.h" #include "ColorMap.h" #include "InstrumentWidgetTab.h" #include "MantidQtWidgets/Common/GraphOptions.h" -#include "MantidQtWidgets/Common/TSVSerialiser.h" class QPushButton; class QLineEdit; @@ -30,7 +30,6 @@ namespace MantidQt { namespace MantidWidgets { class InstrumentWidget; class BinDialog; -class DraggableColorBarWidget; /** * Implements the Render tab in InstrumentWidget. @@ -41,12 +40,13 @@ class EXPORT_OPT_MANTIDQT_INSTRUMENTVIEW InstrumentWidgetRenderTab public: explicit InstrumentWidgetRenderTab(InstrumentWidget *instrWindow); - ~InstrumentWidgetRenderTab() override; + ~InstrumentWidgetRenderTab(); void initSurface() override; void saveSettings(QSettings &) const override; void loadSettings(const QSettings &) override; - GraphOptions::ScaleType getScaleType() const; - void setScaleType(GraphOptions::ScaleType type); + // legacy interface for MantidPlot python api + ColorMap::ScaleType getScaleType() const; + void setScaleType(ColorMap::ScaleType type); void setAxis(const QString &axisName); bool areAxesOn() const; void setupColorBar(const ColorMap &, double, double, double, bool); @@ -107,7 +107,7 @@ private: // methods private: // members QPushButton *m_surfaceTypeButton; QPushButton *mSaveImage; - DraggableColorBarWidget *m_colorMapWidget; + ColorBar *m_colorBarWidget; QFrame *m_resetViewFrame; QComboBox *mAxisCombo; QCheckBox *m_flipCheckBox; diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/MiniPlot.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/MiniPlot.h new file mode 100644 index 0000000000000000000000000000000000000000..f8ee8784c5b1186eeb53b0c86d1bac51ede80f3c --- /dev/null +++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/MiniPlot.h @@ -0,0 +1,28 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTIDQT_WIDGETS_INSTRUMENTVIEW_MINIPLOT_H +#define MANTIDQT_WIDGETS_INSTRUMENTVIEW_MINIPLOT_H + +#include <QtGlobal> + +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +#include "MantidQtWidgets/InstrumentView/MiniPlotQwt.h" +#elif QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) +#include "MantidQtWidgets/InstrumentView/MiniPlotMpl.h" +#endif + +namespace MantidQt { +namespace MantidWidgets { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +using MiniPlot = MiniPlotQwt; +#elif QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) +using MiniPlot = MiniPlotMpl; +#endif +} +} // namespace MantidQt + +#endif // MANTIDQT_WIDGETS_INSTRUMENTVIEW_MINIPLOT_H diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/MiniPlotMpl.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/MiniPlotMpl.h new file mode 100644 index 0000000000000000000000000000000000000000..5d8da592e2d66d025488f3bc012418d67465017d --- /dev/null +++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/MiniPlotMpl.h @@ -0,0 +1,84 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTIDQTWIDGETS_INSTRUMENTVIEW_MINIPLOTMPL_H +#define MANTIDQTWIDGETS_INSTRUMENTVIEW_MINIPLOTMPL_H + +#include "MantidQtWidgets/InstrumentView/DllOption.h" +#include "MantidQtWidgets/MplCpp/Cycler.h" +#include "MantidQtWidgets/MplCpp/Line2D.h" +#include "MantidQtWidgets/MplCpp/Zoomer.h" +#include <QWidget> +#include <list> + +class QPushButton; + +namespace MantidQt { +namespace Widgets { +namespace MplCpp { +class FigureCanvasQt; +} +} // namespace Widgets +namespace MantidWidgets { +class PeakMarker2D; + +class EXPORT_OPT_MANTIDQT_INSTRUMENTVIEW MiniPlotMpl : public QWidget { + Q_OBJECT +public: + explicit MiniPlotMpl(QWidget *parent = nullptr); + + void setData(std::vector<double> x, std::vector<double> y, QString xunit, + QString curveLabel); + void setXLabel(QString xunit); + QString label() const { return m_activeCurveLabel; } + void addPeakLabel(const PeakMarker2D *peakMarker); + void clearPeakLabels(); + bool hasCurve() const; + void store(); + bool hasStored() const; + QStringList getLabels() const { return m_storedCurveLabels; } + void removeCurve(const QString &label); + QColor getCurveColor(const QString &label) const; + bool isYLogScale() const; + void replot(); +public slots: + void clearCurve(); + void setYLogScale(); + void setYLinearScale(); + void clearAll(); + // Required to match the interface with MiniPlotQwt but matplotlib + // handles this for us so it is a noop + void recalcAxisDivs() {} +signals: + void showContextMenu(); + void clickedAt(double, double); + +protected: + bool eventFilter(QObject *watched, QEvent *evt) override; + +private: + bool handleMousePressEvent(QMouseEvent *evt); + bool handleMouseReleaseEvent(QMouseEvent *evt); + +private slots: + void onHomeClicked(); + +private: // data + Widgets::MplCpp::FigureCanvasQt *m_canvas; + QPushButton *m_homeBtn; + std::list<Widgets::MplCpp::Line2D> m_lines; + std::list<Widgets::MplCpp::Artist> m_peakLabels; + Widgets::MplCpp::Cycler m_colorCycler; + QString m_xunit; + QString m_activeCurveLabel; + QStringList m_storedCurveLabels; + Widgets::MplCpp::Zoomer m_zoomer; + QPoint m_mousePressPt; +}; +} // namespace MantidWidgets +} // namespace MantidQt + +#endif // MANTIDQTWIDGETS_INSTRUMENTVIEW_MINIPLOTMPL_H diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/MiniPlotQwt.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/MiniPlotQwt.h index 65fb3102cde38025ec8924d8d23995967c9158eb..6b4c17328213da127d11a3b56dbc48495e29e76a 100644 --- a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/MiniPlotQwt.h +++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/MiniPlotQwt.h @@ -30,9 +30,9 @@ class MiniPlotQwt : public QwtPlot { public: explicit MiniPlotQwt(QWidget *parent); ~MiniPlotQwt() override; - void setData(const double *x, const double *y, int dataSize, - const std::string &xUnits = ""); - void setLabel(const QString &label); + void setXLabel(QString xunit); + void setData(std::vector<double> x, std::vector<double> y, QString xunit, + QString curveLabel); QString label() const { return m_label; } void setYAxisLabelRotation(double degrees); void addPeakLabel(const PeakMarker2D *); @@ -46,7 +46,7 @@ public: void recalcXAxisDivs(); void recalcYAxisDivs(); bool isYLogScale() const; - const std::string &getXUnits() const { return m_xUnits; } + QString getXUnits() const { return m_xUnits; } public slots: void setXScale(double from, double to); void setYScale(double from, double to); @@ -75,7 +75,7 @@ private: QMap<QString, QwtPlotCurve *> m_stored; ///< stored curves QList<QColor> m_colors; ///< colors for stored curves int m_colorIndex; - std::string m_xUnits; + QString m_xUnits; }; class PeakLabel : public QwtPlotItem { diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/OpenGLError.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/OpenGLError.h index 1d250a08e4bee3e54cd7557b77464c53194372e1..8ead2d104869119ec654516626e9ab92112a2488 100644 --- a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/OpenGLError.h +++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/OpenGLError.h @@ -7,6 +7,7 @@ #ifndef OPENGLERROR_H_ #define OPENGLERROR_H_ +#include <ostream> #include <stdexcept> #include <string> diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/Projection3D.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/Projection3D.h index e0aecc41f7112a3b3b6a9a23d8c80ec4df4aaada..be306c957483425db52f99d61c9d9a8cd3086c8f 100644 --- a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/Projection3D.h +++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/Projection3D.h @@ -15,7 +15,6 @@ #include <QGLWidget> #include <QString> -#include <boost/scoped_ptr.hpp> #include <boost/shared_ptr.hpp> namespace MantidQt { diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/Shape2D.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/Shape2D.h index 0ad6522c238cced908b52c0fcc3e3a79f578c29b..3f85865b0efeda88f5177c458454ffd80c47e0d9 100644 --- a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/Shape2D.h +++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/Shape2D.h @@ -7,7 +7,6 @@ #ifndef MANTIDPLOT_SHAPE2D_H_ #define MANTIDPLOT_SHAPE2D_H_ -#include "MantidQtWidgets/Common/TSVSerialiser.h" #include "RectF.h" #include <QColor> diff --git a/qt/widgets/instrumentview/src/BankTextureBuilder.cpp b/qt/widgets/instrumentview/src/BankTextureBuilder.cpp index c53b3d884d2474e6970d2d56dc65cdf19f3f1dfe..60c2cc28e9c81d9f969d7cafe89fc22ec2630255 100644 --- a/qt/widgets/instrumentview/src/BankTextureBuilder.cpp +++ b/qt/widgets/instrumentview/src/BankTextureBuilder.cpp @@ -1,3 +1,9 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/InstrumentView/BankTextureBuilder.h" #include "MantidGeometry/Instrument/ComponentInfo.h" #include "MantidQtWidgets/InstrumentView/BankRenderingHelpers.h" @@ -65,7 +71,7 @@ void addColorsToTopAndBottomTextures( } void upload2DTexture(const std::pair<size_t, size_t> &textSizes, - GLuint &textureID, std::vector<char> &texture) { + GLuint &textureID, const std::vector<char> &texture) { auto w = textSizes.first; auto h = textSizes.second; @@ -121,6 +127,18 @@ BankTextureBuilder::BankTextureBuilder( } } +BankTextureBuilder::~BankTextureBuilder() { + for (size_t i = 0; i < m_colorTextureIDs.size(); ++i) { + auto &colTextureID = m_colorTextureIDs[i]; + auto &pickTextureID = m_pickTextureIDs[i]; + + if (colTextureID > 0) + glDeleteTextures(1, &colTextureID); + if (pickTextureID > 0) + glDeleteTextures(1, &pickTextureID); + } +} + /** Generate and store color textures for bank. This method results in opengl calls to * register texture Ids and build textures in memory. @@ -204,7 +222,7 @@ void BankTextureBuilder::buildTubeBankTextures( texture.resize(children.size() * 3); for (size_t i = 0; i < children.size(); ++i) { - auto col = colors[children[i]]; + const auto &col = colors[children[i]]; auto pos = i * 3; texture[pos] = static_cast<unsigned char>(col.red()); texture[pos + 1] = static_cast<unsigned char>(col.green()); diff --git a/qt/widgets/instrumentview/src/InstrumentActor.cpp b/qt/widgets/instrumentview/src/InstrumentActor.cpp index 430b898a2f91de33f11781ea4d7e9eac6c5eb468..209c717d7f76130ed057a2c714a2fd23d1e53897 100644 --- a/qt/widgets/instrumentview/src/InstrumentActor.cpp +++ b/qt/widgets/instrumentview/src/InstrumentActor.cpp @@ -5,7 +5,9 @@ // & Institut Laue - Langevin // SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/InstrumentView/InstrumentActor.h" +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #include "MantidQtWidgets/Common/TSVSerialiser.h" +#endif #include "MantidQtWidgets/InstrumentView/InstrumentRenderer.h" #include "MantidQtWidgets/InstrumentView/OpenGLError.h" @@ -679,7 +681,7 @@ const std::vector<Mantid::detid_t> &InstrumentActor::getAllDetIDs() const { * @param type :: 0 - linear, 1 - log10. */ void InstrumentActor::changeScaleType(int type) { - m_renderer->changeScaleType(type); + m_renderer->changeScaleType(ColorMap::ScaleType(type)); resetColors(); } @@ -691,10 +693,10 @@ void InstrumentActor::changeNthPower(double nth_power) { void InstrumentActor::loadSettings() { QSettings settings; settings.beginGroup("Mantid/InstrumentWidget"); - m_scaleType = static_cast<GraphOptions::ScaleType>( - settings.value("ScaleType", 0).toInt()); + m_scaleType = ColorMap::ScaleType(settings.value("ScaleType", 0).toInt()); // Load Colormap. If the file is invalid the default stored colour map is used - m_currentCMap = settings.value("ColormapFile", "").toString(); + m_currentCMap = + settings.value("ColormapFile", ColorMap::defaultColorMap()).toString(); // Set values from settings m_showGuides = settings.value("ShowGuides", false).toBool(); settings.endGroup(); @@ -704,7 +706,8 @@ void InstrumentActor::saveSettings() { QSettings settings; settings.beginGroup("Mantid/InstrumentWidget"); settings.setValue("ColormapFile", m_currentCMap); - settings.setValue("ScaleType", (int)m_renderer->getColorMap().getScaleType()); + settings.setValue("ScaleType", + static_cast<int>(m_renderer->getColorMap().getScaleType())); settings.setValue("ShowGuides", m_showGuides); settings.endGroup(); } @@ -1178,6 +1181,7 @@ InstrumentActor::getStringParameter(const std::string &name, * @return string representing the current state of the instrumet actor. */ std::string InstrumentActor::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv; const std::string currentColorMap = getCurrentColorMap().toStdString(); @@ -1186,6 +1190,10 @@ std::string InstrumentActor::saveToProject() const { tsv.writeSection("binmasks", m_maskBinsData.saveToProject()); return tsv.outputLines(); +#else + throw std::runtime_error( + "InstrumentActor::saveToProject() not implemented for Qt >= 5"); +#endif } /** @@ -1193,6 +1201,7 @@ std::string InstrumentActor::saveToProject() const { * @param lines :: string representing the current state of the instrumet actor. */ void InstrumentActor::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv(lines); if (tsv.selectLine("FileName")) { QString filename; @@ -1205,6 +1214,11 @@ void InstrumentActor::loadFromProject(const std::string &lines) { tsv >> binMaskLines; m_maskBinsData.loadFromProject(binMaskLines); } +#else + Q_UNUSED(lines); + throw std::runtime_error( + "InstrumentActor::saveToProject() not implemented for Qt >= 5"); +#endif } bool InstrumentActor::hasGridBank() const { return m_hasGrid; } diff --git a/qt/widgets/instrumentview/src/InstrumentRenderer.cpp b/qt/widgets/instrumentview/src/InstrumentRenderer.cpp index 0f6c3614ccf3e9138a9d7b1a3d25ee97be0f8bd7..09a8e2560cd1618e055ced0afb64008194bbd1a7 100644 --- a/qt/widgets/instrumentview/src/InstrumentRenderer.cpp +++ b/qt/widgets/instrumentview/src/InstrumentRenderer.cpp @@ -330,37 +330,55 @@ void InstrumentRenderer::reset() { } void InstrumentRenderer::resetColors() { - QwtDoubleInterval qwtInterval(m_actor.minValue(), m_actor.maxValue()); auto sharedWorkspace = m_actor.getWorkspace(); + const double vmin(m_actor.minValue()), vmax(m_actor.maxValue()); + // Reset all colors to 0 and resize m_colors to the appropriate size + const auto zero = m_colorMap.rgb(vmin, vmax, 0); const auto &compInfo = m_actor.componentInfo(); - const auto &detInfo = m_actor.detectorInfo(); - auto color = m_colorMap.rgb(qwtInterval, 0); m_colors.assign(compInfo.size(), - GLColor(qRed(color), qGreen(color), qBlue(color), 1)); - auto invalidColor = GLColor(80, 80, 80, 1); - auto maskedColor = GLColor(100, 100, 100, 1); + GLColor(qRed(zero), qGreen(zero), qBlue(zero), 1)); + // No data/masked colors + static const auto invalidColor = GLColor(80, 80, 80, 1); + static const auto maskedColor = GLColor(100, 100, 100, 1); - Mantid::API::IMaskWorkspace_sptr mask = m_actor.getMaskWorkspaceIfExists(); - const auto &detectorIDs = detInfo.detectorIDs(); + // Compute required colors for the detectors in a single shot to avoid + // repeated calls to python and back in the matplotlib-based implementation + const auto &detInfo = m_actor.detectorInfo(); + std::vector<double> counts(detInfo.size()); for (size_t det = 0; det < detInfo.size(); ++det) { - auto masked = false; + counts[det] = m_actor.getIntegratedCounts(det); + } + auto rgba = m_colorMap.rgb(vmin, vmax, counts); - if (mask) - masked = mask->isMasked(detectorIDs[det]); - if (detInfo.isMasked(det) || masked) - m_colors[det] = maskedColor; - else { - auto integratedValue = m_actor.getIntegratedCounts(det); + // Now apply colors taking into account detectors with bad counts and detector + // masking + Mantid::API::IMaskWorkspace_sptr maskWS = m_actor.getMaskWorkspaceIfExists(); + const auto &detectorIDs = detInfo.detectorIDs(); + // Defines a mask checker lambda dependent on if we have a mask workspace or + // not. Done once outside the loop to avoid repeated if branches in the loop + std::function<bool(size_t)> isMasked; + if (maskWS) { + isMasked = [&detInfo, &detectorIDs, &maskWS](size_t index) { + return maskWS->isMasked(detectorIDs[index]) && detInfo.isMasked(index); + }; + } else { + isMasked = [&detInfo](size_t index) { return detInfo.isMasked(index); }; + } + for (size_t det = 0; det < counts.size(); ++det) { + if (!isMasked(det)) { + const double integratedValue(counts[det]); if (integratedValue > -1) { - auto color = m_colorMap.rgb(qwtInterval, integratedValue); - m_colors[det] = GLColor( - qRed(color), qGreen(color), qBlue(color), - static_cast<int>(255 * (integratedValue / m_actor.maxValue()))); + const auto &color = rgba[det]; + m_colors[det] = + GLColor(qRed(color), qGreen(color), qBlue(color), + static_cast<int>(255 * (integratedValue / vmax))); } else m_colors[det] = invalidColor; + } else { + m_colors[det] = maskedColor; } } - + // finish off rest of the components with the mask color for (const auto comp : m_actor.components()) m_colors[comp] = maskedColor; } @@ -374,8 +392,8 @@ void InstrumentRenderer::resetPickColors() { } } -void InstrumentRenderer::changeScaleType(int type) { - m_colorMap.changeScaleType(static_cast<GraphOptions::ScaleType>(type)); +void InstrumentRenderer::changeScaleType(ColorMap::ScaleType type) { + m_colorMap.changeScaleType(type); } void InstrumentRenderer::changeNthPower(double nth_power) { @@ -413,4 +431,4 @@ void InstrumentRenderer::loadColorMap(const QString &fname) { m_colorMap.loadMap(fname); } } // namespace MantidWidgets -} // namespace MantidQt \ No newline at end of file +} // namespace MantidQt diff --git a/qt/widgets/instrumentview/src/InstrumentWidget.cpp b/qt/widgets/instrumentview/src/InstrumentWidget.cpp index a535be0ecd439d63e69a25fb298d596c3e967aad..511aabe24a2d3cb06edf874b5040cadaeb153b5a 100644 --- a/qt/widgets/instrumentview/src/InstrumentWidget.cpp +++ b/qt/widgets/instrumentview/src/InstrumentWidget.cpp @@ -8,7 +8,9 @@ #include "MantidGeometry/Instrument/ComponentInfo.h" #include "MantidGeometry/Instrument/DetectorInfo.h" #include "MantidQtWidgets/Common/MantidDesktopServices.h" +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #include "MantidQtWidgets/Common/TSVSerialiser.h" +#endif #include "MantidQtWidgets/InstrumentView/DetXMLFile.h" #include "MantidQtWidgets/InstrumentView/InstrumentActor.h" #include "MantidQtWidgets/InstrumentView/InstrumentWidgetMaskTab.h" @@ -49,6 +51,7 @@ #include <QLineEdit> #include <QMenu> #include <QMessageBox> +#include <QMimeData> #include <QPushButton> #include <QRadioButton> #include <QSettings> @@ -153,6 +156,7 @@ InstrumentWidget::InstrumentWidget(const QString &wsName, QWidget *parent, m_instrumentActor.reset( new InstrumentActor(m_workspaceName, autoscaling, scaleMin, scaleMax)); + // Create the b=tabs createTabs(settings); @@ -196,7 +200,9 @@ InstrumentWidget::InstrumentWidget(const QString &wsName, QWidget *parent, setWindowTitle(QString("Instrument - ") + m_workspaceName); - init(resetGeometry, autoscaling, scaleMin, scaleMax, setDefaultView); + const bool resetActor(false); + init(resetGeometry, autoscaling, scaleMin, scaleMax, setDefaultView, + resetActor); } /** @@ -254,13 +260,15 @@ InstrumentWidget::getSurfaceAxis(const int surfaceType) const { * @param scaleMax :: Maximum value of the colormap scale. Ignored if * autoscaling == true. * @param setDefaultView :: Set the default surface type + * @param resetActor :: If true reset the instrumentActor object */ void InstrumentWidget::init(bool resetGeometry, bool autoscaling, double scaleMin, double scaleMax, - bool setDefaultView) { - // Previously in (now removed) setWorkspaceName method - m_instrumentActor.reset( - new InstrumentActor(m_workspaceName, autoscaling, scaleMin, scaleMax)); + bool setDefaultView, bool resetActor) { + if (resetActor) { + m_instrumentActor.reset( + new InstrumentActor(m_workspaceName, autoscaling, scaleMin, scaleMax)); + } m_xIntegration->setTotalRange(m_instrumentActor->minBinValue(), m_instrumentActor->maxBinValue()); m_xIntegration->setUnits(QString::fromStdString( @@ -644,8 +652,8 @@ void InstrumentWidget::selectComponent(const QString &name) { * Set the scale type programmatically * @param type :: The scale choice */ -void InstrumentWidget::setScaleType(GraphOptions::ScaleType type) { - emit scaleTypeChanged(type); +void InstrumentWidget::setScaleType(ColorMap::ScaleType type) { + emit scaleTypeChanged(static_cast<int>(type)); } /** @@ -1222,7 +1230,7 @@ void InstrumentWidget::createTabs(QSettings &settings) { * configuration. */ QString InstrumentWidget::getSettingsGroupName() const { - return QString::fromAscii(InstrumentWidgetSettingsGroup); + return QString::fromLatin1(InstrumentWidgetSettingsGroup); } /** @@ -1230,7 +1238,7 @@ QString InstrumentWidget::getSettingsGroupName() const { * configuration. */ QString InstrumentWidget::getInstrumentSettingsGroupName() const { - return QString::fromAscii(InstrumentWidgetSettingsGroup) + "/" + + return QString::fromLatin1(InstrumentWidgetSettingsGroup) + "/" + QString::fromStdString(getInstrumentActor().getInstrumentName()); } @@ -1389,6 +1397,7 @@ int InstrumentWidget::getCurrentTab() const { * @return string representing the current state of the instrumet widget. */ std::string InstrumentWidget::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) TSVSerialiser tsv; // serialise widget properties @@ -1404,6 +1413,10 @@ std::string InstrumentWidget::saveToProject() const { tsv.writeSection("tabs", saveTabs()); return tsv.outputLines(); +#else + throw std::runtime_error( + "InstrumentWidget::saveToProject() not implemented for Qt >= 5"); +#endif } /** @@ -1434,6 +1447,7 @@ void InstrumentWidget::loadTabs(const std::string &lines) const { * file. */ void InstrumentWidget::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) TSVSerialiser tsv(lines); if (tsv.selectLine("SurfaceType")) { @@ -1473,6 +1487,11 @@ void InstrumentWidget::loadFromProject(const std::string &lines) { } updateInstrumentView(); +#else + Q_UNUSED(lines); + throw std::runtime_error( + "InstrumentWidget::loadFromProject() not implemented for Qt >= 5"); +#endif } } // namespace MantidWidgets diff --git a/qt/widgets/instrumentview/src/InstrumentWidgetMaskTab.cpp b/qt/widgets/instrumentview/src/InstrumentWidgetMaskTab.cpp index 5a773408a4affc84c483c0348cc6e6ab34ef55c1..a908b188d5bc3b92cb2c37cab3565f3482a8476b 100644 --- a/qt/widgets/instrumentview/src/InstrumentWidgetMaskTab.cpp +++ b/qt/widgets/instrumentview/src/InstrumentWidgetMaskTab.cpp @@ -5,7 +5,9 @@ // & Institut Laue - Langevin // SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/InstrumentView/InstrumentWidgetMaskTab.h" +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #include "MantidQtWidgets/Common/TSVSerialiser.h" +#endif #include "MantidQtWidgets/InstrumentView/DetXMLFile.h" #include "MantidQtWidgets/InstrumentView/InstrumentActor.h" #include "MantidQtWidgets/InstrumentView/InstrumentWidget.h" @@ -1198,6 +1200,7 @@ void InstrumentWidgetMaskTab::changedIntegrationRange(double, double) { * @param lines :: lines from the project file to load state from */ void InstrumentWidgetMaskTab::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv(lines); if (!tsv.selectSection("masktab")) @@ -1235,6 +1238,11 @@ void InstrumentWidgetMaskTab::loadFromProject(const std::string &lines) { tab >> maskWSName; loadMaskViewFromProject(maskWSName); } +#else + Q_UNUSED(lines); + throw std::runtime_error( + "InstrumentWidgetMaskTab::loadFromProject() not implemented for Qt >= 5"); +#endif } /** Load a mask workspace applied to the instrument actor from the project @@ -1309,6 +1317,7 @@ InstrumentWidgetMaskTab::loadMask(const std::string &fileName) { * @return a string representing the state of the mask tab */ std::string InstrumentWidgetMaskTab::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv; API::TSVSerialiser tab; @@ -1338,6 +1347,10 @@ std::string InstrumentWidgetMaskTab::saveToProject() const { tsv.writeSection("masktab", tab.outputLines()); return tsv.outputLines(); +#else + throw std::runtime_error( + "InstrumentWidgetMaskTab::saveToProject() not implemented for Qt >= 5"); +#endif } /** Save a mask workspace containing masks applied to the instrument view diff --git a/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp b/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp index 5842d4ba4c6e102332e16a063f59bf510176852a..f0ce5d99791c0aa89585fb830c89fece9e16993f 100644 --- a/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp +++ b/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp @@ -5,7 +5,9 @@ // & Institut Laue - Langevin // SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h" +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #include "MantidQtWidgets/Common/TSVSerialiser.h" +#endif #include "MantidQtWidgets/InstrumentView/CollapsiblePanel.h" #include "MantidQtWidgets/InstrumentView/InstrumentActor.h" #include "MantidQtWidgets/InstrumentView/InstrumentWidget.h" @@ -14,8 +16,6 @@ #include "MantidQtWidgets/InstrumentView/ProjectionSurface.h" #include "MantidQtWidgets/InstrumentView/UnwrappedSurface.h" -#include "MantidQtWidgets/InstrumentView/MiniPlotQwt.h" - #include "MantidAPI/AnalysisDataService.h" #include "MantidAPI/Axis.h" #include "MantidAPI/FrameworkManager.h" @@ -91,10 +91,7 @@ InstrumentWidgetPickTab::InstrumentWidgetPickTab(InstrumentWidget *instrWidget) m_selectionInfoDisplay = new QTextEdit(this); // set up the plot widget - m_plot = new MiniPlotQwt(this); - m_plot->setYAxisLabelRotation(-90); - m_plot->setXScale(0, 1); - m_plot->setYScale(-1.2, 1.2); + m_plot = new MiniPlot(this); connect(m_plot, SIGNAL(showContextMenu()), this, SLOT(plotContextMenu())); // Plot context menu actions @@ -297,7 +294,7 @@ bool InstrumentWidgetPickTab::canUpdateTouchedDetector() const { * Display the miniplot's context menu. */ void InstrumentWidgetPickTab::plotContextMenu() { - QMenu context(this); + QMenu context(m_plot); auto plotType = m_plotController->getPlotType(); @@ -600,6 +597,11 @@ void InstrumentWidgetPickTab::initSurface() { static_cast<DetectorPlotController::TubeXUnits>(m_tubeXUnitsCache)); m_plotController->setPlotType( static_cast<DetectorPlotController::PlotType>(m_plotTypeCache)); + // miniplot X unit + const auto &actor = m_instrWidget->getInstrumentActor(); + // default X axis label + m_plot->setXLabel(QString::fromStdString( + actor.getWorkspace()->getAxis(0)->unit()->unitID())); } /** @@ -767,6 +769,7 @@ void InstrumentWidgetPickTab::savePlotToWorkspace() { * @param lines :: lines from the project file to load state from */ void InstrumentWidgetPickTab::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv(lines); if (!tsv.selectSection("picktab")) @@ -788,12 +791,18 @@ void InstrumentWidgetPickTab::loadFromProject(const std::string &lines) { tab >> value; button->setChecked(value); } +#else + Q_UNUSED(lines); + throw std::runtime_error( + "MaskBinsData::loadFromProject() not implemented for Qt >= 5"); +#endif } /** Save the state of the pick tab to a Mantid project file * @return a string representing the state of the pick tab */ std::string InstrumentWidgetPickTab::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv, tab; // save active push button @@ -809,6 +818,10 @@ std::string InstrumentWidgetPickTab::saveToProject() const { tsv.writeSection("picktab", tab.outputLines()); return tsv.outputLines(); +#else + throw std::runtime_error( + "MaskBinsData::saveToProject() not implemented for Qt >= 5"); +#endif } //=====================================================================================// @@ -1084,7 +1097,7 @@ void ComponentInfoController::clear() { m_selectionInfoDisplay->clear(); } */ DetectorPlotController::DetectorPlotController(InstrumentWidgetPickTab *tab, InstrumentWidget *instrWidget, - MiniPlotQwt *plot) + MiniPlot *plot) : QObject(tab), m_tab(tab), m_instrWidget(instrWidget), m_plot(plot), m_plotType(Single), m_enabled(true), m_tubeXUnits(DETECTOR_ID), m_currentPickID(std::numeric_limits<size_t>::max()) { @@ -1098,13 +1111,12 @@ DetectorPlotController::DetectorPlotController(InstrumentWidgetPickTab *tab, * @param pickID :: A pick ID of an instrument component. */ void DetectorPlotController::setPlotData(size_t pickID) { - m_currentPickID = std::numeric_limits<size_t>::max(); - if (m_plotType == DetectorSum) { m_plotType = Single; } if (!m_enabled) { + m_currentPickID = std::numeric_limits<size_t>::max(); m_plot->clearCurve(); return; } @@ -1113,14 +1125,18 @@ void DetectorPlotController::setPlotData(size_t pickID) { const auto &componentInfo = actor.componentInfo(); if (componentInfo.isDetector(pickID)) { if (m_plotType == Single) { - m_currentPickID = pickID; - plotSingle(pickID); + if (m_currentPickID != pickID) { + m_currentPickID = pickID; + plotSingle(pickID); + } } else if (m_plotType == TubeSum || m_plotType == TubeIntegral) { + m_currentPickID = std::numeric_limits<size_t>::max(); plotTube(pickID); } else { throw std::logic_error("setPlotData: Unexpected plot type."); } } else { + m_currentPickID = std::numeric_limits<size_t>::max(); m_plot->clearCurve(); } } @@ -1139,10 +1155,11 @@ void DetectorPlotController::setPlotData( actor.sumDetectors(detIndices, x, y, static_cast<size_t>(m_plot->width())); QApplication::restoreOverrideCursor(); if (!x.empty()) { - m_plot->setData(&x[0], &y[0], static_cast<int>(y.size()), - actor.getWorkspace()->getAxis(0)->unit()->unitID()); + m_plot->setData(std::move(x), std::move(y), + QString::fromStdString( + actor.getWorkspace()->getAxis(0)->unit()->unitID()), + "multiple"); } - m_plot->setLabel("multiple"); } /** @@ -1174,10 +1191,11 @@ void DetectorPlotController::plotSingle(size_t detindex) { const auto &actor = m_instrWidget->getInstrumentActor(); // set the data - m_plot->setData(&x[0], &y[0], static_cast<int>(y.size()), - actor.getWorkspace()->getAxis(0)->unit()->unitID()); auto detid = actor.getDetID(detindex); - m_plot->setLabel("Detector " + QString::number(detid)); + m_plot->setData(std::move(x), std::move(y), + QString::fromStdString( + actor.getWorkspace()->getAxis(0)->unit()->unitID()), + "Detector " + QString::number(detid)); // find any markers auto surface = m_tab->getSurface(); @@ -1244,9 +1262,10 @@ void DetectorPlotController::plotTubeSums(size_t detindex) { auto detid = actor.getDetID(detindex); QString label = QString::fromStdString(componentInfo.name(parent)) + " (" + QString::number(detid) + ") Sum"; - m_plot->setData(&x[0], &y[0], static_cast<int>(y.size()), - actor.getWorkspace()->getAxis(0)->unit()->unitID()); - m_plot->setLabel(label); + m_plot->setData(std::move(x), std::move(y), + QString::fromStdString( + actor.getWorkspace()->getAxis(0)->unit()->unitID()), + std::move(label)); } /** @@ -1273,15 +1292,14 @@ void DetectorPlotController::plotTubeIntegrals(size_t detindex) { if (!xAxisUnits.isEmpty()) { xAxisCaption += " (" + xAxisUnits + ")"; } - m_plot->setData(&x[0], &y[0], static_cast<int>(y.size()), - xAxisCaption.toStdString()); auto parent = componentInfo.parent(detindex); // curve label: "tube_name (detid) Integrals" // detid is included to distiguish tubes with the same name QString label = QString::fromStdString(componentInfo.name(parent)) + " (" + QString::number(actor.getDetID(detindex)) + ") Integrals/" + getTubeXUnitsName(); - m_plot->setLabel(label); + m_plot->setData(std::move(x), std::move(y), std::move(xAxisCaption), + std::move(label)); } /** @@ -1659,7 +1677,6 @@ QString DetectorPlotController::getTubeXUnitsUnits() const { default: return ""; } - return ""; } /** diff --git a/qt/widgets/instrumentview/src/InstrumentWidgetRenderTab.cpp b/qt/widgets/instrumentview/src/InstrumentWidgetRenderTab.cpp index 383409afc8d54318c1c0a72c8682c85db05bd3cb..8e916e0cf61ffbaeadef948b4d3c4d540be825ec 100644 --- a/qt/widgets/instrumentview/src/InstrumentWidgetRenderTab.cpp +++ b/qt/widgets/instrumentview/src/InstrumentWidgetRenderTab.cpp @@ -12,6 +12,10 @@ #include "MantidQtWidgets/InstrumentView/UCorrectionDialog.h" #include "MantidQtWidgets/InstrumentView/UnwrappedSurface.h" +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +#include "MantidQtWidgets/Common/TSVSerialiser.h" +#endif + #include <QAction> #include <QActionGroup> #include <QCheckBox> @@ -30,8 +34,6 @@ #include "MantidQtWidgets/InstrumentView/BinDialog.h" #include "MantidQtWidgets/InstrumentView/InstrumentWidget.h" -#include "MantidQtWidgets/LegacyQwt/DraggableColorBarWidget.h" - #include <limits> namespace MantidQt { @@ -75,7 +77,7 @@ InstrumentWidgetRenderTab::InstrumentWidgetRenderTab( renderControlsLayout->addWidget(axisViewFrame); renderControlsLayout->addWidget(displaySettings); renderControlsLayout->addWidget(mSaveImage); - renderControlsLayout->addWidget(m_colorMapWidget); + renderControlsLayout->addWidget(m_colorBarWidget); renderControlsLayout->addWidget(m_autoscaling); // Add GridBank Controls if Grid bank present @@ -230,14 +232,14 @@ QPushButton *InstrumentWidgetRenderTab::setupDisplaySettings() { void InstrumentWidgetRenderTab::setupColorMapWidget() { // Colormap widget - m_colorMapWidget = new DraggableColorBarWidget(0, this); - connect(m_colorMapWidget, SIGNAL(scaleTypeChanged(int)), m_instrWidget, + m_colorBarWidget = new ColorBar(this); + connect(m_colorBarWidget, SIGNAL(scaleTypeChanged(int)), m_instrWidget, SLOT(changeScaleType(int))); - connect(m_colorMapWidget, SIGNAL(nthPowerChanged(double)), m_instrWidget, + connect(m_colorBarWidget, SIGNAL(nthPowerChanged(double)), m_instrWidget, SLOT(changeNthPower(double))); - connect(m_colorMapWidget, SIGNAL(minValueChanged(double)), m_instrWidget, + connect(m_colorBarWidget, SIGNAL(minValueChanged(double)), m_instrWidget, SLOT(changeColorMapMinValue(double))); - connect(m_colorMapWidget, SIGNAL(maxValueChanged(double)), m_instrWidget, + connect(m_colorBarWidget, SIGNAL(maxValueChanged(double)), m_instrWidget, SLOT(changeColorMapMaxValue(double))); } @@ -467,10 +469,10 @@ void InstrumentWidgetRenderTab::saveSettings(QSettings &settings) const { */ void InstrumentWidgetRenderTab::setMinValue(double value, bool apply) { if (!apply) - m_colorMapWidget->blockSignals(true); - m_colorMapWidget->setMinValue(value); + m_colorBarWidget->blockSignals(true); + m_colorBarWidget->setMinValue(value); if (!apply) - m_colorMapWidget->blockSignals(false); + m_colorBarWidget->blockSignals(false); } /** @@ -480,10 +482,10 @@ void InstrumentWidgetRenderTab::setMinValue(double value, bool apply) { */ void InstrumentWidgetRenderTab::setMaxValue(double value, bool apply) { if (!apply) - m_colorMapWidget->blockSignals(true); - m_colorMapWidget->setMaxValue(value); + m_colorBarWidget->blockSignals(true); + m_colorBarWidget->setMaxValue(value); if (!apply) - m_colorMapWidget->blockSignals(false); + m_colorBarWidget->blockSignals(false); } /** @@ -495,19 +497,19 @@ void InstrumentWidgetRenderTab::setMaxValue(double value, bool apply) { void InstrumentWidgetRenderTab::setRange(double minValue, double maxValue, bool apply) { if (!apply) - m_colorMapWidget->blockSignals(true); - m_colorMapWidget->setMinValue(minValue); - m_colorMapWidget->setMaxValue(maxValue); + m_colorBarWidget->blockSignals(true); + m_colorBarWidget->setMinValue(minValue); + m_colorBarWidget->setMaxValue(maxValue); if (!apply) - m_colorMapWidget->blockSignals(false); + m_colorBarWidget->blockSignals(false); } -GraphOptions::ScaleType InstrumentWidgetRenderTab::getScaleType() const { - return (GraphOptions::ScaleType)m_colorMapWidget->getScaleType(); +ColorMap::ScaleType InstrumentWidgetRenderTab::getScaleType() const { + return (ColorMap::ScaleType)m_colorBarWidget->getScaleType(); } -void InstrumentWidgetRenderTab::setScaleType(GraphOptions::ScaleType type) { - m_colorMapWidget->setScaleType(type); +void InstrumentWidgetRenderTab::setScaleType(ColorMap::ScaleType type) { + m_colorBarWidget->setScaleType(static_cast<int>(type)); } void InstrumentWidgetRenderTab::setAxis(const QString &axisNameArg) { @@ -619,10 +621,11 @@ void InstrumentWidgetRenderTab::setupColorBar(const ColorMap &cmap, double minValue, double maxValue, double minPositive, bool autoscaling) { - setMinValue(minValue, false); - setMaxValue(maxValue, false); - m_colorMapWidget->setMinPositiveValue(minPositive); - m_colorMapWidget->setupColorBarScaling(cmap); + m_colorBarWidget->blockSignals(true); + m_colorBarWidget->setClim(minValue, maxValue); + m_colorBarWidget->blockSignals(false); + m_colorBarWidget->setMinPositiveValue(minPositive); + m_colorBarWidget->setupColorBarScaling(cmap); m_autoscaling->blockSignals(true); m_autoscaling->setChecked(autoscaling); m_autoscaling->blockSignals(false); @@ -752,11 +755,11 @@ void InstrumentWidgetRenderTab::colorMapChanged() { } void InstrumentWidgetRenderTab::scaleTypeChanged(int type) { - setScaleType((GraphOptions::ScaleType)type); + setScaleType(static_cast<ColorMap::ScaleType>(type)); } void InstrumentWidgetRenderTab::nthPowerChanged(double nth_power) { - m_colorMapWidget->setNthPower(nth_power); + m_colorBarWidget->setNthPower(nth_power); } /** @@ -833,6 +836,8 @@ QPointF InstrumentWidgetRenderTab::getUCorrection() const { */ std::string MantidQt::MantidWidgets::InstrumentWidgetRenderTab::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + API::TSVSerialiser tab; tab.writeLine("AxesView") << mAxisCombo->currentIndex(); @@ -853,12 +858,15 @@ MantidQt::MantidWidgets::InstrumentWidgetRenderTab::saveToProject() const { tab.writeLine("ShowRelativeIntensity"); tab << surface->getShowPeakRelativeIntensityFlag(); - const auto colorMap = m_colorMapWidget->saveToProject(); + const auto colorMap = m_colorBarWidget->saveToProject(); tab.writeSection("colormap", colorMap); API::TSVSerialiser tsv; tsv.writeSection("rendertab", tab.outputLines()); return tsv.outputLines(); +#else + return ""; +#endif } /** @@ -866,6 +874,7 @@ MantidQt::MantidWidgets::InstrumentWidgetRenderTab::saveToProject() const { * @param lines :: lines defining the state of the render tab */ void InstrumentWidgetRenderTab::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv(lines); if (!tsv.selectSection("rendertab")) @@ -930,8 +939,13 @@ void InstrumentWidgetRenderTab::loadFromProject(const std::string &lines) { if (tab.selectSection("colormap")) { std::string colorMapLines; tab >> colorMapLines; - m_colorMapWidget->loadFromProject(colorMapLines); + m_colorBarWidget->loadFromProject(colorMapLines); } +#else + Q_UNUSED(lines); + throw std::runtime_error( + "InstrumentActor::saveToProject() not implemented for Qt >= 5"); +#endif } } // namespace MantidWidgets diff --git a/qt/widgets/instrumentview/src/InstrumentWidgetTreeTab.cpp b/qt/widgets/instrumentview/src/InstrumentWidgetTreeTab.cpp index dc562183efbd17ea3d116b4b6e3b698f3fa4b42c..5423e694be73acb1eda4815f3e29eab79e623e37 100644 --- a/qt/widgets/instrumentview/src/InstrumentWidgetTreeTab.cpp +++ b/qt/widgets/instrumentview/src/InstrumentWidgetTreeTab.cpp @@ -5,7 +5,9 @@ // & Institut Laue - Langevin // SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/InstrumentView/InstrumentWidgetTreeTab.h" +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #include "MantidQtWidgets/Common/TSVSerialiser.h" +#endif #include "MantidQtWidgets/InstrumentView/InstrumentActor.h" #include "MantidQtWidgets/InstrumentView/InstrumentTreeWidget.h" #include "MantidQtWidgets/InstrumentView/InstrumentWidget.h" @@ -68,6 +70,7 @@ void InstrumentWidgetTreeTab::showEvent(QShowEvent *) { * @param lines :: lines from the project file to load state from */ void InstrumentWidgetTreeTab::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv(lines); if (!tsv.selectSection("treetab")) @@ -91,12 +94,18 @@ void InstrumentWidgetTreeTab::loadFromProject(const std::string &lines) { m_instrumentTree->setExpanded(index, true); } } +#else + Q_UNUSED(lines); + throw std::runtime_error( + "InstrumentWidgetTreeTab::loadFromProject() not implemented for Qt >= 5"); +#endif } /** Save the state of the tree tab to a Mantid project file * @return a string representing the state of the tree tab */ std::string InstrumentWidgetTreeTab::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv, tab; auto index = m_instrumentTree->currentIndex(); @@ -116,6 +125,10 @@ std::string InstrumentWidgetTreeTab::saveToProject() const { tsv.writeSection("treetab", tab.outputLines()); return tsv.outputLines(); +#else + throw std::runtime_error( + "InstrumentWidgetTreeTab::saveToProject() not implemented for Qt >= 5"); +#endif } } // namespace MantidWidgets diff --git a/qt/widgets/instrumentview/src/MaskBinsData.cpp b/qt/widgets/instrumentview/src/MaskBinsData.cpp index 63e098ce02f9a5a441a6b39e1c65933af5514fa1..cfe1c640940381600a13ea2694277a3f9aba788b 100644 --- a/qt/widgets/instrumentview/src/MaskBinsData.cpp +++ b/qt/widgets/instrumentview/src/MaskBinsData.cpp @@ -7,7 +7,10 @@ #include "MantidQtWidgets/InstrumentView/MaskBinsData.h" #include "MantidAPI/AlgorithmManager.h" #include "MantidAPI/MatrixWorkspace.h" + +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #include "MantidQtWidgets/Common/TSVSerialiser.h" +#endif #include <vector> @@ -71,6 +74,7 @@ void MaskBinsData::clear() { m_masks.clear(); } * @param lines :: lines from the project file to load state from */ void MaskBinsData::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv(lines); for (auto &maskLines : tsv.sections("Mask")) { API::TSVSerialiser mask(maskLines); @@ -88,12 +92,18 @@ void MaskBinsData::loadFromProject(const std::string &lines) { addXRange(start, end, spectra); } +#else + Q_UNUSED(lines); + throw std::runtime_error( + "MaskBinsData::loadFromProject() not implemented for Qt >= 5"); +#endif } /** Save the state of the mask bins to a Mantid project file * @return a string representing the state of the mask bins */ std::string MaskBinsData::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv; for (const auto &binMask : m_masks) { API::TSVSerialiser mask; @@ -105,6 +115,10 @@ std::string MaskBinsData::saveToProject() const { tsv.writeSection("Mask", mask.outputLines()); } return tsv.outputLines(); +#else + throw std::runtime_error( + "MaskBinsData::saveToProject() not implemented for Qt >= 5"); +#endif } } // namespace MantidWidgets diff --git a/qt/widgets/instrumentview/src/MiniPlotMpl.cpp b/qt/widgets/instrumentview/src/MiniPlotMpl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ab3f482aee568bb4aa89fb339db75cd0ecae00d8 --- /dev/null +++ b/qt/widgets/instrumentview/src/MiniPlotMpl.cpp @@ -0,0 +1,346 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidQtWidgets/InstrumentView/MiniPlotMpl.h" +#include "MantidPythonInterface/core/GlobalInterpreterLock.h" +#include "MantidPythonInterface/core/VersionCompat.h" +#include "MantidQtWidgets/InstrumentView/PeakMarker2D.h" +#include "MantidQtWidgets/MplCpp/FigureCanvasQt.h" + +#include "MantidKernel/Logger.h" +#include <QApplication> +#include <QContextMenuEvent> +#include <QDir> +#include <QGridLayout> +#include <QIcon> +#include <QMouseEvent> +#include <QPushButton> +#include <QSpacerItem> +#include <QVBoxLayout> + +using Mantid::PythonInterface::GlobalInterpreterLock; +using MantidQt::Widgets::MplCpp::FigureCanvasQt; +using MantidQt::Widgets::MplCpp::cycler; + +namespace { +const char *ACTIVE_CURVE_FORMAT = "k-"; +const char *STORED_LINE_COLOR_CYCLE = "bgrcmyk"; +const char *LIN_SCALE_NAME = "linear"; +const char *LOG_SCALE_NAME = "symlog"; +Mantid::Kernel::Logger g_log("MiniPlotMpl"); + +QPushButton *createHomeButton() { + using MantidQt::Widgets::MplCpp::Python::NewRef; + using MantidQt::Widgets::MplCpp::Python::Object; + + auto mpl(NewRef(PyImport_ImportModule("matplotlib"))); + QDir dataPath(TO_CSTRING(Object(mpl.attr("get_data_path")()).ptr())); + dataPath.cd("images"); + QIcon icon(dataPath.absoluteFilePath("home.png")); + auto iconSize(icon.availableSizes().front()); + auto button = new QPushButton(icon, ""); + button->setMaximumSize(iconSize + QSize(5, 5)); + return button; +} + +/** + * Check if size(X)==size(Y) and both are not empty + * @param x A reference to the X data vector + * @param y A reference to the Y data vector + * @return True if a warning was produced, false otherwise + */ +bool warnDataInvalid(const std::vector<double> &x, + const std::vector<double> &y) { + if (x.size() != y.size()) { + g_log.warning(std::string( + "setData(): X/Y size mismatch! X=" + std::to_string(x.size()) + + ", Y=" + std::to_string(y.size()))); + return true; + } + if (x.empty()) { + g_log.warning("setData(): X & Y arrays are empty!"); + return true; + } + return false; +} + +} // namespace + +namespace MantidQt { +namespace MantidWidgets { + +/** + * Construct a blank miniplot with a single subplot + * @param parent A pointer to its parent widget + */ +MiniPlotMpl::MiniPlotMpl(QWidget *parent) + : QWidget(parent), m_canvas(new FigureCanvasQt(111)), + m_homeBtn(createHomeButton()), m_lines(), m_peakLabels(), + m_colorCycler(cycler("color", STORED_LINE_COLOR_CYCLE)), m_xunit(), + m_activeCurveLabel(), m_storedCurveLabels(), m_zoomer(m_canvas), + m_mousePressPt() { + auto plotLayout = new QGridLayout(this); + plotLayout->setContentsMargins(0, 0, 0, 0); + plotLayout->setSpacing(0); + // We intentionally place the canvas and home button in the same location + // in the grid layout so that they overlap and take up less space. + plotLayout->addWidget(m_canvas, 0, 0); + plotLayout->addWidget(m_homeBtn, 0, 0, Qt::AlignLeft | Qt::AlignBottom); + setLayout(plotLayout); + + // Capture mouse events destined for the plot canvas + m_canvas->installEventFilterToMplCanvas(this); + // Mouse events cause zooming by default. See mouseReleaseEvent + // for exceptions + m_zoomer.enableZoom(true); + connect(m_homeBtn, SIGNAL(clicked()), this, SLOT(onHomeClicked())); +} + +/** + * Set data and metadata for a new curve + * @param x The X-axis data + * @param y The Y-axis data + * @param xunit The X unit a label + * @param curveLabel A label for the curve data + */ +void MiniPlotMpl::setData(std::vector<double> x, std::vector<double> y, + QString xunit, QString curveLabel) { + if (warnDataInvalid(x, y)) + return; + + clearCurve(); + auto axes = m_canvas->gca(); + // plot automatically calls "scalex=True, scaley=True" + m_lines.emplace_back( + axes.plot(std::move(x), std::move(y), ACTIVE_CURVE_FORMAT)); + m_activeCurveLabel = curveLabel; + setXLabel(std::move(xunit)); + // If the current axis limits can fit the data then matplotlib + // won't change the axis scale. If the intensity of different plots + // is very different we need ensure the scale is tight enough to + // see newer plots so we force a recalculation from the data + axes.relim(); + replot(); +} + +/** + * Se the X unit label on the axis + * @param xunit A string giving the X unit + */ +void MiniPlotMpl::setXLabel(QString xunit) { + m_canvas->gca().setXLabel(xunit.toLatin1().constData()); + m_xunit = std::move(xunit); +} + +/** + * Add a label to mark a peak to the plot + * @param peakMarker A pointer to a PeakMarker2D object defining + * the marker added to the instrument widget + */ +void MiniPlotMpl::addPeakLabel(const PeakMarker2D *peakMarker) { + if (m_xunit.isEmpty()) + return; + const auto &peak = peakMarker->getPeak(); + double peakX(0.0); + if (m_xunit == "dSpacing") { + peakX = peak.getDSpacing(); + } else if (m_xunit == "Wavelength") { + peakX = peak.getWavelength(); + } else { + peakX = peak.getTOF(); + } + double ymax(1.0), _; + std::tie(_, ymax) = m_canvas->gca().getYLim(); + // arbitrarily place the label at 85% of the y-axis height + const double peakY = 0.85 * ymax; + const QString label(peakMarker->getLabel()); + m_peakLabels.emplace_back( + m_canvas->gca().text(peakX, peakY, label, "center")); +} + +/** + * Clear all peak labels from the canvas + */ +void MiniPlotMpl::clearPeakLabels() { + for (auto &label : m_peakLabels) { + label.remove(); + } + m_peakLabels.clear(); +} + +/** + * @return True if an active curve exists + */ +bool MiniPlotMpl::hasCurve() const { return !m_activeCurveLabel.isEmpty(); } + +/** + * Store the active curve so it is not overridden + * by future plotting. The curve's color is updated using the color cycler + */ +void MiniPlotMpl::store() { + m_storedCurveLabels.append(m_activeCurveLabel); + m_activeCurveLabel.clear(); + // lock required when the dict returned by cycler iterator + // is destroyed + GlobalInterpreterLock lock; + m_lines.back().set(m_colorCycler()); +} + +/** + * @return True if the plot has stored curves + */ +bool MiniPlotMpl::hasStored() const { return !m_storedCurveLabels.isEmpty(); } + +/** + * Remove the stored curve with the given label. If the label is not found this + * does nothing + * @param label A string label for a curve + */ +void MiniPlotMpl::removeCurve(const QString &label) { + auto labelIndex = m_storedCurveLabels.indexOf(label); + if (labelIndex < 0) + return; + m_storedCurveLabels.removeAt(labelIndex); + m_lines.erase(std::next(std::begin(m_lines), labelIndex)); + m_canvas->gca().relim(); + m_canvas->gca().autoscaleView(); +} + +/** + * Retrieve the color of the curve with the given label + * @param label + * @return A QColor defining the color of the curve + */ +QColor MiniPlotMpl::getCurveColor(const QString &label) const { + auto labelIndex = m_storedCurveLabels.indexOf(label); + if (labelIndex < 0) + return QColor(); + auto lineIter = std::next(std::begin(m_lines), labelIndex); + return lineIter->getColor(); +} + +/** + * @return True if the Y scale is logarithmic + */ +bool MiniPlotMpl::isYLogScale() const { + return m_canvas->gca().getYScale() == LOG_SCALE_NAME; +} + +/** + * Redraws the canvas + */ +void MiniPlotMpl::replot() { m_canvas->draw(); } + +/** + * Remove the active curve, keeping any stored curves + */ +void MiniPlotMpl::clearCurve() { + // setData places the latest curve at the back of the vector + if (hasCurve()) { + m_lines.pop_back(); + } + m_activeCurveLabel.clear(); + clearPeakLabels(); +} + +/** + * Set the Y scale to logarithmic + */ +void MiniPlotMpl::setYLogScale() { m_canvas->gca().setYScale(LOG_SCALE_NAME); } + +/** + * Set the Y scale to linear + */ +void MiniPlotMpl::setYLinearScale() { + m_canvas->gca().setYScale(LIN_SCALE_NAME); +} + +/** + * Clear all artists from the canvas + */ +void MiniPlotMpl::clearAll() { + // active curve, labels etc + clearCurve(); + // any stored curves + m_lines.clear(); + replot(); +} + +/** + * Filter events from the underlying matplotlib canvas + * @param watched A pointer to the object being watched + * @param evt A pointer to the generated event + * @return True if the event was filtered, false otherwise + */ +bool MiniPlotMpl::eventFilter(QObject *watched, QEvent *evt) { + Q_UNUSED(watched); + bool stopEvent{false}; + switch (evt->type()) { + case QEvent::ContextMenu: + // handled by mouse press events below as we need to + // stop the canvas getting mouse events in some circumstances + // to disable zooming + stopEvent = true; + break; + case QEvent::MouseButtonPress: + stopEvent = handleMousePressEvent(static_cast<QMouseEvent *>(evt)); + break; + case QEvent::MouseButtonRelease: + stopEvent = handleMouseReleaseEvent(static_cast<QMouseEvent *>(evt)); + break; + default: + break; + } + return stopEvent; +} + +/** + * Handler called when the event filter recieves a mouse press event + * @param evt A pointer to the event + * @return True if the event propagation should be stopped, false otherwise + */ +bool MiniPlotMpl::handleMousePressEvent(QMouseEvent *evt) { + bool stopEvent(false); + // right-click events are reserved for the context menu + // show when the mouse click is released + if (evt->buttons() & Qt::LeftButton) { + m_mousePressPt = evt->pos(); + } else if (evt->buttons() & Qt::RightButton) { + m_mousePressPt = QPoint(); + stopEvent = true; + } + return stopEvent; +} + +/** + * Handler called when the event filter recieves a mouse release event + * @param evt A pointer to the event + * @return True if the event propagation should be stopped, false otherwise + */ +bool MiniPlotMpl::handleMouseReleaseEvent(QMouseEvent *evt) { + bool stopEvent(false); + if (evt->button() == Qt::LeftButton) { + auto mouseReleasePt = evt->pos(); + // A click and release at the same point implies picking on the canvas + // and not a zoom so stop matplotlib getting hold it it + if (mouseReleasePt == m_mousePressPt) { + const auto dataCoords = m_canvas->toDataCoords(mouseReleasePt); + emit clickedAt(dataCoords.x(), dataCoords.y()); + } + } else if (evt->button() == Qt::RightButton) { + stopEvent = true; + emit showContextMenu(); + } + return stopEvent; +} + +/** + * Wire to the home button click + */ +void MiniPlotMpl::onHomeClicked() { m_zoomer.zoomOut(); } + +} // namespace MantidWidgets +} // namespace MantidQt diff --git a/qt/widgets/instrumentview/src/MiniPlotQwt.cpp b/qt/widgets/instrumentview/src/MiniPlotQwt.cpp index 513851ec9f1469d0bf8bf962a87a19b5d075047c..dd0f9fa6b5e3b3a31f492b6e0865f4d51a97c2a1 100644 --- a/qt/widgets/instrumentview/src/MiniPlotQwt.cpp +++ b/qt/widgets/instrumentview/src/MiniPlotQwt.cpp @@ -5,6 +5,7 @@ // & Institut Laue - Langevin // SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/InstrumentView/MiniPlotQwt.h" +#include "MantidKernel/Logger.h" #include "MantidQtWidgets/InstrumentView/PeakMarker2D.h" #include <MantidQtWidgets/LegacyQwt/qwt_compat.h> @@ -24,6 +25,10 @@ #include <cmath> +namespace { +Mantid::Kernel::Logger g_log("MiniPlotQwt"); +} + namespace MantidQt { namespace MantidWidgets { @@ -32,6 +37,7 @@ MiniPlotQwt::MiniPlotQwt(QWidget *parent) const QFont &font = parent->font(); setAxisFont(QwtPlot::xBottom, font); setAxisFont(QwtPlot::yLeft, font); + setYAxisLabelRotation(-90); QwtText dummyText; dummyText.setFont(font); setAxisTitle(xBottom, dummyText); @@ -50,6 +56,10 @@ MiniPlotQwt::MiniPlotQwt(QWidget *parent) m_colorIndex = 0; m_x0 = 0; m_y0 = 0; + + // Initial scales so the plot looks sensible + setXScale(0, 1); + setYScale(-1.2, 1.2); } /** @@ -57,6 +67,15 @@ MiniPlotQwt::MiniPlotQwt(QWidget *parent) */ MiniPlotQwt::~MiniPlotQwt() { clearAll(); } +/** + * Set the X label of the plot + * @param xunit + */ +void MiniPlotQwt::setXLabel(QString xunit) { + m_xUnits = xunit; + this->setAxisTitle(xBottom, m_xUnits); +} + /** * Set the scale of the horizontal axis * @param from :: Minimum value @@ -197,20 +216,36 @@ void MiniPlotQwt::setYScale(double from, double to) { /** * Set the data for the curve to display - * @param x :: A pointer to x values - * @param y :: A pointer to y values - * @param dataSize :: The size of the data + * @param x :: A vector of X values + * @param y :: A vector of Y values * @param xUnits :: Units for the data + * @param curveLabel :: A label for hthe die */ -void MiniPlotQwt::setData(const double *x, const double *y, int dataSize, - const std::string &xUnits) { - m_xUnits = xUnits; +void MiniPlotQwt::setData(std::vector<double> x, std::vector<double> y, + QString xunit, QString curveLabel) { + if (x.empty()) { + g_log.warning("setData(): X array is empty!"); + return; + } + if (y.empty()) { + g_log.warning("setData(): Y array is empty!"); + return; + } + if (x.size() != y.size()) { + g_log.warning(std::string( + "setData(): X/Y size mismatch! X=" + std::to_string(x.size()) + + ", Y=" + std::to_string(y.size()))); + return; + } + + m_xUnits = xunit; + m_label = curveLabel; if (!m_curve) { m_curve = new QwtPlotCurve(); m_curve->attach(this); } - - m_curve->setData(x, y, dataSize); + int dataSize = static_cast<int>(x.size()); + m_curve->setData(x.data(), y.data(), dataSize); setXScale(x[0], x[dataSize - 1]); double from = y[0]; double to = from; @@ -222,14 +257,8 @@ void MiniPlotQwt::setData(const double *x, const double *y, int dataSize, to = yy; } setYScale(from, to); - this->setAxisTitle(xBottom, QString::fromStdString(m_xUnits)); } -/** - * Set a label which will identify the curve when it is stored. - */ -void MiniPlotQwt::setLabel(const QString &label) { m_label = label; } - /** * Remove the curve. Rescale the axes if there are stored curves. */ @@ -466,7 +495,7 @@ void PeakLabel::draw(QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRect &canvasRect) const { (void)yMap; double peakX; - if (m_plot->getXUnits().empty()) + if (m_plot->getXUnits().isEmpty()) return; if (m_plot->getXUnits() == "dSpacing") { peakX = m_marker->getPeak().getDSpacing(); diff --git a/qt/widgets/instrumentview/src/Projection3D.cpp b/qt/widgets/instrumentview/src/Projection3D.cpp index c14b3de268eb1c7ef26e0c405ad9438f32677513..a2985161e247a6fbe70cc47183aca52d0576b3ce 100644 --- a/qt/widgets/instrumentview/src/Projection3D.cpp +++ b/qt/widgets/instrumentview/src/Projection3D.cpp @@ -17,9 +17,11 @@ #include "MantidGeometry/Instrument/ComponentInfo.h" #include "MantidGeometry/Objects/CSGObject.h" #include "MantidQtWidgets/Common/InputController.h" + +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #include "MantidQtWidgets/Common/TSVSerialiser.h" +#endif -#include <boost/scoped_ptr.hpp> #include <boost/shared_ptr.hpp> #include <QApplication> @@ -458,6 +460,7 @@ void Projection3D::setLightingModel(bool picking) const { * @param lines :: lines from the project file to load state from */ void Projection3D::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) ProjectionSurface::loadFromProject(lines); API::TSVSerialiser tsv(lines); @@ -466,16 +469,26 @@ void Projection3D::loadFromProject(const std::string &lines) { tsv >> viewportLines; m_viewport.loadFromProject(viewportLines); } +#else + Q_UNUSED(lines); + throw std::runtime_error( + "Projection3D::loadFromProject not implemented for Qt >= 5"); +#endif } /** Save the state of the 3D projection to a Mantid project file * @return a string representing the state of the 3D projection */ std::string Projection3D::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv; tsv.writeRaw(ProjectionSurface::saveToProject()); tsv.writeSection("Viewport", m_viewport.saveToProject()); return tsv.outputLines(); +#else + throw std::runtime_error( + "Projection3D::saveToProject not implemented for Qt >= 5"); +#endif } } // namespace MantidWidgets diff --git a/qt/widgets/instrumentview/src/ProjectionSurface.cpp b/qt/widgets/instrumentview/src/ProjectionSurface.cpp index d360ad89808ccae808fbb3d01eb426ce7dd12946..43828bd71a923b151b0fde7073b6ca309665048f 100644 --- a/qt/widgets/instrumentview/src/ProjectionSurface.cpp +++ b/qt/widgets/instrumentview/src/ProjectionSurface.cpp @@ -6,7 +6,9 @@ // SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/InstrumentView/ProjectionSurface.h" #include "MantidQtWidgets/Common/InputController.h" +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #include "MantidQtWidgets/Common/TSVSerialiser.h" +#endif #include "MantidQtWidgets/InstrumentView/GLColor.h" #include "MantidQtWidgets/InstrumentView/InstrumentRenderer.h" #include "MantidQtWidgets/InstrumentView/MantidGLWidget.h" @@ -1004,6 +1006,7 @@ QStringList ProjectionSurface::getPeaksWorkspaceNames() const { * @param lines :: lines from the project file to load state from */ void ProjectionSurface::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv(lines); if (tsv.selectLine("BackgroundColor")) { @@ -1041,12 +1044,18 @@ void ProjectionSurface::loadFromProject(const std::string &lines) { return std::make_pair(qValue, origin); }); } +#else + Q_UNUSED(lines); + throw std::runtime_error( + "ProjectionSurface::loadFromProject() not implemented for Qt >= 5"); +#endif } /** Save the state of the projection surface to a Mantid project file * @return a string representing the state of the projection surface */ std::string ProjectionSurface::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv; tsv.writeLine("BackgroundColor") << m_backgroundColor; tsv.writeSection("shapes", m_maskShapes.saveToProject()); @@ -1060,6 +1069,10 @@ std::string ProjectionSurface::saveToProject() const { tsv.writeSection("AlignmentInfo", alignmentInfo.outputLines()); return tsv.outputLines(); +#else + throw std::runtime_error( + "ProjectionSurface::loadsaveToProject() not implemented for Qt >= 5"); +#endif } } // namespace MantidWidgets diff --git a/qt/widgets/instrumentview/src/Shape2D.cpp b/qt/widgets/instrumentview/src/Shape2D.cpp index fb21f160945dd110cbe3fa98ede702a8828b7826..b9c8b3a0bf461fe3c2be09ae907571a69d94c5db 100644 --- a/qt/widgets/instrumentview/src/Shape2D.cpp +++ b/qt/widgets/instrumentview/src/Shape2D.cpp @@ -5,6 +5,9 @@ // & Institut Laue - Langevin // SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/InstrumentView/Shape2D.h" +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +#include "MantidQtWidgets/Common/TSVSerialiser.h" +#endif #include <QMouseEvent> #include <QPainter> @@ -43,11 +46,10 @@ Shape2D::Shape2D() void Shape2D::draw(QPainter &painter) const { if (!m_visible) return; - painter.setPen(m_color); + painter.setPen(QPen(m_color, 0)); this->drawShape(painter); if (m_editing || m_selected) { - QColor c(255, 255, 255, 100); - painter.setPen(c); + painter.setPen(QPen(QColor(255, 255, 255, 100), 0)); painter.drawRect(m_boundingRect.toQRectF()); size_t np = NCommonCP; double rsize = 2; @@ -63,10 +65,9 @@ void Shape2D::draw(QPainter &painter) const { QRectF r(p - QPointF(rsize, rsize), p + QPointF(rsize, rsize)); painter.save(); painter.resetTransform(); - QColor c(255, 255, 255, alpha); - painter.fillRect(r, c); + painter.fillRect(r, QColor(255, 255, 255, alpha)); r.adjust(-1, -1, 0, 0); - painter.setPen(QColor(0, 0, 0, alpha)); + painter.setPen(QPen(QColor(0, 0, 0, alpha), 0)); painter.drawRect(r); painter.restore(); } @@ -166,6 +167,7 @@ bool Shape2D::isMasked(const QPointF &p) const { * @return a new shape2D with old state applied */ Shape2D *Shape2D::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv(lines); if (!tsv.selectLine("Type")) @@ -201,6 +203,10 @@ Shape2D *Shape2D::loadFromProject(const std::string &lines) { } return shape; +#else + Q_UNUSED(lines); + return nullptr; +#endif } /** @@ -231,6 +237,7 @@ Shape2D *Shape2D::loadShape2DFromType(const std::string &type, * @return a string representing the state of the shape 2D */ std::string Shape2D::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv; bool props[]{m_scalable, m_editing, m_selected, m_visible}; @@ -246,6 +253,10 @@ std::string Shape2D::saveToProject() const { tsv.writeLine("FillColor") << fillColor; return tsv.outputLines(); +#else + throw std::runtime_error( + "InstrumentActor::saveToProject() not implemented for Qt >= 5"); +#endif } // --- Shape2DEllipse --- // @@ -359,17 +370,24 @@ void Shape2DEllipse::setPoint(const QString &prop, const QPointF &value) { * @return a new shape2D in the shape of a ellipse */ Shape2D *Shape2DEllipse::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv(lines); tsv.selectLine("Parameters"); double radius1, radius2, x, y; tsv >> radius1 >> radius2 >> x >> y; return new Shape2DEllipse(QPointF(x, y), radius1, radius2); +#else + Q_UNUSED(lines); + throw std::runtime_error( + "Shape2DEllipse::loadFromProject not implemented for Qt >= 5"); +#endif } /** Save the state of the shape 2D ellipe to a Mantid project file * @return a string representing the state of the shape 2D */ std::string Shape2DEllipse::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv; double radius1 = getDouble("radius1"); double radius2 = getDouble("radius2"); @@ -379,6 +397,10 @@ std::string Shape2DEllipse::saveToProject() const { tsv.writeLine("Parameters") << radius1 << radius2 << centre.x(), centre.y(); tsv.writeRaw(Shape2D::saveToProject()); return tsv.outputLines(); +#else + throw std::runtime_error( + "Shape2DEllipse::saveToProject() not implemented for Qt >= 5"); +#endif } // --- Shape2DRectangle --- // @@ -423,6 +445,7 @@ void Shape2DRectangle::addToPath(QPainterPath &path) const { * @return a new shape2D in the shape of a rectangle */ Shape2D *Shape2DRectangle::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv(lines); tsv.selectLine("Parameters"); double x0, y0, x1, y1; @@ -430,12 +453,18 @@ Shape2D *Shape2DRectangle::loadFromProject(const std::string &lines) { QPointF point1(x0, y0); QPointF point2(x1, y1); return new Shape2DRectangle(point1, point2); +#else + Q_UNUSED(lines); + throw std::runtime_error( + "Shape2DRectangle::loadFromProject not implemented for Qt >= 5"); +#endif } /** Save the state of the shape 2D rectangle to a Mantid project file * @return a string representing the state of the shape 2D */ std::string Shape2DRectangle::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv; auto x0 = m_boundingRect.x0(); auto x1 = m_boundingRect.x1(); @@ -446,6 +475,10 @@ std::string Shape2DRectangle::saveToProject() const { tsv.writeLine("Parameters") << x0 << y0 << x1 << y1; tsv.writeRaw(Shape2D::saveToProject()); return tsv.outputLines(); +#else + throw std::runtime_error( + "Shape2DRectangle::saveToProject() not implemented for Qt >= 5"); +#endif } // --- Shape2DRing --- // @@ -592,6 +625,7 @@ void Shape2DRing::setColor(const QColor &color) { * @return a new shape2D in the shape of a ring */ Shape2D *Shape2DRing::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv(lines); tsv.selectLine("Parameters"); double xWidth, yWidth; @@ -603,12 +637,18 @@ Shape2D *Shape2DRing::loadFromProject(const std::string &lines) { auto baseShape = Shape2D::loadFromProject(baseShapeLines); return new Shape2DRing(baseShape, xWidth, yWidth); +#else + Q_UNUSED(lines); + throw std::runtime_error( + "Shape2DRing::saveToProject() not implemented for Qt >= 5"); +#endif } /** Save the state of the shape 2D ring to a Mantid project file * @return a string representing the state of the shape 2D */ std::string Shape2DRing::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv; auto xWidth = getDouble("xwidth"); auto yWidth = getDouble("ywidth"); @@ -619,6 +659,10 @@ std::string Shape2DRing::saveToProject() const { tsv.writeSection("shape", baseShape->saveToProject()); tsv.writeRaw(Shape2D::saveToProject()); return tsv.outputLines(); +#else + throw std::runtime_error( + "Shape2DRing::saveToProject() not implemented for Qt >= 5"); +#endif } //------------------------------------------------------------------------------ @@ -760,6 +804,7 @@ void Shape2DFree::subtractPolygon(const QPolygonF &polygon) { * @return a new freefrom shape2D */ Shape2D *Shape2DFree::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv(lines); QPolygonF polygon; @@ -773,12 +818,18 @@ Shape2D *Shape2DFree::loadFromProject(const std::string &lines) { } return new Shape2DFree(polygon); +#else + Q_UNUSED(lines); + throw std::runtime_error( + "InstrumentActor::saveToProject() not implemented for Qt >= 5"); +#endif } /** Save the state of the shape 2D to a Mantid project file * @return a string representing the state of the shape 2D */ std::string Shape2DFree::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv; tsv.writeLine("Type") << "free"; @@ -788,6 +839,10 @@ std::string Shape2DFree::saveToProject() const { } tsv.writeRaw(Shape2D::saveToProject()); return tsv.outputLines(); +#else + throw std::runtime_error( + "InstrumentActor::saveToProject() not implemented for Qt >= 5"); +#endif } Shape2DFree::Shape2DFree(const QPolygonF &polygon) : m_polygon(polygon) { diff --git a/qt/widgets/instrumentview/src/Shape2DCollection.cpp b/qt/widgets/instrumentview/src/Shape2DCollection.cpp index 06358be8930cddd615e4f9dd5df9f9381a56ec44..89c8ab737e833adb7a1c00eae00f72e020f343cd 100644 --- a/qt/widgets/instrumentview/src/Shape2DCollection.cpp +++ b/qt/widgets/instrumentview/src/Shape2DCollection.cpp @@ -5,6 +5,9 @@ // & Institut Laue - Langevin // SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/InstrumentView/Shape2DCollection.h" +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +#include "MantidQtWidgets/Common/TSVSerialiser.h" +#endif #include "MantidAPI/AnalysisDataService.h" #include "MantidAPI/ITableWorkspace.h" #include "MantidAPI/TableRow.h" @@ -746,23 +749,34 @@ void Shape2DCollection::eraseFree(const QPolygonF &polygon) { * @param lines :: lines from the project file to load state from */ void Shape2DCollection::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv(lines); for (const auto &shapeLines : tsv.sections("shape")) { Shape2D *shape = Shape2D::loadFromProject(shapeLines); m_shapes.push_back(shape); emit shapeCreated(); } +#else + Q_UNUSED(lines); + throw std::runtime_error( + "Shape2DCollection::loadFromProject() not implemented for Qt >= 5"); +#endif } /** Save the state of the shape 2D collection to a Mantid project file * @return a string representing the state of the shape 2D collection */ std::string Shape2DCollection::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv; for (auto shape : m_shapes) { tsv.writeSection("shape", shape->saveToProject()); } return tsv.outputLines(); +#else + throw std::runtime_error( + "Shape2DCollection::saveToProject() not implemented for Qt >= 5"); +#endif } } // namespace MantidWidgets diff --git a/qt/widgets/instrumentview/src/UnwrappedSurface.cpp b/qt/widgets/instrumentview/src/UnwrappedSurface.cpp index 9bb57139843f32164c91284753ada6a3deaa8244..88dde95341b229773a60ffbe584f2253ec62c4f2 100644 --- a/qt/widgets/instrumentview/src/UnwrappedSurface.cpp +++ b/qt/widgets/instrumentview/src/UnwrappedSurface.cpp @@ -17,6 +17,9 @@ #include "MantidGeometry/Instrument/DetectorInfo.h" #include "MantidGeometry/Objects/CSGObject.h" #include "MantidQtWidgets/Common/InputController.h" +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +#include "MantidQtWidgets/Common/TSVSerialiser.h" +#endif #include <QApplication> #include <QMenu> @@ -655,6 +658,7 @@ void UnwrappedSurface::calcSize(UnwrappedDetector &udet) { * @param lines :: lines from the project file to load state from */ void UnwrappedSurface::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) ProjectionSurface::loadFromProject(lines); API::TSVSerialiser tsv(lines); @@ -679,6 +683,11 @@ void UnwrappedSurface::loadFromProject(const std::string &lines) { setPeaksWorkspace(ws); } } +#else + Q_UNUSED(lines); + throw std::runtime_error( + "UnwrappedSurface::loadFromProject() not implemented for Qt >= 5"); +#endif } /** @@ -706,6 +715,7 @@ UnwrappedSurface::retrievePeaksWorkspace(const std::string &name) const { * @return a string representing the state of the surface */ std::string UnwrappedSurface::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv; tsv.writeRaw(ProjectionSurface::saveToProject()); @@ -719,6 +729,10 @@ std::string UnwrappedSurface::saveToProject() const { } return tsv.outputLines(); +#else + throw std::runtime_error( + "UnwrappedSurface::saveToProject() not implemented for Qt >= 5"); +#endif } } // namespace MantidWidgets diff --git a/qt/widgets/instrumentview/src/Viewport.cpp b/qt/widgets/instrumentview/src/Viewport.cpp index 9be46a5c36bdcf11d20d7d05a1f4804080505e7c..acb688ea341dfaaf65c7fcd1034b806682bcfb71 100644 --- a/qt/widgets/instrumentview/src/Viewport.cpp +++ b/qt/widgets/instrumentview/src/Viewport.cpp @@ -7,7 +7,12 @@ #include "MantidQtWidgets/InstrumentView/Viewport.h" #include "MantidGeometry/Rendering/OpenGL_Headers.h" #include "MantidKernel/V3D.h" + +#include <QtGlobal> +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #include "MantidQtWidgets/Common/TSVSerialiser.h" +#endif + #include "MantidQtWidgets/InstrumentView/OpenGLError.h" #include <cmath> #include <limits> @@ -479,6 +484,7 @@ void Viewport::transform(Mantid::Kernel::V3D &pos) const { } void Viewport::loadFromProject(const std::string &lines) { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) reset(); API::TSVSerialiser tsv(lines); @@ -497,9 +503,15 @@ void Viewport::loadFromProject(const std::string &lines) { tsv >> w >> a >> b >> c; Mantid::Kernel::Quat quat(w, a, b, c); setRotation(quat); +#else + Q_UNUSED(lines); + throw std::runtime_error( + "Viewport::loadFromProject not implemented for Qt >= 5"); +#endif } std::string Viewport::saveToProject() const { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) API::TSVSerialiser tsv; tsv.writeLine("Translation") << m_xTrans << m_yTrans; tsv.writeLine("Zoom") << m_zoomFactor; @@ -510,6 +522,10 @@ std::string Viewport::saveToProject() const { } return tsv.outputLines(); +#else + throw std::runtime_error( + "Viewport::saveToProject() not implemented for Qt >= 5"); +#endif } } // namespace MantidWidgets } // namespace MantidQt diff --git a/qt/widgets/legacyqwt/inc/MantidQtWidgets/LegacyQwt/DraggableColorBarWidget.h b/qt/widgets/legacyqwt/inc/MantidQtWidgets/LegacyQwt/DraggableColorBarWidget.h index ed85663c5d67b10669b193a5c02cabcbb005c5a6..6d4e21a7ba163b0571f5191afe8589b6308ca045 100644 --- a/qt/widgets/legacyqwt/inc/MantidQtWidgets/LegacyQwt/DraggableColorBarWidget.h +++ b/qt/widgets/legacyqwt/inc/MantidQtWidgets/LegacyQwt/DraggableColorBarWidget.h @@ -28,14 +28,15 @@ class EXPORT_OPT_MANTIDQT_LEGACYQWT DraggableColorBarWidget : public QFrame { enum DragType { Bottom, Top }; public: - DraggableColorBarWidget(int type, QWidget *parent, + DraggableColorBarWidget(QWidget *parent, const double &minPositiveValue = 0.0001); void setupColorBarScaling(const MantidColorMap &); + void setClim(double vmin, double vmax); void setMinValue(double); void setMaxValue(double); QString getMinValue() const; QString getMaxValue() const; - QString getNth_power() const; + QString getNthPower() const; void setMinPositiveValue(double); int getScaleType() const; void setScaleType(int); diff --git a/qt/widgets/legacyqwt/inc/MantidQtWidgets/LegacyQwt/MantidColorMap.h b/qt/widgets/legacyqwt/inc/MantidQtWidgets/LegacyQwt/MantidColorMap.h index a930769829d47ad6f1b456d90c52ca25ca764e2b..25da61fcbb65cda730c4f63112699538e6e3ae45 100644 --- a/qt/widgets/legacyqwt/inc/MantidQtWidgets/LegacyQwt/MantidColorMap.h +++ b/qt/widgets/legacyqwt/inc/MantidQtWidgets/LegacyQwt/MantidColorMap.h @@ -22,17 +22,21 @@ class EXPORT_OPT_MANTIDQT_LEGACYQWT MantidColorMap : public QwtColorMap { public: + /// Define the possible scale types + enum class ScaleType { Linear = 0, Log10, Power }; + static QString chooseColorMap(const QString &previousFile, QWidget *parent); + static QString defaultColorMap(); static QString exists(const QString &filename); public: MantidColorMap(); explicit MantidColorMap(const QString &filename, - GraphOptions::ScaleType type); + MantidColorMap::ScaleType type); ~MantidColorMap() override; QwtColorMap *copy() const override; - void changeScaleType(GraphOptions::ScaleType type); + void changeScaleType(ScaleType type); void setNthPower(double nth_power) { m_nth_power = nth_power; }; @@ -44,6 +48,9 @@ public: void setupDefaultMap(); + QRgb rgb(double vmin, double vmax, double value) const; + std::vector<QRgb> rgb(double vmin, double vmax, + const std::vector<double> &values) const; QRgb rgb(const QwtDoubleInterval &interval, double value) const override; double normalize(const QwtDoubleInterval &interval, double value) const; @@ -57,7 +64,7 @@ public: * Retrieve the scale type * @returns the current scale type */ - GraphOptions::ScaleType getScaleType() const { return m_scale_type; } + ScaleType getScaleType() const { return m_scale_type; } /** * Retrieve the map name @@ -87,7 +94,7 @@ public: private: /// The scale choice - mutable GraphOptions::ScaleType m_scale_type; + mutable ScaleType m_scale_type; /// An array of shared pointers to objects that define how the color should be /// painted on diff --git a/qt/widgets/legacyqwt/src/ColorBarWidget.cpp b/qt/widgets/legacyqwt/src/ColorBarWidget.cpp index b2a7d15a53e23810e23f4f0d646f03e29bfacb4b..ec168263c80cc5d934647468bc64fcefde1946fc 100644 --- a/qt/widgets/legacyqwt/src/ColorBarWidget.cpp +++ b/qt/widgets/legacyqwt/src/ColorBarWidget.cpp @@ -33,7 +33,7 @@ ColorBarWidget::ColorBarWidget(QWidget *parent) : QWidget(parent) { ui.cmbScaleType->addItem(tr("linear")); ui.cmbScaleType->addItem(tr("logarithmic")); ui.cmbScaleType->addItem(tr("power")); - m_colorMap.changeScaleType(GraphOptions::Linear); + m_colorMap.changeScaleType(MantidColorMap::ScaleType::Linear); ui.dspnN->setMinimum(-100.0); ui.dspnN->setEnabled(false); @@ -296,7 +296,7 @@ void ColorBarWidget::changedScaleType(int type) { // Record if log scale option is selected m_log = (type == 1); - m_colorMap.changeScaleType(GraphOptions::ScaleType(type)); + m_colorMap.changeScaleType(MantidColorMap::ScaleType(type)); ui.valMin->setLogSteps(m_log); ui.valMax->setLogSteps(m_log); setSpinBoxesSteps(); @@ -363,14 +363,14 @@ void ColorBarWidget::updateColorMap() { // Show the scale on the right double minValue = m_min; double maxValue = m_max; - GraphOptions::ScaleType type = m_colorMap.getScaleType(); - if (type == GraphOptions::Linear) { + MantidColorMap::ScaleType type = m_colorMap.getScaleType(); + if (type == MantidColorMap::ScaleType::Linear) { QwtLinearScaleEngine linScaler; m_colorBar->setScaleDiv( linScaler.transformation(), linScaler.divideScale(minValue, maxValue, maxMajorSteps, 5)); m_colorBar->setColorMap(QwtDoubleInterval(minValue, maxValue), m_colorMap); - } else if (type == GraphOptions::Power) { + } else if (type == MantidColorMap::ScaleType::Power) { PowerScaleEngine powScaler; m_colorBar->setScaleDiv( powScaler.transformation(), diff --git a/qt/widgets/legacyqwt/src/DraggableColorBarWidget.cpp b/qt/widgets/legacyqwt/src/DraggableColorBarWidget.cpp index 0fd16c7c3486c615538297810c2d7a36cae6884c..1dac543468870646287f04d12381de66a1d94cf3 100644 --- a/qt/widgets/legacyqwt/src/DraggableColorBarWidget.cpp +++ b/qt/widgets/legacyqwt/src/DraggableColorBarWidget.cpp @@ -23,12 +23,11 @@ namespace MantidQt { namespace MantidWidgets { /** - * Constructor. - * @param type The scale type, e.g. "Linear" or "Log10" + * Constructor giving a colorbar * @param parent A parent widget * @param minPositiveValue A minimum positive value for the Log10 scale */ -DraggableColorBarWidget::DraggableColorBarWidget(int type, QWidget *parent, +DraggableColorBarWidget::DraggableColorBarWidget(QWidget *parent, const double &minPositiveValue) : QFrame(parent), m_minPositiveValue(minPositiveValue), m_dragging(false), m_y(0), m_dtype(), m_nth_power(2.0) { @@ -65,7 +64,7 @@ DraggableColorBarWidget::DraggableColorBarWidget(int type, QWidget *parent, m_scaleOptions->addItem("Log10", QVariant(GraphOptions::Log10)); m_scaleOptions->addItem("Linear", QVariant(GraphOptions::Linear)); m_scaleOptions->addItem("Power", QVariant(GraphOptions::Power)); - m_scaleOptions->setCurrentIndex(m_scaleOptions->findData(type)); + m_scaleOptions->setCurrentIndex(1); // linear default connect(m_scaleOptions, SIGNAL(currentIndexChanged(int)), this, SLOT(scaleOptionsChanged(int))); @@ -113,14 +112,14 @@ void DraggableColorBarWidget::setupColorBarScaling( double minValue = m_minValueBox->displayText().toDouble(); double maxValue = m_maxValueBox->displayText().toDouble(); - GraphOptions::ScaleType type = colorMap.getScaleType(); - if (type == GraphOptions::Linear) { + auto type = colorMap.getScaleType(); + if (type == MantidColorMap::ScaleType::Linear) { QwtLinearScaleEngine linScaler; m_scaleWidget->setScaleDiv( linScaler.transformation(), linScaler.divideScale(minValue, maxValue, 20, 5)); m_scaleWidget->setColorMap(QwtDoubleInterval(minValue, maxValue), colorMap); - } else if (type == GraphOptions::Power) { + } else if (type == MantidColorMap::ScaleType::Power) { PowerScaleEngine powerScaler; m_scaleWidget->setScaleDiv( powerScaler.transformation(), @@ -146,8 +145,9 @@ void DraggableColorBarWidget::setupColorBarScaling( m_scaleWidget->setColorMap(QwtDoubleInterval(logmin, maxValue), colorMap); } m_scaleOptions->blockSignals(true); - m_scaleOptions->setCurrentIndex(m_scaleOptions->findData(type)); - if (m_scaleOptions->findData(type) == 2) { + m_scaleOptions->setCurrentIndex( + m_scaleOptions->findData(static_cast<int>(type))); + if (m_scaleOptions->findData(static_cast<int>(type)) == 2) { m_dspnN->setEnabled(true); } else { m_dspnN->setEnabled(false); @@ -165,6 +165,17 @@ void DraggableColorBarWidget::maxValueChanged() { emit maxValueChanged(m_maxValueBox->text().toDouble()); } +/** + * Update the minimum and maximum range of the scale + * @param vmin New minimum of the scale + * @param vmax New maximum of the scale + */ + +void DraggableColorBarWidget::setClim(double vmin, double vmax) { + setMinValue(vmin); + setMaxValue(vmax); +} + /** * Set a new min value and update the widget. * @param value :: The new value @@ -206,9 +217,7 @@ QString DraggableColorBarWidget::getMaxValue() const { /** * returns the mnth powder as QString */ -QString DraggableColorBarWidget::getNth_power() const { - return m_dspnN->text(); -} +QString DraggableColorBarWidget::getNthPower() const { return m_dspnN->text(); } /** * Update the min value text box. @@ -340,7 +349,7 @@ void DraggableColorBarWidget::mouseReleaseEvent(QMouseEvent * /*e*/) { std::string DraggableColorBarWidget::saveToProject() const { API::TSVSerialiser tsv; tsv.writeLine("ScaleType") << getScaleType(); - tsv.writeLine("Power") << getNth_power(); + tsv.writeLine("Power") << getNthPower(); tsv.writeLine("MinValue") << getMinValue(); tsv.writeLine("MaxValue") << getMaxValue(); return tsv.outputLines(); diff --git a/qt/widgets/legacyqwt/src/MantidColorMap.cpp b/qt/widgets/legacyqwt/src/MantidColorMap.cpp index 02af8339c544dff44d508e8843540b37a3ae9063..44de9157b77f2eea5b0aeaf18ae35d3b7cb4b08a 100644 --- a/qt/widgets/legacyqwt/src/MantidColorMap.cpp +++ b/qt/widgets/legacyqwt/src/MantidColorMap.cpp @@ -54,6 +54,11 @@ QString MantidColorMap::chooseColorMap(const QString &previousFile, return fileselection; } +/** + * @return An empty string to indicate to load the internal map + */ +QString MantidColorMap::defaultColorMap() { return ""; } + /** * Does the given colormap exist * @param filename A filename to check @@ -76,8 +81,9 @@ QString MantidColorMap::exists(const QString &filename) { * Default */ MantidColorMap::MantidColorMap() - : QwtColorMap(QwtColorMap::Indexed), m_scale_type(GraphOptions::Log10), - m_colors(0), m_num_colors(0), m_name(), m_path(), m_nth_power(2.0) { + : QwtColorMap(QwtColorMap::Indexed), + m_scale_type(MantidColorMap::ScaleType::Log10), m_colors(0), + m_num_colors(0), m_name(), m_path(), m_nth_power(2.0) { m_nan = std::numeric_limits<double>::quiet_NaN(); this->setNanColor(255, 255, 255); setupDefaultMap(); @@ -90,7 +96,7 @@ MantidColorMap::MantidColorMap() * @param type :: The scale type */ MantidColorMap::MantidColorMap(const QString &filename, - GraphOptions::ScaleType type) + MantidColorMap::ScaleType type) : QwtColorMap(QwtColorMap::Indexed), m_scale_type(type), m_colors(0), m_num_colors(0), m_name(), m_path(), m_nth_power(2.0) { m_nan = std::numeric_limits<double>::quiet_NaN(); @@ -123,9 +129,7 @@ QwtColorMap *MantidColorMap::copy() const { * Change the scale type * @param type :: The new scale type */ -void MantidColorMap::changeScaleType(GraphOptions::ScaleType type) { - m_scale_type = type; -} +void MantidColorMap::changeScaleType(ScaleType type) { m_scale_type = type; } //------------------------------------------------------------------------------------------------- /** @@ -289,9 +293,9 @@ double MantidColorMap::normalize(const QwtDoubleInterval &interval, return 1.0; double ratio(0.0); - if (m_scale_type == GraphOptions::Linear) { + if (m_scale_type == ScaleType::Linear) { ratio = (value - interval.minValue()) / width; - } else if (m_scale_type == GraphOptions::Power) { + } else if (m_scale_type == ScaleType::Power) { ratio = (pow(value, m_nth_power) - pow(interval.minValue(), m_nth_power)) / (pow(interval.maxValue(), m_nth_power) - pow(interval.minValue(), m_nth_power)); @@ -309,6 +313,36 @@ double MantidColorMap::normalize(const QwtDoubleInterval &interval, } //------------------------------------------------------------------------------------------------- +/** + * Convenience method to call rbg without constructing a QwtDoubleInterval + * externally + * @param vmin Minimum value of the data range + * @param vmax Maximum value of the data range + * @param value The data value within the above range whose rgb color should + * be identified + */ +QRgb MantidColorMap::rgb(double vmin, double vmax, double value) const { + return rgb(QwtDoubleInterval(vmin, vmax), value); +} + +/** + * @brief Compute RGB color values on the current scale type for the given + * data values + * @param vmin The minimum value of the data range + * @param vmax The maximum value of the data range + * @param values The values to be transformed + * @return An array of QRgb describing the colors + */ +std::vector<QRgb> MantidColorMap::rgb(double vmin, double vmax, + const std::vector<double> &values) const { + const QwtDoubleInterval interval(vmin, vmax); + std::vector<QRgb> rgba(values.size()); + for (size_t i = 0; i < rgba.size(); ++i) { + rgba[i] = rgb(interval, values[i]); + } + return rgba; +} + /** * Compute an rgb value for the given data value and interval * @param interval :: The data range @@ -363,8 +397,8 @@ unsigned char MantidColorMap::colorIndex(const QwtDoubleInterval &interval, QVector<QRgb> MantidColorMap::colorTable(const QwtDoubleInterval &interval) const { // Swicth to linear scaling when computing the lookup table - GraphOptions::ScaleType current_type = m_scale_type; - m_scale_type = GraphOptions::Linear; + ScaleType current_type = m_scale_type; + m_scale_type = ScaleType::Linear; short table_size = (m_num_colors > 1) ? m_num_colors : 2; QVector<QRgb> rgbtable(table_size + 1); diff --git a/qt/widgets/legacyqwt/test/MantidColorMapTest.h b/qt/widgets/legacyqwt/test/MantidColorMapTest.h index 4055c41558fd95394971310f2957a0561a610bab..1cf7fa4ef9b5f4b31b93914f451b4bbc4c06cf32 100644 --- a/qt/widgets/legacyqwt/test/MantidColorMapTest.h +++ b/qt/widgets/legacyqwt/test/MantidColorMapTest.h @@ -23,27 +23,27 @@ public: col = map.rgb(QwtDoubleInterval(0.0, 1.0), 1.0); TSM_ASSERT_EQUALS("Default max color.", col, qRgb(255, 255, 255)); TSM_ASSERT_EQUALS("Default map is linear", map.getScaleType(), - GraphOptions::Log10); + MantidColorMap::ScaleType::Log10); } void test_normalize_linear() { MantidColorMap map; QwtDoubleInterval range(10.0, 20.0); - map.changeScaleType(GraphOptions::Linear); + map.changeScaleType(MantidColorMap::ScaleType::Linear); TS_ASSERT_DELTA(map.normalize(range, 15.), 0.5, 1e-5); } void test_normalize_log() { MantidColorMap map; QwtDoubleInterval range(1.0, 10000.0); - map.changeScaleType(GraphOptions::Log10); + map.changeScaleType(MantidColorMap::ScaleType::Log10); TS_ASSERT_DELTA(map.normalize(range, 1000.), 0.75, 1e-5); } void test_normalize_power() { MantidColorMap map; QwtDoubleInterval range(10.0, 20.0); - map.changeScaleType(GraphOptions::Power); + map.changeScaleType(MantidColorMap::ScaleType::Power); map.setNthPower(2.0); TS_ASSERT_DELTA(map.normalize(range, 16.), 0.52, 1e-5); } diff --git a/qt/widgets/mplcpp/CMakeLists.txt b/qt/widgets/mplcpp/CMakeLists.txt index 97284a2caf175a33c70abf64933f7a1a75b88291..cdb9b1342b330a86c60049022058dab83e796777 100644 --- a/qt/widgets/mplcpp/CMakeLists.txt +++ b/qt/widgets/mplcpp/CMakeLists.txt @@ -2,15 +2,22 @@ set ( LIB_SRCS src/Artist.cpp src/Axes.cpp + src/BackendQt.cpp src/Colors.cpp + src/ColorbarWidget.cpp src/Colormap.cpp + src/ColorConverter.cpp + src/Cycler.cpp src/Figure.cpp src/FigureCanvasQt.cpp src/Line2D.cpp + src/MantidColorMap.cpp src/ScalarMappable.cpp + src/Zoomer.cpp ) set ( MOC_HEADERS + inc/MantidQtWidgets/MplCpp/ColorbarWidget.h inc/MantidQtWidgets/MplCpp/FigureCanvasQt.h ) @@ -19,9 +26,17 @@ set (NOMOC_HEADERS inc/MantidQtWidgets/MplCpp/Axes.h inc/MantidQtWidgets/MplCpp/Colors.h inc/MantidQtWidgets/MplCpp/Colormap.h + inc/MantidQtWidgets/MplCpp/ColorConverter.h + inc/MantidQtWidgets/MplCpp/Cycler.h inc/MantidQtWidgets/MplCpp/Figure.h inc/MantidQtWidgets/MplCpp/Line2D.h + inc/MantidQtWidgets/MplCpp/MantidColorMap.h inc/MantidQtWidgets/MplCpp/ScalarMappable.h + inc/MantidQtWidgets/MplCpp/Zoomer.h +) + +set ( UI_FILES + inc/MantidQtWidgets/MplCpp/Colorbar.ui ) find_package ( BoostPython REQUIRED ) @@ -29,17 +44,22 @@ find_package ( BoostPython REQUIRED ) # Target mtd_add_qt_library (TARGET_NAME MantidQtWidgetsMplCpp QT_VERSION 5 - SRC ${LIB_SRCS} - MOC ${MOC_HEADERS} - NOMOC ${NOMOC_HEADERS} + SRC + ${LIB_SRCS} + MOC + ${MOC_HEADERS} + NOMOC + ${NOMOC_HEADERS} + UI + ${UI_FILES} DEFS IN_MANTIDQT_MPLCPP INCLUDE_DIRS - inc - ../../../Framework/PythonInterface/core/inc - ${Boost_INCLUDE_DIRS} - ${PYTHON_INCLUDE_PATH} - ${PYTHON_NUMPY_INCLUDE_DIR} + inc + ../../../Framework/PythonInterface/core/inc + ${Boost_INCLUDE_DIRS} + ${PYTHON_INCLUDE_PATH} + ${PYTHON_NUMPY_INCLUDE_DIR} LINK_LIBS ${TCMALLOC_LIBRARIES_LINKTIME} ${Boost_LIBRARIES} diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Artist.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Artist.h index 2451d2dcdb6f42d45ef1cbe2b664c0a6eda196c8..278a3cd30e2d7e706479da74dfa387086cc6d03a 100644 --- a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Artist.h +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Artist.h @@ -1,21 +1,12 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_ARTIST_H #define MPLCPP_ARTIST_H -/* - Copyright © 2018 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. -*/ #include "MantidQtWidgets/MplCpp/DllConfig.h" #include "MantidQtWidgets/MplCpp/Python/Object.h" @@ -31,7 +22,7 @@ public: // Holds a reference to the matplotlib artist object explicit Artist(Python::Object obj); - // Remove the artist from the canvas + void set(Python::Dict kwargs); void remove(); }; diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Axes.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Axes.h index 3ebb13b06d230a8114acf8ee124a0fa7129b1931..31c6d0f6fb86accf64f6ba100787c165ffc4ab3e 100644 --- a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Axes.h +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Axes.h @@ -1,24 +1,19 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_AXES_H #define MPLCPP_AXES_H -/* - Copyright © 2018 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. -*/ + #include "MantidQtWidgets/MplCpp/DllConfig.h" #include "MantidQtWidgets/MplCpp/Line2D.h" +#include <QString> + +#include <tuple> + namespace MantidQt { namespace Widgets { namespace MplCpp { @@ -34,10 +29,28 @@ public: void setTitle(const char *label); /// @} - /// @name Plotting + /// @name Drawing /// @{ Line2D plot(std::vector<double> xdata, std::vector<double> ydata, const char *format = "b-"); + Artist text(double x, double y, QString text, + const char *horizontalAlignment); + /// @} + + ///@name Scales + /// @{ + void setXScale(const char *value); + QString getXScale() const; + void setYScale(const char *value); + QString getYScale() const; + std::tuple<double, double> getXLim() const; + void setXLim(double min, double max) const; + std::tuple<double, double> getYLim() const; + void setYLim(double min, double max) const; + void relim(bool visibleOnly = false); + void autoscale(bool enable); + void autoscaleView(bool scaleX = true, bool scaleY = true); + void autoscaleView(bool tight, bool scaleX = true, bool scaleY = true); /// @} }; diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/BackendQt.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/BackendQt.h index 9997a483b179c0c989cf8682c063b5f2fb4c811a..bf01f0fa74b3dead34585e0bcc6541b5867f8d97 100644 --- a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/BackendQt.h +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/BackendQt.h @@ -1,21 +1,13 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_BACKENDQT_H #define MPLCPP_BACKENDQT_H -/* - Copyright © 2018 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. -*/ +#include "MantidQtWidgets/MplCpp/DllConfig.h" #include "MantidQtWidgets/MplCpp/Python/Object.h" #include <QtGlobal> @@ -29,10 +21,11 @@ QT_VERSION < QT_VERSION_CHECK(6, 0, 0) /// Define PyQt version that matches the matplotlib backend -const char *PYQT_MODULE = "PyQt5"; +constexpr static const char *PYQT_MODULE = "PyQt5"; /// Define matplotlib backend that will be used to draw the canvas -const char *MPL_QT_BACKEND = "matplotlib.backends.backend_qt5agg"; +constexpr static const char *MPL_QT_BACKEND = + "matplotlib.backends.backend_qt5agg"; #else #error "Unknown Qt version. Cannot determine matplotlib backend." @@ -43,12 +36,7 @@ namespace Widgets { namespace MplCpp { /// Import and return the backend module for this version of Qt -Python::Object backendModule() { - // Importing PyQt first allows matplotlib to select the correct - // backend - Python::NewRef(PyImport_ImportModule(PYQT_MODULE)); - return Python::NewRef(PyImport_ImportModule(MPL_QT_BACKEND)); -} +MANTID_MPLCPP_DLL Python::Object backendModule(); } // namespace MplCpp } // namespace Widgets diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/ColorConverter.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/ColorConverter.h new file mode 100644 index 0000000000000000000000000000000000000000..01c562d831e6bb257cf64ff4bafc44aa420c0f3e --- /dev/null +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/ColorConverter.h @@ -0,0 +1,32 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MPLCPP_COLORCONVERTER_H +#define MPLCPP_COLORCONVERTER_H + +#include "MantidQtWidgets/MplCpp/DllConfig.h" +#include "MantidQtWidgets/MplCpp/Python/Object.h" + +#include <QColor> + +namespace MantidQt { +namespace Widgets { +namespace MplCpp { + +/** + * A static C++ wrapper around the matplotlib.colors.colorConverter instance. + * It is used to translate colors of various formats to a QColor instance. + */ +class MANTID_MPLCPP_DLL ColorConverter : public Python::InstanceHolder { +public: + static QColor toRGB(const Python::Object &colorSpec); +}; + +} // namespace MplCpp +} // namespace Widgets +} // namespace MantidQt + +#endif // MPLCPP_COLORCONVERTER_H diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Colorbar.ui b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Colorbar.ui new file mode 100644 index 0000000000000000000000000000000000000000..51cb544d392658ede97382d5d637ddb8a7f03466 --- /dev/null +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Colorbar.ui @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Colorbar</class> + <widget class="QWidget" name="Colorbar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>282</width> + <height>407</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout" stretch="1,8,1,1"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>5</number> + </property> + <property name="topMargin"> + <number>5</number> + </property> + <property name="rightMargin"> + <number>5</number> + </property> + <property name="bottomMargin"> + <number>5</number> + </property> + <item> + <layout class="QHBoxLayout" name="scaleMaxLayout" stretch="4,3,4"> + <property name="spacing"> + <number>0</number> + </property> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLineEdit" name="scaleMaxEdit"/> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QWidget" name="mplColorbar" native="true"/> + </item> + <item> + <layout class="QHBoxLayout" name="scaleMinLayout" stretch="4,3,4"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLineEdit" name="scaleMinEdit"/> + </item> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="scaleLayout" stretch="0,0,0,0"> + <item> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QComboBox" name="normTypeOpt"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="powerEdit"> + <property name="maximumSize"> + <size> + <width>50</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>2</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/ColorbarWidget.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/ColorbarWidget.h new file mode 100644 index 0000000000000000000000000000000000000000..c9f9103632cc00e56d5b29f515ae19f6ef3f019e --- /dev/null +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/ColorbarWidget.h @@ -0,0 +1,84 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MPLCPP_COLORBARWIDGET_H +#define MPLCPP_COLORBARWIDGET_H + +#include "MantidQtWidgets/MplCpp/Colors.h" +#include "MantidQtWidgets/MplCpp/DllConfig.h" +#include "MantidQtWidgets/MplCpp/Figure.h" +#include "MantidQtWidgets/MplCpp/ScalarMappable.h" +#include "ui_Colorbar.h" + +#include <tuple> + +namespace MantidQt { +namespace Widgets { +namespace MplCpp { +class FigureCanvasQt; +class MantidColorMap; + +/** + * @brief Provides a widget to display a color map on a defined scale with a + * configurable scale type. It contains controls the scale range + * and type. + + * The implementation uses matplotlib.colorbar. + */ +class MANTID_MPLCPP_DLL ColorbarWidget : public QWidget { + Q_OBJECT +public: + ColorbarWidget(QWidget *parent = nullptr); + + void setNorm(const NormalizeBase &norm); + void setClim(boost::optional<double> vmin, boost::optional<double> vmax); + std::tuple<double, double> clim() const; + + ///@name Legacy API to match DraggableColorBarWidget for instrument view + ///@{ + void setupColorBarScaling(const MantidColorMap &mtdCMap); + void setMinValue(double min); + void setMaxValue(double max); + QString getMinValue() const; + QString getMaxValue() const; + QString getNthPower() const; + void setMinPositiveValue(double) { /*Unused in this implementation*/ + } + int getScaleType() const; + void setScaleType(int); + void setNthPower(double); + void loadFromProject(const std::string &) {} + std::string saveToProject() const { return ""; } +signals: + void scaleTypeChanged(int); + void minValueChanged(double); + void maxValueChanged(double); + void nthPowerChanged(double); + ///@} + +private slots: + void scaleMinimumEdited(); + void scaleMaximumEdited(); + void scaleTypeSelectionChanged(int index); + void powerExponentEdited(); + +private: + void initLayout(); + void createColorbar(const Python::Object &ticks = Python::Object(), + const Python::Object &format = Python::Object()); + void connectSignals(); + +private: // data + Ui::Colorbar m_ui; + FigureCanvasQt *m_canvas{nullptr}; + ScalarMappable m_mappable; +}; + +} // namespace MplCpp +} // namespace Widgets +} // namespace MantidQt + +#endif // MPLCPP_COLORBARWIDGET_H diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Colormap.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Colormap.h index 520d426e8a811e3eb01b41f9c5c7dab1e1e83be3..1de23f008507033811bf6a0f5f774ea5e8be41ee 100644 --- a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Colormap.h +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Colormap.h @@ -1,23 +1,17 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_COLORMAP_H #define MPLCPP_COLORMAP_H -/* - Copyright © 2018 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. -*/ + #include "MantidQtWidgets/MplCpp/DllConfig.h" +#pragma push_macro("slots") +#undef slots #include "MantidQtWidgets/MplCpp/Python/Object.h" +#pragma pop_macro("slots") #include <QString> namespace MantidQt { @@ -25,7 +19,8 @@ namespace Widgets { namespace MplCpp { /** - * @brief Defines a wrapper + * @brief Defines a C++ wrapper for the matplotlib.cm.Colormap + * class */ class MANTID_MPLCPP_DLL Colormap : public Python::InstanceHolder { public: @@ -35,9 +30,15 @@ public: /// Return the matplotlib.cm module MANTID_MPLCPP_DLL Python::Object cmModule(); +/// Check if the named colormap if it exists +MANTID_MPLCPP_DLL bool cmapExists(const QString &name); + /// Return the named colormap if it exists MANTID_MPLCPP_DLL Colormap getCMap(const QString &name); +/// Return the named colormap if it exists +MANTID_MPLCPP_DLL QString defaultCMapName(); + } // namespace MplCpp } // namespace Widgets } // namespace MantidQt diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Colors.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Colors.h index 9c1e592fd0a258c100e37d640610342c5bc2e4e6..33c616532ba6a486547cbf0c4e3579b8f26b76a6 100644 --- a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Colors.h +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Colors.h @@ -1,21 +1,12 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_COLORS_H #define MPLCPP_COLORS_H -/* - Copyright © 2018 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. -*/ #include "MantidQtWidgets/MplCpp/DllConfig.h" #include "MantidQtWidgets/MplCpp/Python/Object.h" @@ -36,7 +27,20 @@ namespace MplCpp { */ class MANTID_MPLCPP_DLL NormalizeBase : public Python::InstanceHolder { public: - NormalizeBase(Python::Object obj); + /// Autoscale the limits to vmin, vmax, clamping any invalid values + std::tuple<double, double> autoscale(std::tuple<double, double> clim); + + /// Return an appropriate object to determine the tick locations + /// The default returns None indicating that matplotlib should autoselect it + virtual Python::Object tickLocator() const { return Python::Object(); } + /// Return an appropriate object to determine the text format type + /// The default returns None indicating that matplotlib should autoselect it + virtual Python::Object labelFormatter() const { return Python::Object(); } + +protected: + // Only to be called by derived classes. They should ensure + // this object is of the correct type + NormalizeBase(Python::Object pyobj); }; /** @@ -47,6 +51,7 @@ public: */ class MANTID_MPLCPP_DLL Normalize : public NormalizeBase { public: + Normalize(); Normalize(double vmin, double vmax); }; @@ -57,7 +62,19 @@ public: */ class MANTID_MPLCPP_DLL SymLogNorm : public NormalizeBase { public: + static double DefaultLinearThreshold; + static double DefaultLinearScale; + +public: + SymLogNorm(double linthresh, double linscale); SymLogNorm(double linthresh, double linscale, double vmin, double vmax); + + Python::Object tickLocator() const override; + Python::Object labelFormatter() const override; + +private: + // cache the linscale as it's not available publicly on the class + double m_linscale; }; /** @@ -67,6 +84,7 @@ public: */ class MANTID_MPLCPP_DLL PowerNorm : public NormalizeBase { public: + PowerNorm(double gamma); PowerNorm(double gamma, double vmin, double vmax); }; diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Cycler.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Cycler.h new file mode 100644 index 0000000000000000000000000000000000000000..271cbf82a543a88f1d615460f7664ccc12d8ed1c --- /dev/null +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Cycler.h @@ -0,0 +1,41 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MPLCPP_CYCLER_H +#define MPLCPP_CYCLER_H + +#include "MantidQtWidgets/MplCpp/DllConfig.h" +#pragma push_macro("slots") +#undef slots +#include "MantidQtWidgets/MplCpp/Python/Object.h" +#pragma pop_macro("slots") + +namespace MantidQt { +namespace Widgets { +namespace MplCpp { + +/** + * @brief The Cycler class combines the functionality of Cycler + * object from the cycler module with Python's itertools.cycle functionality + * to create an interable that endlessly loops around a sequence of values. + * The call operator is used to produce the next value in the cycle + */ +class MANTID_MPLCPP_DLL Cycler : public Python::InstanceHolder { +public: + Cycler(Python::Object obj); + + /// Return the next value in the sequence + Python::Dict operator()() const; +}; + +/// Create a cycler from a string of values +MANTID_MPLCPP_DLL Cycler cycler(const char *label, const char *iterable); + +} // namespace MplCpp +} // namespace Widgets +} // namespace MantidQt + +#endif // MPLCPP_CYCLER_H diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/DllConfig.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/DllConfig.h index b46991b2360e4d1a5cb04ebd3ad77a5e6772111f..d1fa98616c61dab8fda8042004089785f61c1670 100644 --- a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/DllConfig.h +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/DllConfig.h @@ -1,3 +1,9 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MANTIDQT_MPLCPP_DLLCONFIG_H_ #define MANTIDQT_MPLCPP_DLLCONFIG_H_ diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Figure.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Figure.h index 83fbda2dbd7749c6b8d7960703fc133bf6764e12..5e6ab902964edcea2a92552832e488aa1b4cc927 100644 --- a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Figure.h +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Figure.h @@ -1,24 +1,17 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_FIGURE_H #define MPLCPP_FIGURE_H -/* - Copyright © 2018 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. -*/ +#include "MantidPythonInterface/core/GlobalInterpreterLock.h" #include "MantidQtWidgets/MplCpp/Axes.h" #include "MantidQtWidgets/MplCpp/DllConfig.h" #include "MantidQtWidgets/MplCpp/Python/Object.h" +#include "MantidQtWidgets/MplCpp/ScalarMappable.h" namespace MantidQt { namespace Widgets { @@ -29,18 +22,32 @@ namespace MplCpp { */ class MANTID_MPLCPP_DLL Figure : public Python::InstanceHolder { public: + Figure(Python::Object obj); Figure(bool tightLayout = true); + /** + * @brief Access (and create if necessar) the active Axes + * @return An instance of Axes attached to the figure + */ + inline Axes gca() const { + Mantid::PythonInterface::GlobalInterpreterLock lock; + return Axes{pyobj().attr("gca")()}; + } /** * @param index The index of the axes to return * @return The axes instance */ inline Axes axes(size_t index) const { + Mantid::PythonInterface::GlobalInterpreterLock lock; return Axes{pyobj().attr("axes")[index]}; } + void setFaceColor(const char *color); Axes addAxes(double left, double bottom, double width, double height); Axes addSubPlot(int subplotspec); + Python::Object colorbar(const ScalarMappable &mappable, const Axes &cax, + const Python::Object &ticks = Python::Object(), + const Python::Object &format = Python::Object()); }; } // namespace MplCpp diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/FigureCanvasQt.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/FigureCanvasQt.h index 1cc2b81c8241772042eb95061394d68b1179bb49..afd591d941539b91b6838a391b75500986e18d4b 100644 --- a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/FigureCanvasQt.h +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/FigureCanvasQt.h @@ -1,24 +1,16 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_FIGURECANVASQT_H #define MPLCPP_FIGURECANVASQT_H -/* - Copyright © 2018 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. -*/ + #include "MantidQtWidgets/MplCpp/DllConfig.h" #include "MantidQtWidgets/MplCpp/Figure.h" +#include <QPointF> #include <QWidget> namespace MantidQt { @@ -38,11 +30,31 @@ public: FigureCanvasQt(int subplotspec, QWidget *parent = nullptr); FigureCanvasQt(Figure fig, QWidget *parent = nullptr); - /// Non-const access to the current active axes instance. - inline Axes &gca() { return m_axes; } + /// Attach an event filter to the underlying matplotlib canvas + void installEventFilterToMplCanvas(QObject *filter); + /// Access to the current figure instance. + inline Figure gcf() const { return m_figure; } + /// Access to the current active axes instance. + inline Axes gca() const { return m_figure.gca(); } + + /// Convert a point in screen coordinates to data coordinates + QPointF toDataCoords(QPoint pos) const; + + /// Redraw the canvas + inline void draw() { + Mantid::PythonInterface::GlobalInterpreterLock lock; + pyobj().attr("draw")(); + } + /// Redraw the canvas if nothing else is happening + inline void drawIdle() { + Mantid::PythonInterface::GlobalInterpreterLock lock; + pyobj().attr("draw_idle")(); + } private: // members - Axes m_axes; + Figure m_figure; + // A pointer to the C++ widget extract from the Python FigureCanvasQT object + QWidget *m_mplCanvas; }; } // namespace MplCpp diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Line2D.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Line2D.h index 24dd93158b065c4074efd28c0e0c8279bf0b0a61..1ecafad76b027f2f7c275e4940bb30120f222d31 100644 --- a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Line2D.h +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Line2D.h @@ -1,23 +1,15 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_LINE2D_H #define MPLCPP_LINE2D_H -/* - Copyright © 2018 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. -*/ #include "MantidQtWidgets/MplCpp/Artist.h" #include "MantidQtWidgets/MplCpp/DllConfig.h" +#include <QColor> #include <vector> namespace MantidQt { @@ -34,7 +26,7 @@ class MANTID_MPLCPP_DLL Line2D : public Artist { public: Line2D(Python::Object obj, std::vector<double> xdataOwner, std::vector<double> ydataOwner); - ~Line2D(); + ~Line2D() noexcept; // not copyable Line2D(const Line2D &) = delete; Line2D &operator=(const Line2D &) = delete; @@ -42,6 +34,8 @@ public: Line2D(Line2D &&) = default; Line2D &operator=(Line2D &&) = default; + QColor getColor() const; + private: // Containers that own the data making up the line std::vector<double> m_xOwner, m_yOwner; diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/MantidColorMap.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/MantidColorMap.h new file mode 100644 index 0000000000000000000000000000000000000000..53f273000941d1d4a079284d8fca9c9c31067625 --- /dev/null +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/MantidColorMap.h @@ -0,0 +1,61 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MPLCPP_MANTIDCOLORMAP_H +#define MPLCPP_MANTIDCOLORMAP_H + +#include "MantidQtWidgets/MplCpp/DllConfig.h" +#include "MantidQtWidgets/MplCpp/ScalarMappable.h" + +#include <QRgb> +#include <QString> + +// Forward delcarations +class QWidget; + +namespace MantidQt { +namespace Widgets { +namespace MplCpp { + +/** + * @brief The MantidColormap exists to provide an matplotlib-based + * implementation that satisfies the same colormap interface used by the Qt4 + * Qwt-based version in the LegacyQwt library. + */ +class MANTID_MPLCPP_DLL MantidColorMap { +public: + /// Define the possible scale types + enum class ScaleType { Linear = 0, Log10 = 1, Power = 2 }; + + static QString chooseColorMap(const QString &previous, QWidget *parent); + static QString defaultColorMap(); + static QString exists(const QString &name); + +public: + MantidColorMap(); + void setupDefaultMap(); + bool loadMap(const QString &name); + void changeScaleType(ScaleType type); + ScaleType getScaleType() const; + void setNthPower(double gamma); + double getNthPower() const { return m_gamma; } + Colormap cmap() const { return m_mappable.cmap(); } + + QRgb rgb(double vmin, double vmax, double value) const; + std::vector<QRgb> rgb(double vmin, double vmax, + const std::vector<double> &values) const; + +private: + mutable ScalarMappable m_mappable; + ScaleType m_scaleType; + double m_gamma = {2.0}; +}; + +} // namespace MplCpp +} // namespace Widgets +} // namespace MantidQt + +#endif // MPLCPP_MANTIDCOLORMAP_H diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Python/Object.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Python/Object.h index da0f9353bc9eb6e67953de9e8c5686c8d3bca85f..c0c0f9373b38c9d1e361304a85f149f17266e481 100644 --- a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Python/Object.h +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Python/Object.h @@ -1,24 +1,24 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2017 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_PYTHON_OBJECT_H #define MPLCPP_PYTHON_OBJECT_H -/* - Copyright © 2017 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. -*/ + +#include "MantidPythonInterface/core/ErrorHandling.h" +#include "MantidPythonInterface/core/GlobalInterpreterLock.h" #include <boost/python/borrowed.hpp> +#include <boost/python/dict.hpp> #include <boost/python/object.hpp> -#include <stdexcept> + +/** + * @file The intetion of this module is to centralize the access + * to boost::python so that it is not scattered throughout this library. In + * theory updating to a different wrapper library would just require altering + * this file. + */ namespace MantidQt { namespace Widgets { @@ -28,17 +28,33 @@ namespace Python { // Alias for boost python object wrapper using Object = boost::python::object; +// Alias for boost python dict wrapper +using Dict = boost::python::dict; + // Alias for handle wrapping a raw PyObject* template <typename T = PyObject> using Handle = boost::python::handle<T>; -// Alias to borrowed function that increments the reference count -template <typename T> using BorrowedRef = boost::python::detail::borrowed<T>; +// Helper to forward to boost python +inline ssize_t Len(const Python::Object &obj) { + return boost::python::len(obj); +} // Helper to create an Object from a new reference to a raw PyObject* inline Python::Object NewRef(PyObject *obj) { + if (!obj) { + throw Mantid::PythonInterface::PythonException(); + } return Python::Object(Python::Handle<>(obj)); } +// Helper to create an Object from a borrowed reference to a raw PyObject* +inline Python::Object BorrowedRef(PyObject *obj) { + if (!obj) { + throw Mantid::PythonInterface::PythonException(); + } + return Python::Object(Python::Handle<>(boost::python::borrowed(obj))); +} + // Alias for exception indicating Python error handler is set using ErrorAlreadySet = boost::python::error_already_set; @@ -61,12 +77,20 @@ public: * object */ InstanceHolder(Object obj, const char *attr) : m_instance(std::move(obj)) { + Mantid::PythonInterface::GlobalInterpreterLock lock; if (PyObject_HasAttrString(pyobj().ptr(), attr) == 0) { throw std::invalid_argument(std::string("object has no attribute ") + attr); } } + /// The destructor must hold the GIL to be able reduce the refcount of + /// the object + ~InstanceHolder() { + Mantid::PythonInterface::GlobalInterpreterLock lock; + m_instance = Python::Object(); // none + } + /// Return the held instance object inline const Object &pyobj() const { return m_instance; } diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Python/Sip.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Python/Sip.h index 5d495c36c8519f4c5140e6385fd3e5d51662ba2b..25954e54cfc2931ac67a585889b2b752f298df4d 100644 --- a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Python/Sip.h +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Python/Sip.h @@ -1,21 +1,12 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_SIPUTILS_H #define MPLCPP_SIPUTILS_H -/* - Copyright © 2018 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. -*/ #include "MantidQtWidgets/MplCpp/Python/Object.h" #include <sip.h> #include <stdexcept> diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/ScalarMappable.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/ScalarMappable.h index 1f356277f99b19a8ab85c1ce3dc865b15d5ab700..52a807f9d25a4a690975eebc7b93a906d5d66b11 100644 --- a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/ScalarMappable.h +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/ScalarMappable.h @@ -1,26 +1,21 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_SCALARMAPPABLE_H #define MPLCPP_SCALARMAPPABLE_H -/* - Copyright © 2018 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. -*/ + #include "MantidQtWidgets/MplCpp/Colormap.h" #include "MantidQtWidgets/MplCpp/Colors.h" #include "MantidQtWidgets/MplCpp/DllConfig.h" +#include <boost/optional/optional.hpp> + #include <QRgb> +#include <QString> +#include <vector> namespace MantidQt { namespace Widgets { @@ -29,13 +24,22 @@ namespace MplCpp { /** * @brief A C++ wrapper around the matplotlib.cm.ScalarMappable * type to provide the capability to map an arbitrary data - * value to an RGBA value within a given colormap + * value or array of values to an RGBA value(s) within a given colormap */ class MANTID_MPLCPP_DLL ScalarMappable : public Python::InstanceHolder { public: ScalarMappable(const NormalizeBase &norm, const Colormap &cmap); - + ScalarMappable(const NormalizeBase &norm, const QString &cmap); + + Colormap cmap() const; + void setCmap(const Colormap &cmap); + void setCmap(const QString &cmap); + void setNorm(const NormalizeBase &norm); + void setClim(boost::optional<double> vmin = boost::none, + boost::optional<double> vmax = boost::none); QRgb toRGBA(double x, double alpha = 1.0) const; + std::vector<QRgb> toRGBA(const std::vector<double> &x, + double alpha = 1.0) const; }; } // namespace MplCpp diff --git a/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Zoomer.h b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Zoomer.h new file mode 100644 index 0000000000000000000000000000000000000000..700237c703ee1ddf6b5bea69a095af564bcf5ca0 --- /dev/null +++ b/qt/widgets/mplcpp/inc/MantidQtWidgets/MplCpp/Zoomer.h @@ -0,0 +1,43 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MPLCPP_ZOOMER_H +#define MPLCPP_ZOOMER_H + +#include "MantidQtWidgets/MplCpp/DllConfig.h" +#include "MantidQtWidgets/MplCpp/Python/Object.h" + +namespace MantidQt { +namespace Widgets { +namespace MplCpp { +class FigureCanvasQt; + +/** + * @brief The Zoomer class adds zooming capabilities to + * an existing FigureCanvasQt object. The implementation relies on + * the matplotlib NavigationToolbar2 class corresponding to the backend. + * + * This object holds a pointer to the FigureCanvasQt object but + * it will not keep it alive. It is assumed that the canvas lifetime + * is handled separately. + */ +class MANTID_MPLCPP_DLL Zoomer : public Python::InstanceHolder { +public: + explicit Zoomer(FigureCanvasQt *canvas); + + bool isZoomEnabled() const; + void enableZoom(bool requestOn); + void zoomOut(); + +private: + FigureCanvasQt *m_canvas; +}; + +} // namespace MplCpp +} // namespace Widgets +} // namespace MantidQt + +#endif // MPLCPP_ZOOMER_H diff --git a/qt/widgets/mplcpp/src/Artist.cpp b/qt/widgets/mplcpp/src/Artist.cpp index 64bd3509d03a15d9af5fcc9cf48d46459397f3b7..a003e083228aa3cae341b84ef2c1dcbd29c6ef2b 100644 --- a/qt/widgets/mplcpp/src/Artist.cpp +++ b/qt/widgets/mplcpp/src/Artist.cpp @@ -1,6 +1,16 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/MplCpp/Artist.h" +#include "MantidPythonInterface/core/CallMethod.h" #include <cassert> +using Mantid::PythonInterface::GlobalInterpreterLock; +using Mantid::PythonInterface::callMethodNoCheck; + namespace MantidQt { namespace Widgets { namespace MplCpp { @@ -11,10 +21,20 @@ namespace MplCpp { */ Artist::Artist(Python::Object obj) : InstanceHolder(std::move(obj), "draw") {} +/** + * Set properties on the Artist given by the dict of kwargs + * @param kwargs A dict of known matplotlib.artist.Artist properties + */ +void Artist::set(Python::Dict kwargs) { + GlobalInterpreterLock lock; + auto args = Python::NewRef(Py_BuildValue("()")); + pyobj().attr("set")(*args, **kwargs); +} + /** * Call .remove on the underlying artist */ -void Artist::remove() { pyobj().attr("remove")(); } +void Artist::remove() { callMethodNoCheck<void>(pyobj(), "remove"); } } // namespace MplCpp } // namespace Widgets diff --git a/qt/widgets/mplcpp/src/Axes.cpp b/qt/widgets/mplcpp/src/Axes.cpp index 3107c43d828ee545eb79b7ed20aa908142e08c96..e41ca890c8406f2dd1f355b39b8c6d6ab3d7d832 100644 --- a/qt/widgets/mplcpp/src/Axes.cpp +++ b/qt/widgets/mplcpp/src/Axes.cpp @@ -1,7 +1,15 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/MplCpp/Axes.h" +#include "MantidPythonInterface/core/CallMethod.h" #include "MantidPythonInterface/core/Converters/VectorToNDArray.h" #include "MantidPythonInterface/core/Converters/WrapWithNDArray.h" #include "MantidPythonInterface/core/ErrorHandling.h" +#include "MantidPythonInterface/core/VersionCompat.h" namespace MantidQt { namespace Widgets { @@ -9,7 +17,37 @@ namespace MplCpp { using Mantid::PythonInterface::Converters::VectorToNDArray; using Mantid::PythonInterface::Converters::WrapReadOnly; -using Mantid::PythonInterface::PythonRuntimeError; +using Mantid::PythonInterface::GlobalInterpreterLock; +using Mantid::PythonInterface::PythonException; +using Mantid::PythonInterface::callMethodNoCheck; + +namespace { +/** + * Create a QString from a Python string object + * @param pystr A Python string object. This is not checked + * @return A new QString + */ +QString toQString(const Python::Object &pystr) { + return QString::fromLatin1(TO_CSTRING(pystr.ptr())); +} + +/** + * Retrieve axes limits from an axes object as C++ tuple + * @param axes A reference to the axes object + * @param method The accessor name + * @return A 2-tuple of the limits values + */ +std::tuple<double, double> limitsToTuple(const Python::Object &axes, + const char *method) { + auto toDouble = [](const Python::Object &value) { + return PyFloat_AsDouble(value.ptr()); + }; + auto limits = axes.attr(method)(); + return std::make_tuple<double, double>(toDouble(limits[0]), + toDouble(limits[1])); +} + +} // namespace /** * Construct an Axes wrapper around an existing Axes instance @@ -21,19 +59,25 @@ Axes::Axes(Python::Object obj) : InstanceHolder(std::move(obj), "plot") {} * @brief Set the X-axis label * @param label String for the axis label */ -void Axes::setXLabel(const char *label) { pyobj().attr("set_xlabel")(label); } +void Axes::setXLabel(const char *label) { + callMethodNoCheck<void, const char *>(pyobj(), "set_xlabel", label); +} /** * @brief Set the Y-axis label * @param label String for the axis label */ -void Axes::setYLabel(const char *label) { pyobj().attr("set_ylabel")(label); } +void Axes::setYLabel(const char *label) { + callMethodNoCheck<void, const char *>(pyobj(), "set_ylabel", label); +} /** * @brief Set the title * @param label String for the title label */ -void Axes::setTitle(const char *label) { pyobj().attr("set_title")(label); } +void Axes::setTitle(const char *label) { + callMethodNoCheck<void, const char *>(pyobj(), "set_title", label); +} /** * @brief Take the data and draw a single Line2D on the axes @@ -56,6 +100,7 @@ Line2D Axes::plot(std::vector<double> xdata, std::vector<double> ydata, throwIfEmpty(xdata, 'X'); throwIfEmpty(ydata, 'Y'); + GlobalInterpreterLock lock; // Wrap the vector data in a numpy facade to avoid a copy. // The vector still owns the data so it needs to be kept alive too // It is transferred to the Line2D for this purpose. @@ -68,10 +113,148 @@ Line2D Axes::plot(std::vector<double> xdata, std::vector<double> ydata, std::move(xdata), std::move(ydata)}; } catch (Python::ErrorAlreadySet &) { - throw PythonRuntimeError(); + throw PythonException(); } } +/** + * Add an arbitrary text label to the canvas + * @param x X position in data coordinates + * @param y Y position in data coordinates + * @param text The string to attach to the canvas + * @param horizontalAlignment A string indicating the horizontal + * alignment of the string + */ +Artist Axes::text(double x, double y, QString text, + const char *horizontalAlignment) { + GlobalInterpreterLock lock; + auto args = + Python::NewRef(Py_BuildValue("(ffs)", x, y, text.toLatin1().constData())); + auto kwargs = Python::NewRef( + Py_BuildValue("(ss)", "horizontalalignment", horizontalAlignment)); + return Artist(pyobj().attr("text")(*args, **kwargs)); +} + +/** + * @brief Set the X-axis scale to the given value. + * @param value New scale type. See + * https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.set_xscale.html + * @raises PythonException if the value is unknown + */ +void Axes::setXScale(const char *value) { + try { + callMethodNoCheck<void, const char *>(pyobj(), "set_xscale", value); + } catch (PythonException &) { + throw std::invalid_argument( + std::string("Axes::setXScale - Invalid scale type ") + value); + } +} + +/// @return The scale type of the X axis as a string +QString Axes::getXScale() const { + GlobalInterpreterLock lock; + return toQString(pyobj().attr("get_xscale")()); +} + +/** + * @brief Set the Y-axis scale to the given value. + * @param value New scale type. See + * https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.set_xscale.html + * @raises std::invalid_argument if the value is unknown + */ +void Axes::setYScale(const char *value) { + try { + callMethodNoCheck<void, const char *>(pyobj(), "set_yscale", value); + } catch (PythonException &) { + throw std::invalid_argument( + std::string("Axes::setYScale - Invalid scale type ") + value); + } +} + +/// @return The scale type of the Y axis as a string +QString Axes::getYScale() const { + GlobalInterpreterLock lock; + return toQString(pyobj().attr("get_yscale")()); +} + +/** + * Retrieve the X limits of the axes + * @return A 2-tuple of (min,max) values for the X axis + */ +std::tuple<double, double> Axes::getXLim() const { + GlobalInterpreterLock lock; + return limitsToTuple(pyobj(), "get_xlim"); +} + +/** + * Set the limits for the X axis + * @param min Minimum value + * @param max Maximum value + */ +void Axes::setXLim(double min, double max) const { + callMethodNoCheck<void, double, double>(pyobj(), "set_xlim", min, max); +} + +/** + * Retrieve the Y limits of the axes + * @return A 2-tuple of (min,max) values for the Y axis + */ +std::tuple<double, double> Axes::getYLim() const { + GlobalInterpreterLock lock; + return limitsToTuple(pyobj(), "get_ylim"); +} + +/** + * Set the limits for the Y axis + * @param min Minimum value + * @param max Maximum value + */ +void Axes::setYLim(double min, double max) const { + callMethodNoCheck<void, double, double>(pyobj(), "set_ylim", min, max); +} + +/** + * @brief Recompute the data limits from the current artists. + * @param visibleOnly If true then only include visble artists in the + * calculation + */ +void Axes::relim(bool visibleOnly) { + callMethodNoCheck<void, bool>(pyobj(), "relim", visibleOnly); +} + +/** + * Calls Axes.autoscale to enable/disable auto scaling + * @param enable If true enable autoscaling and perform the automatic rescale + */ +void Axes::autoscale(bool enable) { + callMethodNoCheck<void, bool>(pyobj(), "autoscale", enable); +} + +/** + * Autoscale the view based on the current data limits. Calls + * Axes.autoscale_view with the tight argument set to None. Autoscaling + * must be turned on for this to work as expected + * @param scaleX If true (default) scale the X axis limits + * @param scaleY If true (default) scale the Y axis limits + */ +void Axes::autoscaleView(bool scaleX, bool scaleY) { + callMethodNoCheck<void, Python::Object, bool, bool>( + pyobj(), "autoscale_view", Python::Object(), scaleX, scaleY); +} + +/** + * Autoscale the view based on the current data limits. Calls + * Axes.autoscale_view + * @param tight If true tight is False, the axis major locator will be used to + * expand the view limits + * @param scaleX If true (default) scale the X axis limits + * @param scaleY If true (default) scale the Y axis limits + */ +void Axes::autoscaleView(bool tight, bool scaleX, bool scaleY) { + callMethodNoCheck<void, bool, bool, bool>(pyobj(), "autoscale_view", tight, + scaleX, scaleY); +} + } // namespace MplCpp } // namespace Widgets } // namespace MantidQt diff --git a/qt/widgets/mplcpp/src/BackendQt.cpp b/qt/widgets/mplcpp/src/BackendQt.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f5a0e7fe600acbec778644595eb41330f0e67d9c --- /dev/null +++ b/qt/widgets/mplcpp/src/BackendQt.cpp @@ -0,0 +1,23 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidQtWidgets/MplCpp/BackendQt.h" + +namespace MantidQt { +namespace Widgets { +namespace MplCpp { + +/// Import and return the backend module for this version of Qt +Python::Object backendModule() { + // Importing PyQt first allows matplotlib to select the correct + // backend + Python::NewRef(PyImport_ImportModule(PYQT_MODULE)); + return Python::NewRef(PyImport_ImportModule(MPL_QT_BACKEND)); +} + +} // namespace MplCpp +} // namespace Widgets +} // namespace MantidQt diff --git a/qt/widgets/mplcpp/src/ColorConverter.cpp b/qt/widgets/mplcpp/src/ColorConverter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f7da50f78e541eca9d6a7bec18e98e00bcafa7cb --- /dev/null +++ b/qt/widgets/mplcpp/src/ColorConverter.cpp @@ -0,0 +1,44 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidQtWidgets/MplCpp/ColorConverter.h" +#include "MantidPythonInterface/core/GlobalInterpreterLock.h" + +using Mantid::PythonInterface::GlobalInterpreterLock; + +namespace MantidQt { +namespace Widgets { +namespace MplCpp { + +namespace { +Python::Object colorConverter() { + auto colors(Python::NewRef(PyImport_ImportModule("matplotlib.colors"))); + return Python::Object(colors.attr("colorConverter")); +} + +/// Convert a Python float to a byte value from 0->255 +inline int toByte(const Python::Object &pyfloat) { + const double rgbFloat = PyFloat_AsDouble(pyfloat.ptr()) * 255; + return static_cast<int>(rgbFloat); +} +} // namespace + +/** + * @brief Convert a matplotlib color specification to a QColor object + * @param colorSpec A matplotlib color spec. See + * https://matplotlib.org/api/colors_api.html?highlight=colors#module-matplotlib.colors + * @return A QColor object that represents this color + */ +QColor MantidQt::Widgets::MplCpp::ColorConverter::toRGB( + const Python::Object &colorSpec) { + GlobalInterpreterLock lock; + auto tuple = Python::Object(colorConverter().attr("to_rgb")(colorSpec)); + return QColor(toByte(tuple[0]), toByte(tuple[1]), toByte(tuple[2])); +} + +} // namespace MplCpp +} // namespace Widgets +} // namespace MantidQt diff --git a/qt/widgets/mplcpp/src/ColorbarWidget.cpp b/qt/widgets/mplcpp/src/ColorbarWidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b3c1517fcbcac836c7dad63f3ff7b4f79b024c09 --- /dev/null +++ b/qt/widgets/mplcpp/src/ColorbarWidget.cpp @@ -0,0 +1,299 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidQtWidgets/MplCpp/ColorbarWidget.h" +#include "MantidPythonInterface/core/GlobalInterpreterLock.h" +#include "MantidQtWidgets/MplCpp/Colors.h" +#include "MantidQtWidgets/MplCpp/Figure.h" +#include "MantidQtWidgets/MplCpp/FigureCanvasQt.h" +#include "MantidQtWidgets/MplCpp/MantidColorMap.h" + +#include <QComboBox> +#include <QDoubleValidator> +#include <QLineEdit> +#include <QVBoxLayout> + +using Mantid::PythonInterface::GlobalInterpreterLock; + +namespace MantidQt { +namespace Widgets { +namespace MplCpp { + +namespace { +// These values control the dimensions of the axes used +// to hold the colorbar. The aspect ratio is set to give +// the usual long thin colobar +constexpr double AXES_LEFT = 0.4; +constexpr double AXES_BOTTOM = 0.05; +constexpr double AXES_WIDTH = 0.2; +constexpr double AXES_HEIGHT = 0.9; + +// Background color for figure +constexpr const char *FIGURE_FACECOLOR = "w"; + +// Define the available normalization option labels and tooltips +// The order defines the order in the combo box. The index +// values is used as an integer representation. See the setScaleType +// method if this is changed. +QStringList NORM_OPTS = {"Linear", "SymmetricLog10", "Power"}; + +} // namespace + +/** + * @brief Construct a default color bar with a linear scale. The default limits + * are set to [0, 1] so setRange would need to be called at a minimum + * @param parent A pointer to the parent widget + */ +ColorbarWidget::ColorbarWidget(QWidget *parent) + : QWidget(parent), m_ui(), + m_mappable(Normalize(0, 1), getCMap(defaultCMapName())) { + initLayout(); + connectSignals(); +} + +/** + * Set the normalization instance + * @param norm An instance of NormalizeBase. See Colors.h + */ +void ColorbarWidget::setNorm(const NormalizeBase &norm) { + m_mappable.setNorm(norm); + // matplotlib requires creating a brand new colorbar if the + // normalization type changes + createColorbar(norm.tickLocator(), norm.labelFormatter()); + m_canvas->draw(); +} + +/** + * Update the range of the scale + * @param vmin An optional new minimum of the scale + * @param vmax An optional new maximum of the scale + */ +void ColorbarWidget::setClim(boost::optional<double> vmin, + boost::optional<double> vmax) { + m_mappable.setClim(vmin, vmax); + m_canvas->draw(); + + if (vmin.is_initialized()) { + m_ui.scaleMinEdit->setText(QString::number(vmin.get())); + emit minValueChanged(vmin.get()); + } + if (vmax.is_initialized()) { + m_ui.scaleMaxEdit->setText(QString::number(vmax.get())); + emit maxValueChanged(vmax.get()); + } +} + +/** + * @return A tuple giving the current colorbar scale limits + */ +std::tuple<double, double> ColorbarWidget::clim() const { + return std::make_tuple<double, double>(m_ui.scaleMinEdit->text().toDouble(), + m_ui.scaleMaxEdit->text().toDouble()); +} + +/** + * @brief Called to setup the widget based on the MantidColorMap instance + * @param mtdCMap A reference to the MantidColorMap wrapper + */ +void ColorbarWidget::setupColorBarScaling(const MantidColorMap &mtdCMap) { + // Sync the colormap first as resetting the scale type forces a redraw + // anyway + m_mappable.setCmap(mtdCMap.cmap()); + // block signals to avoid infinite loop while setting scale type + this->blockSignals(true); + setScaleType(static_cast<int>(mtdCMap.getScaleType())); + this->blockSignals(false); +} + +// ------------------------------ Legacy API ----------------------------------- + +/** + * Update the minimum value of the normalization scale + * @param vmin New minimum of the scale + */ +void ColorbarWidget::setMinValue(double vmin) { setClim(vmin, boost::none); } + +/** + * Update the maximum value of the normalization scale + * @param vmin New maximum of the scale + */ +void ColorbarWidget::setMaxValue(double vmax) { setClim(boost::none, vmax); } + +/** + * @return The minimum color scale value as a string + */ +QString ColorbarWidget::getMinValue() const { + return QString::number(std::get<0>(clim())); +} + +/** + * @return The maximum color scale value as a string + */ +QString ColorbarWidget::getMaxValue() const { + return QString::number(std::get<1>(clim())); +} + +/** + * @return The power value as a string + */ +QString ColorbarWidget::getNthPower() const { return m_ui.powerEdit->text(); } + +/** + * @return The scale type choice as an integer. + */ +int ColorbarWidget::getScaleType() const { + return m_ui.normTypeOpt->currentIndex(); +} + +/** + * @brief Set the scale type from an integer representation + * Linear=0, Log=1, Power=2, which is backwards compatible + * with the original Qwt version + * @param index The scale type as an integer + */ +void ColorbarWidget::setScaleType(int index) { + // Protection against a bad index. + if (index < 0 || index > 2) + return; + // Some ranges will be invalid for some scale types, e.g. x < 0 for PowerNorm. + // Compute a valid range and reset user-specified range if necessary + auto autoscaleAndSetNorm = [this](auto norm) { + auto validRange = norm.autoscale(clim()); + setNorm(std::move(norm)); + return validRange; + }; + + std::tuple<double, double> validRange; + switch (index) { + case 0: + validRange = autoscaleAndSetNorm(Normalize()); + break; + case 1: + validRange = autoscaleAndSetNorm(SymLogNorm( + SymLogNorm::DefaultLinearThreshold, SymLogNorm::DefaultLinearScale)); + break; + case 2: + validRange = autoscaleAndSetNorm(PowerNorm(getNthPower().toDouble())); + break; + } + setClim(std::get<0>(validRange), std::get<1>(validRange)); + m_ui.normTypeOpt->setCurrentIndex(index); + emit scaleTypeChanged(index); +} + +/** + * @brief Set the power for the power scale + * @param gamma The value of the exponent + */ +void ColorbarWidget::setNthPower(double gamma) { + m_ui.powerEdit->setText(QString::number(gamma)); + auto range = clim(); + setNorm(PowerNorm(gamma, std::get<0>(range), std::get<1>(range))); + emit nthPowerChanged(gamma); +} + +// --------------------------- Private slots ----------------------------------- +/** + * Called when a user has edited the minimum scale value + */ +void ColorbarWidget::scaleMinimumEdited() { + // The validator ensures the text is a double + setClim(m_ui.scaleMinEdit->text().toDouble(), boost::none); +} + +/** + * Called when a user has edited the maximum scale value + */ +void ColorbarWidget::scaleMaximumEdited() { + // The validator ensures the text is a double + setClim(boost::none, m_ui.scaleMaxEdit->text().toDouble()); +} + +/** + * Called when a new selection in the scale type box is made + */ +void ColorbarWidget::scaleTypeSelectionChanged(int index) { + if (index == 2) { // Power + m_ui.powerEdit->show(); + } else { + m_ui.powerEdit->hide(); + } + m_ui.normTypeOpt->blockSignals(true); + setScaleType(index); + m_ui.normTypeOpt->blockSignals(false); +} + +/** + * Called when the power exponent input has been edited + */ +void ColorbarWidget::powerExponentEdited() { setScaleType(2); } + +// --------------------------- Private methods -------------------------------- + +/** + * Setup the layout of the child widgets + */ +void ColorbarWidget::initLayout() { + // Create colorbar (and figure if necessary) + Figure fig{false}; + fig.setFaceColor(FIGURE_FACECOLOR); + m_ui.setupUi(this); + // remove placeholder widget and add figure canvas + delete m_ui.mplColorbar; + m_canvas = new FigureCanvasQt(std::move(fig), this); + m_ui.mplColorbar = m_canvas; + m_ui.verticalLayout->insertWidget(1, m_canvas); + createColorbar(); + + // Set validators on the scale inputs + m_ui.scaleMinEdit->setValidator(new QDoubleValidator()); + m_ui.scaleMaxEdit->setValidator(new QDoubleValidator()); + m_ui.powerEdit->setValidator(new QDoubleValidator()); + // Setup normalization options + m_ui.normTypeOpt->addItems(NORM_OPTS); + scaleTypeSelectionChanged(0); +} + +/** + * (Re)-create a colorbar around the current mappable. It assumes the figure + * and canvas have been created + * @param ticks An optional matplotlib.ticker.*Locator object. Default=None to + * autoselect the most appropriate + * @param format An optional matplotlib.ticker.*Format object. Default=None to + * autoselect the most appropriate + */ +void ColorbarWidget::createColorbar(const Python::Object &ticks, + const Python::Object &format) { + assert(m_canvas); + GlobalInterpreterLock lock; + auto cb = Python::Object(m_mappable.pyobj().attr("colorbar")); + if (!cb.is_none()) { + cb.attr("remove")(); + } + // create the new one + auto fig = m_canvas->gcf(); + Axes cbAxes{fig.addAxes(AXES_LEFT, AXES_BOTTOM, AXES_WIDTH, AXES_HEIGHT)}; + fig.colorbar(m_mappable, cbAxes, ticks, format); +} + +/** + * Wire up the signals for the child widgets + */ +void ColorbarWidget::connectSignals() { + connect(m_ui.scaleMinEdit, SIGNAL(editingFinished()), this, + SLOT(scaleMinimumEdited())); + connect(m_ui.scaleMaxEdit, SIGNAL(editingFinished()), this, + SLOT(scaleMaximumEdited())); + + connect(m_ui.normTypeOpt, SIGNAL(currentIndexChanged(int)), this, + SLOT(scaleTypeSelectionChanged(int))); + connect(m_ui.powerEdit, SIGNAL(editingFinished()), this, + SLOT(powerExponentEdited())); +} + +} // namespace MplCpp +} // namespace Widgets +} // namespace MantidQt diff --git a/qt/widgets/mplcpp/src/Colormap.cpp b/qt/widgets/mplcpp/src/Colormap.cpp index e1da71ebf63ca17a021c065a3ca26c94422e1d7b..1e3b0dd787bb387270d73f0b3a3a990601b1b3fd 100644 --- a/qt/widgets/mplcpp/src/Colormap.cpp +++ b/qt/widgets/mplcpp/src/Colormap.cpp @@ -1,9 +1,16 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/MplCpp/Colormap.h" #include "MantidQtWidgets/MplCpp/Colors.h" #include "MantidPythonInterface/core/ErrorHandling.h" -using Mantid::PythonInterface::PythonRuntimeError; +using Mantid::PythonInterface::GlobalInterpreterLock; +using Mantid::PythonInterface::PythonException; namespace MantidQt { namespace Widgets { @@ -24,6 +31,19 @@ Python::Object cmModule() { return cmModule; } +/** + * @param name The name of a possible colormap + * @return True if the map is known, false otherwise + */ +bool cmapExists(const QString &name) { + try { + getCMap(name); + return true; + } catch (PythonException &) { + return false; + } +} + /** * @param name The name of an existing colormap. * @return A new Colormap instance for the named map @@ -31,9 +51,24 @@ Python::Object cmModule() { */ Colormap getCMap(const QString &name) { try { + GlobalInterpreterLock lock; return cmModule().attr("get_cmap")(name.toLatin1().constData()); } catch (Python::ErrorAlreadySet &) { - throw PythonRuntimeError(); + throw PythonException(); + } +} + +/** + * Return the name of the default color map. We prefer viridis if it is + * available otherwise we fallback to jet. + * @return The string name of the default colormap we want to + * use in the library + */ +QString defaultCMapName() { + if (cmapExists("viridis")) { + return "viridis"; + } else { + return "jet"; } } diff --git a/qt/widgets/mplcpp/src/Colors.cpp b/qt/widgets/mplcpp/src/Colors.cpp index 9122de96d55428b084e26fd8bc1803fa312e49ff..5df529f8546e1fa528f7f915914f9f366b66425d 100644 --- a/qt/widgets/mplcpp/src/Colors.cpp +++ b/qt/widgets/mplcpp/src/Colors.cpp @@ -1,7 +1,21 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/MplCpp/Colors.h" #include "MantidPythonInterface/core/ErrorHandling.h" +#include "MantidPythonInterface/core/GlobalInterpreterLock.h" +#include <boost/optional.hpp> +#include <tuple> -using Mantid::PythonInterface::PythonRuntimeError; +using Mantid::PythonInterface::GlobalInterpreterLock; +using Mantid::PythonInterface::PythonException; +using boost::none; +using boost::optional; + +using OptionalTupleDouble = optional<std::tuple<double, double>>; namespace MantidQt { namespace Widgets { @@ -12,36 +26,129 @@ namespace { * @return A reference to the matplotlib.colors module */ Python::Object colorsModule() { - Python::Object colorsModule{ - Python::NewRef(PyImport_ImportModule("matplotlib.colors"))}; - return colorsModule; + return Python::NewRef(PyImport_ImportModule("matplotlib.colors")); +} + +// Factory function for creating a Normalize instance +// Holds the GIL +Python::Object createNormalize(OptionalTupleDouble clim = none) { + GlobalInterpreterLock lock; + if (clim.is_initialized()) { + const auto &range = clim.get(); + return colorsModule().attr("Normalize")(std::get<0>(range), + std::get<1>(range)); + } else + return colorsModule().attr("Normalize")(); +} + +// Factory function for creating a SymLogNorm instance +// Holds the GIL +Python::Object createSymLog(double linthresh, double linscale, + OptionalTupleDouble clim = none) { + GlobalInterpreterLock lock; + if (clim.is_initialized()) { + const auto &range = clim.get(); + return colorsModule().attr("SymLogNorm")( + linthresh, linscale, std::get<0>(range), std::get<1>(range)); + } else + return colorsModule().attr("SymLogNorm")(linthresh, linscale); +} + +// Factory function for creating a SymLogNorm instance +// Holds the GIL +Python::Object createPowerNorm(double gamma, OptionalTupleDouble clim = none) { + GlobalInterpreterLock lock; + if (clim.is_initialized()) { + const auto &range = clim.get(); + return colorsModule().attr("PowerNorm")(gamma, std::get<0>(range), + std::get<1>(range)); + } else { + return colorsModule().attr("PowerNorm")(gamma); + } +} + +/** + * @return A reference to the matplotlib.ticker module + */ +Python::Object tickerModule() { + GlobalInterpreterLock lock; + return Python::NewRef(PyImport_ImportModule("matplotlib.ticker")); +} + +/** + * @return A reference to the matplotlib.ticker module + */ +Python::Object scaleModule() { + GlobalInterpreterLock lock; + return Python::NewRef(PyImport_ImportModule("matplotlib.scale")); } + } // namespace // ------------------------ NormalizeBase--------------------------------------- /** - * @brief NormalizeBase::NormalizeBase + * Calls autoscale([vmin,vmax]) on the normalize instance. This + * forces any invalid values to a valid range + * @param clim A 2-tuple of the scale range + * @return A 2-tuple of the new scale values + */ +std::tuple<double, double> +NormalizeBase::autoscale(std::tuple<double, double> clim) { + GlobalInterpreterLock lock; + pyobj().attr("autoscale")(Python::NewRef( + Py_BuildValue("(ff)", std::get<0>(clim), std::get<1>(clim)))); + Python::Object scaleMin(pyobj().attr("vmin")), scaleMax(pyobj().attr("vmax")); + return std::make_tuple(PyFloat_AsDouble(scaleMin.ptr()), + PyFloat_AsDouble(scaleMax.ptr())); +} + +/** + * Constructor * @param obj An existing Normalize instance or subtype */ NormalizeBase::NormalizeBase(Python::Object obj) - : Python::InstanceHolder(std::move(obj), "autoscale") {} + : Python::InstanceHolder(std::move(obj)) {} // ------------------------ Normalize ------------------------------------------ +/** + * @brief Construct a Normalize object mapping data from [vmin, vmax] + * to [0, 1] leaving the vmin,vmax limits unset. A call to autoscale + * will be required + */ +Normalize::Normalize() : NormalizeBase(createNormalize()) {} + /** * @brief Construct a Normalize object mapping data from [vmin, vmax] * to [0, 1] * @param vmin Minimum value of the data interval - * @param vmax Maximum value of the data interval - */ + * @param vmax Maximum value of the data interval */ Normalize::Normalize(double vmin, double vmax) - : NormalizeBase(colorsModule().attr("Normalize")(vmin, vmax)) {} + : NormalizeBase(createNormalize(std::make_tuple(vmin, vmax))) {} // ------------------------ SymLogNorm ----------------------------------------- +/// The threshold below which the scale becomes linear +double SymLogNorm::DefaultLinearThreshold = 1e-3; +/// The value to scale the linear range by. Defaults to 1 decade +double SymLogNorm::DefaultLinearScale = 1.0; + +/** + * @brief Construct a SymLogNorm object mapping data from [vmin, vmax] + * to a symmetric logarithm scale. Default limits are None so autoscale + * will need to be called + * @param linthresh The range within which the plot is linear + * @param linscale This allows the linear range (-linthresh to linthresh) to be + * stretched relative to the logarithmic range. + * See + * https://matplotlib.org/2.2.3/api/_as_gen/matplotlib.colors.SymLogNorm.html#matplotlib.colors.SymLogNorm + */ +SymLogNorm::SymLogNorm(double linthresh, double linscale) + : NormalizeBase(createSymLog(linthresh, linscale)), m_linscale(linscale) {} /** * @brief Construct a SymLogNorm object mapping data from [vmin, vmax] - * to a symmetric logarithm scale + * to a symmetric logarithm scale. Default limits are None so autoscale + * will need to be called * @param linthresh The range within which the plot is linear * @param linscale This allows the linear range (-linthresh to linthresh) to be * stretched relative to the logarithmic range. @@ -53,9 +160,38 @@ Normalize::Normalize(double vmin, double vmax) SymLogNorm::SymLogNorm(double linthresh, double linscale, double vmin, double vmax) : NormalizeBase( - colorsModule().attr("SymLogNorm")(linthresh, linscale, vmin, vmax)) {} + createSymLog(linthresh, linscale, std::make_tuple(vmin, vmax))), + m_linscale(linscale) {} + +/** + * @return An instance of the SymmetricalLogLocator + */ +Python::Object SymLogNorm::tickLocator() const { + GlobalInterpreterLock lock; + // Create log transform with base=10 + auto transform = scaleModule().attr("SymmetricalLogTransform")( + 10, Python::Object(pyobj().attr("linthresh")), m_linscale); + return Python::Object( + tickerModule().attr("SymmetricalLogLocator")(transform)); +} + +/** + * @brief SymLogNorm::labelFormatter + * @return + */ +Python::Object SymLogNorm::labelFormatter() const { + GlobalInterpreterLock lock; + return Python::Object(tickerModule().attr("LogFormatterMathtext")()); +} // ------------------------ PowerNorm ------------------------------------------ +/** + * @brief Construct a PowerNorm object to map data from [vmin,vmax] to + * [0,1] pn a power-law scale. Default limits are None so autoscale + * will need to be called + * @param gamma The exponent for the power-law + */ +PowerNorm::PowerNorm(double gamma) : NormalizeBase(createPowerNorm(gamma)) {} /** * @brief Construct a PowerNorm object to map data from [vmin,vmax] to @@ -65,7 +201,7 @@ SymLogNorm::SymLogNorm(double linthresh, double linscale, double vmin, * @param vmax Maximum value of the data interval */ PowerNorm::PowerNorm(double gamma, double vmin, double vmax) - : NormalizeBase(colorsModule().attr("PowerNorm")(gamma, vmin, vmax)) {} + : NormalizeBase(createPowerNorm(gamma, std::make_tuple(vmin, vmax))) {} } // namespace MplCpp } // namespace Widgets diff --git a/qt/widgets/mplcpp/src/Cycler.cpp b/qt/widgets/mplcpp/src/Cycler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4db8de9f50b9d6e630af7cd8cd7136bafe9c35b9 --- /dev/null +++ b/qt/widgets/mplcpp/src/Cycler.cpp @@ -0,0 +1,67 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidQtWidgets/MplCpp/Cycler.h" +#include "MantidPythonInterface/core/GlobalInterpreterLock.h" + +using Mantid::PythonInterface::GlobalInterpreterLock; + +namespace MantidQt { +namespace Widgets { +namespace MplCpp { + +namespace { +Python::Object cyclerModule() { + return Python::NewRef(PyImport_ImportModule("cycler")); +} + +/** + * Creates an iterable from a plain Cycler object + * @param rawCycler A Cycler object + * @return An iterable returned from itertools.cycle + */ +Python::Object cycleIterator(const Python::Object &rawCycler) { + GlobalInterpreterLock lock; + auto itertools = Python::NewRef(PyImport_ImportModule("itertools")); + try { + return Python::Object(itertools.attr("cycle")(rawCycler)); + } catch (Python::ErrorAlreadySet &) { + throw std::invalid_argument("itertools.cycle() - Object not iterable"); + } +} +} // namespace + +/** + * Create a wrapper around an existing matplotlib.cycler.Cycler object + * that produces an iterable + * @param obj An existing instance of a Cycler object + */ +Cycler::Cycler(Python::Object obj) + : Python::InstanceHolder(cycleIterator(std::move(obj))) {} + +/** + * Advance the iterator and return the previous item + * @return The next item in the cycle + */ +Python::Dict Cycler::operator()() const { + GlobalInterpreterLock lock; + return Python::Dict(Python::NewRef(PyIter_Next(pyobj().ptr()))); +} + +/** + * @param label A string label to assign to the cycler. It forms the key for + * each item return by the cycler + * @param iterable A string sequence of values to form a cycle + * @return A new cycler object wrapping the given iterable sequence + */ +Cycler cycler(const char *label, const char *iterable) { + GlobalInterpreterLock lock; + return cyclerModule().attr("cycler")(label, iterable); +} + +} // namespace MplCpp +} // namespace Widgets +} // namespace MantidQt diff --git a/qt/widgets/mplcpp/src/Figure.cpp b/qt/widgets/mplcpp/src/Figure.cpp index 5e1fb6f3a5de4e9e0371f051ea6047902140ae91..f9bdbf90e5517d68b144cfc10a18012f841f3a5d 100644 --- a/qt/widgets/mplcpp/src/Figure.cpp +++ b/qt/widgets/mplcpp/src/Figure.cpp @@ -1,4 +1,14 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/MplCpp/Figure.h" +#include "MantidPythonInterface/core/CallMethod.h" + +using Mantid::PythonInterface::GlobalInterpreterLock; +using Mantid::PythonInterface::callMethodNoCheck; namespace MantidQt { namespace Widgets { @@ -6,17 +16,25 @@ namespace MplCpp { namespace { Python::Object newFigure(bool tightLayout = true) { + GlobalInterpreterLock lock; Python::Object figureModule{ Python::NewRef(PyImport_ImportModule("matplotlib.figure"))}; auto fig = figureModule.attr("Figure")(); if (tightLayout) { - auto kwargs = Python::NewRef(Py_BuildValue("{s:f}", "pad", 0.5)); - fig.attr("set_tight_layout")(kwargs); + auto tight = Python::NewRef(Py_BuildValue("{sf}", "pad", 0.5)); + fig.attr("set_tight_layout")(tight); } return fig; } } // namespace +/** + * Construct a C++ wrapper around an existing figure instance + * @param obj An existing Figure instance + */ +Figure::Figure(Python::Object obj) + : Python::InstanceHolder(std::move(obj), "add_axes") {} + /** * Construct a new default figure. * @param tightLayout If true set a tight layout on the matplotlib figure @@ -24,6 +42,15 @@ Python::Object newFigure(bool tightLayout = true) { Figure::Figure(bool tightLayout) : Python::InstanceHolder(newFigure(tightLayout)) {} +/** + * Reset the background color of the figure. + * @param color A character string indicating the color. + * See https://matplotlib.org/api/colors_api.html + */ +void Figure::setFaceColor(const char *color) { + callMethodNoCheck<void, const char *>(pyobj(), "set_facecolor", color); +} + /** * Add an Axes of the given dimensions to the current figure * All quantities are in fractions of figure width and height @@ -34,6 +61,7 @@ Figure::Figure(bool tightLayout) * @return A new Axes instance */ Axes Figure::addAxes(double left, double bottom, double width, double height) { + GlobalInterpreterLock lock; return Axes{pyobj().attr("add_axes")( Python::NewRef(Py_BuildValue("(ffff)", left, bottom, width, height)))}; } @@ -44,9 +72,31 @@ Axes Figure::addAxes(double left, double bottom, double width, double height) { * @return */ Axes Figure::addSubPlot(int subplotspec) { + GlobalInterpreterLock lock; return Axes{pyobj().attr("add_subplot")(subplotspec)}; } +/** + * @brief Add a colorbar to this figure + * @param mappable An objet providing the mapping of data to rgb colors + * @param cax An axes instance to hold the color bar + * @param ticks An optional array or ticker.Locator object to control tick + * placement + * @param format An optional object describing how to format the tick labels + * @return A reference to the matplotlib.colorbar.Colorbar object + */ +Python::Object Figure::colorbar(const ScalarMappable &mappable, const Axes &cax, + const Python::Object &ticks, + const Python::Object &format) { + GlobalInterpreterLock lock; + auto args = Python::NewRef( + Py_BuildValue("(OO)", mappable.pyobj().ptr(), cax.pyobj().ptr())); + auto kwargs = Python::NewRef( + Py_BuildValue("{sOsO}", "ticks", ticks.ptr(), "format", format.ptr())); + Python::Object attr{pyobj().attr("colorbar")}; + return Python::NewRef(PyObject_Call(attr.ptr(), args.ptr(), kwargs.ptr())); +} + } // namespace MplCpp } // namespace Widgets } // namespace MantidQt diff --git a/qt/widgets/mplcpp/src/FigureCanvasQt.cpp b/qt/widgets/mplcpp/src/FigureCanvasQt.cpp index d6530dbc2e558b2f62144bcef8f9ce8bbcfe5689..c3f677eea2089d34f14cb412a27b644957305d40 100644 --- a/qt/widgets/mplcpp/src/FigureCanvasQt.cpp +++ b/qt/widgets/mplcpp/src/FigureCanvasQt.cpp @@ -1,19 +1,36 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/MplCpp/FigureCanvasQt.h" #include "MantidQtWidgets/MplCpp/BackendQt.h" #include "MantidQtWidgets/MplCpp/Figure.h" #include "MantidQtWidgets/MplCpp/Python/Sip.h" +#include "MantidPythonInterface/core/ErrorHandling.h" +#include "MantidPythonInterface/core/GlobalInterpreterLock.h" +#include "MantidPythonInterface/core/NDArray.h" + #include <QVBoxLayout> +using Mantid::PythonInterface::GlobalInterpreterLock; +using Mantid::PythonInterface::NDArray; + namespace MantidQt { namespace Widgets { namespace MplCpp { namespace { + +const char *DEFAULT_FACECOLOR = "w"; + /** * @param fig An existing matplotlib Figure instance * @return A new FigureCanvasQT object */ Python::Object createPyCanvasFromFigure(Figure fig) { + GlobalInterpreterLock lock; return backendModule().attr("FigureCanvasQTAgg")(fig.pyobj()); } @@ -24,6 +41,8 @@ Python::Object createPyCanvasFromFigure(Figure fig) { */ Python::Object createPyCanvas(int subplotspec) { Figure fig{true}; + fig.setFaceColor(DEFAULT_FACECOLOR); + if (subplotspec > 0) fig.addSubPlot(subplotspec); return createPyCanvasFromFigure(std::move(fig)); @@ -33,13 +52,14 @@ Python::Object createPyCanvas(int subplotspec) { /** * @brief Common constructor code for FigureCanvasQt * @param cppCanvas A pointer to the FigureCanvasQt object + * @return A pointer to the C++ widget extract from the Python FigureCanvasQT + * object */ -void initLayout(FigureCanvasQt *cppCanvas) { +QWidget *initLayout(FigureCanvasQt *cppCanvas) { cppCanvas->setLayout(new QVBoxLayout()); QWidget *pyCanvas = Python::extract<QWidget>(cppCanvas->pyobj()); cppCanvas->layout()->addWidget(pyCanvas); - pyCanvas->setMouseTracking(false); - pyCanvas->installEventFilter(cppCanvas); + return pyCanvas; } /** @@ -48,26 +68,78 @@ void initLayout(FigureCanvasQt *cppCanvas) { * See * https://matplotlib.org/2.2.3/api/_as_gen/matplotlib.figure.Figure.html?highlight=add_subplot#matplotlib.figure.Figure.add_subplot * @param subplotspec A matplotlib subplot spec defined as a 3-digit integer + * @param facecolor String denoting the figure's facecolor * @param parent The owning parent widget */ FigureCanvasQt::FigureCanvasQt(int subplotspec, QWidget *parent) : QWidget(parent), InstanceHolder(createPyCanvas(subplotspec), "draw"), - m_axes(pyobj().attr("figure").attr("axes")[0]) { + m_figure(Figure(Python::Object(pyobj().attr("figure")))) { // Cannot use delegating constructor here as InstanceHolder needs to be // initialized before the axes can be created - initLayout(this); + m_mplCanvas = initLayout(this); } /** * @brief Constructor specifying an existing axes object and optional * parent. - * @param axes An existing axes instance + * @param fig An existing figure instance containing an axes * @param parent The owning parent widget */ FigureCanvasQt::FigureCanvasQt(Figure fig, QWidget *parent) : QWidget(parent), InstanceHolder(createPyCanvasFromFigure(fig), "draw"), - m_axes(fig.axes(0)) { - initLayout(this); + m_figure(std::move(fig)) { + m_mplCanvas = initLayout(this); +} + +/** + * Allows events destined for the child canvas to be intercepted by a filter + * object. The Matplotlib canvas defines its own event handlers for certain + * events but FigureCanvasQt does not directly inherit from the matplotlib + * canvas so we cannot use the standard virtual event methods to intercept them. + * Instead the event filter allows an object to capture and process them. + * @param filter A pointer to an object overriding the eventFilter method + */ +void FigureCanvasQt::installEventFilterToMplCanvas(QObject *filter) { + assert(m_mplCanvas); + m_mplCanvas->installEventFilter(filter); +} + +/** + * @param pos A point in Qt screen coordinates from, for example, a mouse click + * @return A QPointF defining the position in data coordinates + */ +QPointF FigureCanvasQt::toDataCoords(QPoint pos) const { + // There is no isolated method for doing the transform on matplotlib's + // classes. The functionality is bound up inside other methods + // so we are forced to duplicate the behaviour here. + // The following code is derived form what happens in + // matplotlib.backends.backend_qt5.FigureCanvasQT & + // matplotlib.backend_bases.LocationEvent where we transform first to + // matplotlib's coordinate system, (0,0) is bottom left, + // and then to the data coordinates + const int dpiRatio(devicePixelRatio()); + const double xPosPhysical = pos.x() * dpiRatio; + GlobalInterpreterLock lock; + // Y=0 is at the bottom + double height = PyFloat_AsDouble( + Python::Object(m_figure.pyobj().attr("bbox").attr("height")).ptr()); + const double yPosPhysical = + ((height / devicePixelRatio()) - pos.y()) * dpiRatio; + // Transform to data coordinates + QPointF dataCoords; + try { + auto invTransform = gca().pyobj().attr("transData").attr("inverted")(); + NDArray transformed = invTransform.attr("transform_point")( + Python::NewRef(Py_BuildValue("(ff)", xPosPhysical, yPosPhysical))); + auto rawData = reinterpret_cast<double *>(transformed.get_data()); + dataCoords = + QPointF(static_cast<qreal>(rawData[0]), static_cast<qreal>(rawData[1])); + } catch (Python::ErrorAlreadySet &) { + PyErr_Clear(); + // an exception indicates no transform possible. Matplotlib sets this as + // an empty data coordinate so we will do the same + } + return dataCoords; } } // namespace MplCpp diff --git a/qt/widgets/mplcpp/src/Line2D.cpp b/qt/widgets/mplcpp/src/Line2D.cpp index 2ae28b6755452a9e3b5d627ff772c9312aabb7c5..e2f17869e20627af0ac471087f746758edb96ff1 100644 --- a/qt/widgets/mplcpp/src/Line2D.cpp +++ b/qt/widgets/mplcpp/src/Line2D.cpp @@ -1,4 +1,16 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/MplCpp/Line2D.h" +#include "MantidQtWidgets/MplCpp/ColorConverter.h" + +#include <QColor> + +using Mantid::PythonInterface::GlobalInterpreterLock; +using MantidQt::Widgets::MplCpp::ColorConverter; namespace MantidQt { namespace Widgets { @@ -15,21 +27,36 @@ namespace MplCpp { */ Line2D::Line2D(Python::Object obj, std::vector<double> xdataOwner, std::vector<double> ydataOwner) - : Artist(obj), m_xOwner(std::move(xdataOwner)), - m_yOwner(std::move(ydataOwner)) {} + : Artist(std::move(obj)), m_xOwner(std::move(xdataOwner)), + m_yOwner(std::move(ydataOwner)) { + assert(!m_xOwner.empty()); + assert(!m_yOwner.empty()); +} /** * The data is being deleted so the the line is removed from the axes * if it is present */ -Line2D::~Line2D() { - try { - this->remove(); - } catch (Python::ErrorAlreadySet &) { - // line is not attached to an axes +Line2D::~Line2D() noexcept { + // If the Line2D has not been gutted by a std::move() then + // detach the line + if (!m_xOwner.empty()) { + try { + this->remove(); + } catch (...) { + // line is not attached to an axes + } } } +/** + * @return A QColor defining the color of the artist + */ +QColor Line2D::getColor() const { + GlobalInterpreterLock lock; + return ColorConverter::toRGB(pyobj().attr("get_color")()); +} + } // namespace MplCpp } // namespace Widgets } // namespace MantidQt diff --git a/qt/widgets/mplcpp/src/MantidColorMap.cpp b/qt/widgets/mplcpp/src/MantidColorMap.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c8b2ed94efffd2cee7166004db4e0a7393c728ff --- /dev/null +++ b/qt/widgets/mplcpp/src/MantidColorMap.cpp @@ -0,0 +1,149 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidQtWidgets/MplCpp/MantidColorMap.h" +#include "MantidQtWidgets/MplCpp/Colormap.h" +#include "MantidQtWidgets/MplCpp/Colors.h" +#include "MantidQtWidgets/MplCpp/ScalarMappable.h" + +#include <QInputDialog> +#include <QStringList> +#include <QWidget> + +namespace MantidQt { +namespace Widgets { +namespace MplCpp { + +// ------------------------ Static methods --------------------- +/** + * @brief Ask the user to select a color map. Shows + * @param previous + * @param parent A widget to act as parent for the chooser dialog + * @return + */ +QString MantidColorMap::chooseColorMap(const QString &previous, + QWidget *parent) { + static QStringList allowedCMaps{"coolwarm", "jet", "summer", "winter", + "viridis"}; + const int currentIdx = allowedCMaps.indexOf(previous); + bool ok; + QString item = + QInputDialog::getItem(parent, "Select colormap...", "Name:", allowedCMaps, + currentIdx >= 0 ? currentIdx : 0, false, &ok); + if (ok && !item.isEmpty()) + return item; + else + return previous; +} + +/** + * @return The name of the default colormap + */ +QString MantidColorMap::defaultColorMap() { return defaultCMapName(); } + +/** + * @brief Check if a given color map exists. This interface has to + * match the existing interface in LegacyQwt. + * @param name The name of a colormap + * @return The same name passed to the function if it exists + * @throws std::runtime_error if the colomap does not exist + */ +QString MantidColorMap::exists(const QString &name) { + getCMap(name); // throws if it does not exist + return name; +} + +// ------------------------ Public methods --------------------- + +/** + * Construct a default colormap + */ +MantidColorMap::MantidColorMap() + : m_mappable(Normalize(0, 1), getCMap(defaultCMapName())) {} + +/** + * Reset the colormap to the default + */ +void MantidColorMap::setupDefaultMap() { loadMap(defaultCMapName()); } + +/** + * Load the given colormap into the object + * @param name The name of an existing colormap + * @return True if it suceeded, false otherwise + */ +bool MantidColorMap::loadMap(const QString &name) { + if (cmapExists(name)) { + m_mappable.setCmap(name); + return true; + } else { + return false; + } +} + +/** + * Switch the scale type of the map + * @param type An enumeration giving the type of scale to use + */ +void MantidColorMap::changeScaleType(MantidColorMap::ScaleType type) { + if (type == m_scaleType) + return; + m_scaleType = type; + switch (type) { + case ScaleType::Linear: + m_mappable.setNorm(Normalize()); + break; + case ScaleType::Log10: + m_mappable.setNorm(SymLogNorm(SymLogNorm::DefaultLinearThreshold, + SymLogNorm::DefaultLinearScale, 0, 1)); + break; + case ScaleType::Power: + m_mappable.setNorm(PowerNorm(m_gamma, 0, 1)); + } +} + +/** + * @return The current scale type of the map + */ +MantidColorMap::ScaleType MantidColorMap::getScaleType() const { + return m_scaleType; +} + +/** + * @brief Set the value of the exponent for the power scale + * @param gamma The value of the exponent + */ +void MantidColorMap::setNthPower(double gamma) { m_gamma = gamma; } + +/** + * @brief Compute an RGB color value on the current scale type for the given + * data value + * @param vmin The minimum value of the data range + * @param vmax The maximum value of the data range + * @param value The value to be transformed + * @return An instance QRgb describing the color + */ +QRgb MantidColorMap::rgb(double vmin, double vmax, double value) const { + m_mappable.setClim(vmin, vmax); + return m_mappable.toRGBA(value); +} + +/** + * @brief Compute RGB color values on the current scale type for the given + * data values + * @param vmin The minimum value of the data range + * @param vmax The maximum value of the data range + * @param values The values to be transformed + * @return An array of QRgb describing the colors + */ +std::vector<QRgb> MantidColorMap::rgb(double vmin, double vmax, + const std::vector<double> &values) const { + m_mappable.setClim(vmin, vmax); + return m_mappable.toRGBA(values); +} + +} // namespace MplCpp +} // namespace Widgets +} // namespace MantidQt diff --git a/qt/widgets/mplcpp/src/ScalarMappable.cpp b/qt/widgets/mplcpp/src/ScalarMappable.cpp index f5567194dc83a6857859b27b1514e2a2ba4256f6..ae29516b31769e96d7a78fded7cc72c0fe6f55c1 100644 --- a/qt/widgets/mplcpp/src/ScalarMappable.cpp +++ b/qt/widgets/mplcpp/src/ScalarMappable.cpp @@ -1,9 +1,22 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #include "MantidQtWidgets/MplCpp/ScalarMappable.h" #include "MantidQtWidgets/MplCpp/Colormap.h" +#include "MantidPythonInterface/core/CallMethod.h" +#include "MantidPythonInterface/core/Converters/VectorToNDArray.h" +#include "MantidPythonInterface/core/Converters/WrapWithNDArray.h" #include "MantidPythonInterface/core/NDArray.h" +using Mantid::PythonInterface::Converters::VectorToNDArray; +using Mantid::PythonInterface::Converters::WrapReadOnly; +using Mantid::PythonInterface::GlobalInterpreterLock; using Mantid::PythonInterface::NDArray; +using Mantid::PythonInterface::callMethodNoCheck; namespace MantidQt { namespace Widgets { @@ -13,6 +26,7 @@ namespace { Python::Object createScalarMappable(const NormalizeBase &norm, const Colormap &cmap) { + GlobalInterpreterLock lock; return cmModule().attr("ScalarMappable")(norm.pyobj(), cmap.pyobj()); } } // namespace @@ -21,7 +35,7 @@ Python::Object createScalarMappable(const NormalizeBase &norm, * @brief Construct a ScalarMappable instance with the given normalization * type and colormap * @param norm Instance use to define the mapping from data to [0,1] - * @param cmap A Colormap defning the RGBA values to use for drawing + * @param cmap A Colormap defining the RGBA values to use for drawing */ ScalarMappable::ScalarMappable(const NormalizeBase &norm, const Colormap &cmap) : Python::InstanceHolder(createScalarMappable(norm, cmap), "to_rgba") { @@ -31,6 +45,65 @@ ScalarMappable::ScalarMappable(const NormalizeBase &norm, const Colormap &cmap) pyobj().attr("set_array")(Python::NewRef(Py_BuildValue("()"))); } +/** + * @brief Construct a ScalarMappable instance with the given normalization + * type and colormap + * @param norm Instance use to define the mapping from data to [0,1] + * @param cmap A string name for a Colormap + */ +ScalarMappable::ScalarMappable(const NormalizeBase &norm, const QString &cmap) + : ScalarMappable(norm, getCMap(cmap)) {} + +/// @return A reference to the colormap instance +Colormap ScalarMappable::cmap() const { + GlobalInterpreterLock lock; + return Colormap(pyobj().attr("cmap")); +} + +/** + * Reset the underlying colormap + * @param cmap An instance of a Colormap + */ +void ScalarMappable::setCmap(const Colormap &cmap) { + callMethodNoCheck<void, Python::Object>(pyobj(), "set_cmap", cmap.pyobj()); +} + +/** + * Reset the underlying colormap + * @param cmap The name of a colormap + */ +void ScalarMappable::setCmap(const QString &cmap) { + callMethodNoCheck<void, const char *>(pyobj(), "set_cmap", + cmap.toLatin1().constData()); +} + +/** + * @brief Reset the normalization instance + * @param norm A normalization type + */ +void ScalarMappable::setNorm(const NormalizeBase &norm) { + callMethodNoCheck<void, Python::Object>(pyobj(), "set_norm", norm.pyobj()); +} + +/** + * Reset the mappable limits + * @param vmin An optional new minmum value + * @param vmax An optional new maximum value + */ +void ScalarMappable::setClim(boost::optional<double> vmin, + boost::optional<double> vmax) { + GlobalInterpreterLock lock; + Python::Object none; + auto setClimAttr = pyobj().attr("set_clim"); + if (vmin.is_initialized() && vmax.is_initialized()) { + setClimAttr(vmin.get(), vmax.get()); + } else if (vmin.is_initialized()) { + setClimAttr(vmin.get(), none); + } else if (vmax.is_initialized()) { + setClimAttr(none, vmax.get()); + } +} + /** * @brief Convert a data value to an RGBA value * @param x The data value within the @@ -38,29 +111,48 @@ ScalarMappable::ScalarMappable(const NormalizeBase &norm, const Colormap &cmap) * @return A QRgb value corresponding to this data point */ QRgb ScalarMappable::toRGBA(double x, double alpha) const { - // Sending the first argument as an iterable gives a numpy array back. + GlobalInterpreterLock lock; + return toRGBA(std::vector<double>(1, x), alpha)[0]; +} + +/** + * @brief Convert an array of data values to a set of RGBA values + * @param x The data array of values + * @param alpha The alpha value (default = 1) + * @return An array of QRgb values corresponding to the data points + */ +std::vector<QRgb> ScalarMappable::toRGBA(const std::vector<double> &x, + double alpha) const { + std::vector<QRgb> rgbaVector(x.size()); + GlobalInterpreterLock lock; + auto ndarrayView = Python::NewRef(VectorToNDArray<double, WrapReadOnly>()(x)); // The final argument (bytes=true) forces the return value to be 0->255 - NDArray rgba{pyobj().attr("to_rgba")(Python::NewRef(Py_BuildValue("(f)", x)), - alpha, true)}; + NDArray bytes{pyobj().attr("to_rgba")(ndarrayView, alpha, true)}; // sanity check - auto shape = rgba.get_shape(); - if (rgba.get_typecode() == 'B' && rgba.get_nd() == 2 && shape[0] == 1 && - shape[1] == 4) { - auto bytes = reinterpret_cast<std::uint8_t *>(rgba.get_data()); - return qRgba(bytes[0], bytes[1], bytes[2], bytes[3]); + auto shape = bytes.get_shape(); + if (bytes.get_typecode() == 'B' && bytes.get_nd() == 2 && + static_cast<size_t>(shape[0]) == x.size() && shape[1] == 4) { + // The returned array is of shape (x.size(), 4) + auto bytesRaw = reinterpret_cast<std::uint8_t *>(bytes.get_data()); + size_t bytesIndex{0}; + for (auto &rgb : rgbaVector) { + rgb = qRgba(bytesRaw[bytesIndex], bytesRaw[bytesIndex + 1], + bytesRaw[bytesIndex + 2], bytesRaw[bytesIndex + 3]); + bytesIndex += 4; + } } else { - std::string msg = "Unexpected return type from " - "ScalarMappable.to_rgba. Expected " - "np.array(dtype=B) with shape (1,4) but found " - "np.array(dtype="; - msg.append(std::to_string(rgba.get_typecode())) - .append(") with shape (") - .append(std::to_string(shape[0])) - .append(",") - .append(std::to_string(shape[1])) - .append(")"); - throw std::runtime_error(std::move(msg)); + QString msg = + QString("Unexpected return type from " + "ScalarMappable.to_rgba. Expected " + "np.array(dtype=B) with shape (%1, 4) " + "but found np.array(dtype=%2) with shape (%3, %4). " + "Cannot continue. Please contact development team.") + .arg(QString::number(x.size()), + QString::number(bytes.get_typecode()), + QString::number(shape[0]), QString::number(shape[1])); + throw std::runtime_error(msg.toLatin1().constData()); } + return rgbaVector; } } // namespace MplCpp diff --git a/qt/widgets/mplcpp/src/Zoomer.cpp b/qt/widgets/mplcpp/src/Zoomer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d1b7eb6293a5a0070a8347af0cfc46ae676a4623 --- /dev/null +++ b/qt/widgets/mplcpp/src/Zoomer.cpp @@ -0,0 +1,69 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidQtWidgets/MplCpp/Zoomer.h" +#include "MantidQtWidgets/MplCpp/BackendQt.h" +#include "MantidQtWidgets/MplCpp/FigureCanvasQt.h" + +namespace MantidQt { +namespace Widgets { +namespace MplCpp { + +namespace { +const char *TOOLBAR_CLS = "NavigationToolbar2QT"; +const char *TOOLBAR_MODE_ATTR = "mode"; +const char *TOOLBAR_MODE_ZOOM = "zoom rect"; + +/// Return the matplotlib NavigationToolbar type appropriate +/// for our backend +Python::Object mplNavigationToolbar(FigureCanvasQt *canvas) { + auto backend = backendModule(); + Python::Object parent; // None + bool showCoordinates(false); + return Python::Object( + backend.attr(TOOLBAR_CLS)(canvas->pyobj(), parent, showCoordinates)); +} + +} // namespace + +/** + * Create a Zoomer object to attach zooming capability to + * the given canvas. + * @param canvas A reference to an existing FigureCanvasQt object + */ +Zoomer::Zoomer(FigureCanvasQt *canvas) + : Python::InstanceHolder(mplNavigationToolbar(canvas)), m_canvas(canvas) {} + +/** + * + * @return True if zooming has been enabled, false otherwise + */ +bool Zoomer::isZoomEnabled() const { + return (pyobj().attr(TOOLBAR_MODE_ATTR) == TOOLBAR_MODE_ZOOM); +} + +/** + * Enable/disable zooming mode + */ +void Zoomer::enableZoom(bool requestOn) { + // The base functionality works as a toggle + const bool isOn = isZoomEnabled(); + if ((requestOn && !isOn) || (!requestOn && isOn)) { + pyobj().attr("zoom")(); + } +} + +/** + * Resets the view to encompass all of the data + */ +void Zoomer::zoomOut() { + m_canvas->gca().autoscale(true); + m_canvas->drawIdle(); +} + +} // namespace MplCpp +} // namespace Widgets +} // namespace MantidQt diff --git a/qt/widgets/mplcpp/test/ArtistTest.h b/qt/widgets/mplcpp/test/ArtistTest.h index f2a6f4a60ca7078242d14f5f2f75c12efd05fa74..d762c42ce2e59aaecccbbeb87ec6bd8ba7b3a063 100644 --- a/qt/widgets/mplcpp/test/ArtistTest.h +++ b/qt/widgets/mplcpp/test/ArtistTest.h @@ -1,3 +1,9 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_ARTISTTEST_H #define MPLCPP_ARTISTTEST_H @@ -21,10 +27,21 @@ public: TS_ASSERT_THROWS_NOTHING(Artist drawer(pyartist)); } + void testSetCallsArtistSet() { + auto textModule(Python::NewRef(PyImport_ImportModule("matplotlib.text"))); + Artist label(textModule.attr("Text")()); + Python::Dict kwargs; + kwargs["color"] = "r"; + label.set(kwargs); + + TS_ASSERT_EQUALS("r", label.pyobj().attr("get_color")()); + } + void testArtistCallsRemoveOnPyObject() { + using Mantid::PythonInterface::PythonException; auto textModule(Python::NewRef(PyImport_ImportModule("matplotlib.text"))); Artist label(textModule.attr("Text")()); - TS_ASSERT_THROWS(label.remove(), Python::ErrorAlreadySet); + TS_ASSERT_THROWS(label.remove(), PythonException); } // ----------------- failure tests --------------------- diff --git a/qt/widgets/mplcpp/test/AxesTest.h b/qt/widgets/mplcpp/test/AxesTest.h index a1dff83573a9cc87012aa85ea2c58c41243a90bb..a8b45ec087bb167545610794157d0f91ce9a69c8 100644 --- a/qt/widgets/mplcpp/test/AxesTest.h +++ b/qt/widgets/mplcpp/test/AxesTest.h @@ -1,3 +1,9 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_AXESTEST_H #define MPLCPP_AXESTEST_H @@ -62,6 +68,43 @@ public: TS_ASSERT_EQUALS(format[1], line.pyobj().attr("get_marker")()); } + void testSetXScaleWithKnownScaleType() { + Axes axes(pyAxes()); + axes.setXScale("symlog"); + } + + void testSetYScaleWithKnownScaleType() { + Axes axes(pyAxes()); + axes.setYScale("symlog"); + } + + void testGetXLimReturnsXLimits() { + Axes axes(pyAxes()); + axes.plot({5, 6, 7, 8}, {10, 11, 12, 13}); + axes.setXLim(4, 9); + auto xlimits = axes.getXLim(); + TS_ASSERT_DELTA(4, std::get<0>(xlimits), 1e-5); + TS_ASSERT_DELTA(9, std::get<1>(xlimits), 1e-5); + } + + void testGetYLimReturnsYLimits() { + Axes axes(pyAxes()); + axes.plot({5, 6, 7, 8}, {10, 11, 12, 13}); + axes.setYLim(9, 14); + auto ylimits = axes.getYLim(); + TS_ASSERT_DELTA(9, std::get<0>(ylimits), 1e-5); + TS_ASSERT_DELTA(14, std::get<1>(ylimits), 1e-5); + } + + void testTextAddsTextAddGivenCoordinate() { + Axes axes(pyAxes()); + auto artist = axes.text(0.5, 0.4, "test", "left"); + + TS_ASSERT_EQUALS("test", artist.pyobj().attr("get_text")()); + TS_ASSERT_EQUALS(0.5, artist.pyobj().attr("get_position")()[0]); + TS_ASSERT_EQUALS(0.4, artist.pyobj().attr("get_position")()[1]); + } + // ----------------- failure tests --------------------- void testPlotThrowsWithEmptyData() { Axes axes(pyAxes()); @@ -70,6 +113,16 @@ public: TS_ASSERT_THROWS(axes.plot({}, {1}), std::invalid_argument); } + void testSetXScaleWithUnknownScaleTypeThrows() { + Axes axes(pyAxes()); + TS_ASSERT_THROWS(axes.setXScale("notascaletype"), std::invalid_argument); + } + + void testSetYScaleWithUnknownScaleTypeThrows() { + Axes axes(pyAxes()); + TS_ASSERT_THROWS(axes.setYScale("notascaletype"), std::invalid_argument); + } + private: Python::Object pyAxes() { // An Axes requires a figure and rectangle definition diff --git a/qt/widgets/mplcpp/test/CMakeLists.txt b/qt/widgets/mplcpp/test/CMakeLists.txt index bad3326489d8d6e6a61a5c0b7621e00f8a575e0e..615c313942d269c86ebc704729348e217955d993 100644 --- a/qt/widgets/mplcpp/test/CMakeLists.txt +++ b/qt/widgets/mplcpp/test/CMakeLists.txt @@ -5,9 +5,13 @@ set ( TEST_FILES ArtistTest.h AxesTest.h ColormapTest.h + ColorConverterTest.h + CyclerTest.h FigureTest.h Line2DTest.h + MantidColormapTest.h ScalarMappableTest.h + ZoomerTest.h ) if ( NOT APPLE ) @@ -17,7 +21,7 @@ if ( NOT APPLE ) SipTest.h ) endif() - + set (CXXTEST_EXTRA_HEADER_INCLUDE ${CMAKE_CURRENT_LIST_DIR}/MplCppTestInitialization.h) mtd_add_qt_tests (TARGET_NAME MantidQtWidgetsMplCppTest diff --git a/qt/widgets/mplcpp/test/ColorConverterTest.h b/qt/widgets/mplcpp/test/ColorConverterTest.h new file mode 100644 index 0000000000000000000000000000000000000000..b5fddc84cff2edb07a9f9128813b20afa710113d --- /dev/null +++ b/qt/widgets/mplcpp/test/ColorConverterTest.h @@ -0,0 +1,33 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MPLCPP_COLORCONVERTERTEST_H +#define MPLCPP_COLORCONVERTERTEST_H + +#include "MantidQtWidgets/MplCpp/ColorConverter.h" +#include <QRgb> +#include <cxxtest/TestSuite.h> + +using MantidQt::Widgets::MplCpp::ColorConverter; +namespace Python = MantidQt::Widgets::MplCpp::Python; + +class ColorConverterTest : public CxxTest::TestSuite { +public: + static ColorConverterTest *createSuite() { return new ColorConverterTest; } + static void destroySuite(ColorConverterTest *suite) { delete suite; } + +public: + // ----------------- success tests --------------------- + void testKnownColorIsTranslated() { + // matplotlib cyan=(0,0.75,0.75). See matplotlib.colors + auto color = ColorConverter::toRGB(Python::NewRef(Py_BuildValue("s", "c"))); + TS_ASSERT_EQUALS(0, color.red()); + TS_ASSERT_EQUALS(191, color.blue()); + TS_ASSERT_EQUALS(191, color.green()); + } +}; + +#endif // MPLCPP_COLORCONVERTERTEST_H diff --git a/qt/widgets/mplcpp/test/ColormapTest.h b/qt/widgets/mplcpp/test/ColormapTest.h index 043c550d863abf25bf307b5ca19dca2e94ccc6b1..d501fcce6b9091c22408d9a347e681ac39904148 100644 --- a/qt/widgets/mplcpp/test/ColormapTest.h +++ b/qt/widgets/mplcpp/test/ColormapTest.h @@ -1,3 +1,9 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_COLORMAPTEST_H #define MPLCPP_COLORMAPTEST_H @@ -6,9 +12,10 @@ #include <cxxtest/TestSuite.h> -using Mantid::PythonInterface::PythonRuntimeError; +using Mantid::PythonInterface::PythonException; using MantidQt::Widgets::MplCpp::Colormap; using MantidQt::Widgets::MplCpp::Python::Object; +using MantidQt::Widgets::MplCpp::cmapExists; using MantidQt::Widgets::MplCpp::getCMap; class ColormapTest : public CxxTest::TestSuite { @@ -22,6 +29,12 @@ public: TS_ASSERT_THROWS_NOTHING(getCMap("jet")); } + void testcmapExistsForKnownCMapReturnsTrue() { TS_ASSERT(cmapExists("jet")); } + + void testcmapExistsForUnknownCMapReturnsFalse() { + TS_ASSERT(!cmapExists("NotAKnownCMap")); + } + void testConstructionColorMapInstanceIsSuccessful() { auto jet = getCMap("jet"); TS_ASSERT_EQUALS("jet", jet.pyobj().attr("name")); @@ -29,7 +42,7 @@ public: // ----------------------- Failure tests ------------------------ void testgetCMapWithUnknownCMapThrowsException() { - TS_ASSERT_THROWS(getCMap("AnUnknownName"), PythonRuntimeError); + TS_ASSERT_THROWS(getCMap("AnUnknownName"), PythonException); } void testConstructionWithNonColorMapObjectThrows() { diff --git a/qt/widgets/mplcpp/test/ColorsTest.h b/qt/widgets/mplcpp/test/ColorsTest.h index 4d266b9457bf07c933da83745afb3d6cc6c445c1..f6024bd9a4c3371f735ac42d0d0cb21085e36e44 100644 --- a/qt/widgets/mplcpp/test/ColorsTest.h +++ b/qt/widgets/mplcpp/test/ColorsTest.h @@ -1,3 +1,9 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_COLORSTEST_H #define MPLCPP_COLORSTEST_H @@ -6,6 +12,7 @@ #include "MantidQtWidgets/MplCpp/Colors.h" using MantidQt::Widgets::MplCpp::Normalize; +using MantidQt::Widgets::MplCpp::NormalizeBase; using MantidQt::Widgets::MplCpp::PowerNorm; using MantidQt::Widgets::MplCpp::SymLogNorm; @@ -15,25 +22,62 @@ public: static void destroySuite(ColorsTest *suite) { delete suite; } public: - void testNormalize() { - Normalize norm(-1, 1); - TS_ASSERT_EQUALS(-1, norm.pyobj().attr("vmin")); - TS_ASSERT_EQUALS(1, norm.pyobj().attr("vmax")); + void testDefaultNormalizeAndAutoscale() { + Normalize norm; + const double vmin(-1), vmax(1); + auto range = norm.autoscale(std::make_tuple(vmin, vmax)); + + TS_ASSERT_EQUALS(vmin, std::get<0>(range)); + TS_ASSERT_EQUALS(vmax, std::get<1>(range)); + assertColorLimits(norm, vmin, vmax); + } + + void testNormalizeWithLimits() { + const double vmin(-1), vmax(1); + Normalize norm(vmin, vmax); + assertColorLimits(norm, vmin, vmax); + } + + void testDefaultSymLogNormAndAutoscale() { + SymLogNorm norm(0.001, 2); + const double vmin(-1), vmax(1); + auto range = norm.autoscale(std::make_tuple(vmin, vmax)); + + TS_ASSERT_EQUALS(vmin, std::get<0>(range)); + TS_ASSERT_EQUALS(vmax, std::get<1>(range)); + assertColorLimits(norm, vmin, vmax); } void testSymLogNorm() { - SymLogNorm norm(0.001, 2, -1, 1); + const double vmin(-1), vmax(1); + SymLogNorm norm(0.001, 2, vmin, vmax); // No public api method for access linscale TS_ASSERT_EQUALS(0.001, norm.pyobj().attr("linthresh")); - TS_ASSERT_EQUALS(-1, norm.pyobj().attr("vmin")); - TS_ASSERT_EQUALS(1, norm.pyobj().attr("vmax")); + assertColorLimits(norm, vmin, vmax); + } + + void testDefaultPowerNormAndAutoscale() { + PowerNorm norm(2); + // vmin will get rescaled as -1 is not valid + const double vmin(-1), vmax(1), validMin(0.); + auto range = norm.autoscale(std::make_tuple(vmin, vmax)); + + TS_ASSERT_EQUALS(validMin, std::get<0>(range)); + TS_ASSERT_EQUALS(vmax, std::get<1>(range)); + assertColorLimits(norm, validMin, vmax); } void testPowerNorm() { - PowerNorm norm(2, -1, 1); + const double vmin(-1), vmax(1); + PowerNorm norm(2, vmin, vmax); TS_ASSERT_EQUALS(2, norm.pyobj().attr("gamma")); - TS_ASSERT_EQUALS(-1, norm.pyobj().attr("vmin")); - TS_ASSERT_EQUALS(1, norm.pyobj().attr("vmax")); + assertColorLimits(norm, vmin, vmax); + } + +private: + void assertColorLimits(const NormalizeBase &norm, double vmin, double vmax) { + TS_ASSERT_EQUALS(vmin, norm.pyobj().attr("vmin")); + TS_ASSERT_EQUALS(vmax, norm.pyobj().attr("vmax")); } }; diff --git a/qt/widgets/mplcpp/test/CyclerTest.h b/qt/widgets/mplcpp/test/CyclerTest.h new file mode 100644 index 0000000000000000000000000000000000000000..2381ea574868014be0864e122e0c50823afc6f81 --- /dev/null +++ b/qt/widgets/mplcpp/test/CyclerTest.h @@ -0,0 +1,45 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MPLCPPTEST_CYCLERTEST_H +#define MPLCPPTEST_CYCLERTEST_H + +#include "MantidQtWidgets/MplCpp/Cycler.h" + +#include <cxxtest/TestSuite.h> + +using MantidQt::Widgets::MplCpp::Cycler; +using MantidQt::Widgets::MplCpp::cycler; +namespace Python = MantidQt::Widgets::MplCpp::Python; + +class CyclerTest : public CxxTest::TestSuite { +public: + static CyclerTest *createSuite() { return new CyclerTest; } + static void destroySuite(CyclerTest *suite) { delete suite; } + +public: + // ----------------- success tests --------------------- + void testCyclerFactoryFunctionReturnsExpectedCycler() { + const std::string label("colors"); + auto colors = cycler(label.c_str(), "rgb"); + + auto toDict = [&label](const char *value) { + return Python::NewRef(Py_BuildValue("{ss}", label.c_str(), value)); + }; + TS_ASSERT_EQUALS(toDict("r"), colors()); + TS_ASSERT_EQUALS(toDict("g"), colors()); + TS_ASSERT_EQUALS(toDict("b"), colors()); + TS_ASSERT_EQUALS(toDict("r"), colors()); + } + // ----------------- failure tests --------------------- + + void testConstructWithNonCyclerThrowsInvalidArgument() { + Python::Object none; + TS_ASSERT_THROWS(Cycler cycler(none), std::invalid_argument); + } +}; + +#endif // MPLCPPTEST_CYCLERTEST_H diff --git a/qt/widgets/mplcpp/test/FigureCanvasQtTest.h b/qt/widgets/mplcpp/test/FigureCanvasQtTest.h index 1dcc3796308a844726219d0ea53b42bae1d51021..95fddf26aa3d54fddd30f52eaf6dd2df33c7fe9a 100644 --- a/qt/widgets/mplcpp/test/FigureCanvasQtTest.h +++ b/qt/widgets/mplcpp/test/FigureCanvasQtTest.h @@ -1,3 +1,9 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_FIGURECANVASQTTEST_H #define MPLCPP_FIGURECANVASQTTEST_H @@ -24,7 +30,6 @@ public: } void testConstructionCapturesGivenAxesObject() { - using namespace MantidQt::Widgets::MplCpp; Figure fig; fig.addSubPlot(221); FigureCanvasQt canvas{std::move(fig)}; @@ -33,6 +38,17 @@ public: TS_ASSERT_EQUALS(2, geometry[1]); TS_ASSERT_EQUALS(1, geometry[2]); } + + void testToDataCoordinatesReturnsExpectedPoint() { + FigureCanvasQt canvas{111}; + canvas.gca().plot({1, 2, 3, 4, 5}, {1, 2, 3, 4, 5}); + + auto dataCoords = + canvas.toDataCoords(QPoint(static_cast<int>(canvas.width() * 0.5), + static_cast<int>(canvas.height() * 0.25))); + TS_ASSERT_DELTA(2.9, dataCoords.x(), 0.25); + TS_ASSERT_DELTA(4.25, dataCoords.y(), 0.25); + } }; #endif // MPLCPP_FIGURECANVASQTTEST_H diff --git a/qt/widgets/mplcpp/test/FigureTest.h b/qt/widgets/mplcpp/test/FigureTest.h index 636289799ea6cf8462980c0029f553c441e10f1e..b548909d6386d384cb2b7dc7432f2df3590e7f02 100644 --- a/qt/widgets/mplcpp/test/FigureTest.h +++ b/qt/widgets/mplcpp/test/FigureTest.h @@ -1,11 +1,21 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_FIGURETEST_H #define MPLCPP_FIGURETEST_H #include "MantidQtWidgets/MplCpp/Figure.h" +#include "MantidQtWidgets/MplCpp/ScalarMappable.h" #include <cxxtest/TestSuite.h> using MantidQt::Widgets::MplCpp::Figure; +using MantidQt::Widgets::MplCpp::Normalize; +using MantidQt::Widgets::MplCpp::ScalarMappable; +namespace Python = MantidQt::Widgets::MplCpp::Python; class FigureTest : public CxxTest::TestSuite { public: @@ -23,15 +33,43 @@ public: TS_ASSERT_EQUALS(false, fig.pyobj().attr("get_tight_layout")()); } + void testGcaReturnsAxesIfNotAdded() { + Figure fig{false}; + TS_ASSERT_THROWS_NOTHING(fig.gca()); + } + void testAddAxes() { Figure fig{false}; TS_ASSERT_THROWS_NOTHING(fig.addAxes(0.1, 0.1, 0.9, 0.9)); } + void testSetFaceColor() { + Figure fig{false}; + fig.setFaceColor("r"); + auto rgb = fig.pyobj().attr("get_facecolor")(); + TS_ASSERT_EQUALS(1.0, rgb[0]); + TS_ASSERT_EQUALS(0.0, rgb[1]); + TS_ASSERT_EQUALS(0.0, rgb[2]); + } + void testSubPlot() { Figure fig{false}; TS_ASSERT_THROWS_NOTHING(fig.addSubPlot(111)); } + + void testColorbar() { + Figure fig{false}; + auto cax = fig.addAxes(0.1, 0.1, 0.9, 0.9); + ScalarMappable mappable(Normalize(-1, 1), "jet"); + + TS_ASSERT_THROWS_NOTHING(fig.colorbar(mappable, cax)); + } + + // -------------------------- Failure tests --------------------------- + void testFigureConstructedWithNonFigureThrows() { + TS_ASSERT_THROWS(Figure fig(Python::NewRef(Py_BuildValue("(i)", 1))), + std::invalid_argument); + } }; #endif // FigureTEST_H diff --git a/qt/widgets/mplcpp/test/Line2DTest.h b/qt/widgets/mplcpp/test/Line2DTest.h index 3d44efeff3b53a2e3c6c0675afee1475a061226e..35b11da3f4215283d7a3cc803bd390fe2bc753ec 100644 --- a/qt/widgets/mplcpp/test/Line2DTest.h +++ b/qt/widgets/mplcpp/test/Line2DTest.h @@ -1,7 +1,14 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_LINE2DTEST_H #define MPLCPP_LINE2DTEST_H #include "MantidQtWidgets/MplCpp/Line2D.h" +#include <QRgb> #include <cxxtest/TestSuite.h> using namespace MantidQt::Widgets::MplCpp; @@ -13,7 +20,17 @@ public: // ---------------------- success tests -------------------- void testConstructionRequiresMplLine2DObject() { - TS_ASSERT_THROWS_NOTHING(Line2D line(pyLine2D(), {}, {})); + TS_ASSERT_THROWS_NOTHING(Line2D line(pyLine2D(), {1, 2}, {1, 2})); + } + + void testGetColorReturnsExpectedColor() { + Line2D line(pyLine2D(), {1, 2}, {1, 2}); + line.pyobj().attr("set_color")("r"); + + auto color = line.getColor(); + TS_ASSERT_EQUALS(255, color.red()); + TS_ASSERT_EQUALS(0, color.green()); + TS_ASSERT_EQUALS(0, color.blue()); } // ---------------------- failure tests -------------------- diff --git a/qt/widgets/mplcpp/test/MantidColormapTest.h b/qt/widgets/mplcpp/test/MantidColormapTest.h new file mode 100644 index 0000000000000000000000000000000000000000..4b5f391ff1b42bb4abdfca610455e41810f2dba8 --- /dev/null +++ b/qt/widgets/mplcpp/test/MantidColormapTest.h @@ -0,0 +1,35 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MANTIDCOLORMAPTEST_H +#define MANTIDCOLORMAPTEST_H + +#include "MantidQtWidgets/MplCpp/MantidColorMap.h" + +#include <cxxtest/TestSuite.h> + +using MantidQt::Widgets::MplCpp::MantidColorMap; + +class MantidColormapTest : public CxxTest::TestSuite { +public: + static MantidColormapTest *createSuite() { return new MantidColormapTest; } + static void destroySuite(MantidColormapTest *suite) { delete suite; } + +public: + // ----------------- success tests --------------------- + void testExistsReturnsEmptyStringIfMapExists() { + TS_ASSERT_EQUALS("jet", MantidColorMap::exists("jet")); + } + + // ----------------- failure tests --------------------- + + void testExistsThrowsIfMapDoesNotExist() { + using Mantid::PythonInterface::PythonException; + TS_ASSERT_THROWS(MantidColorMap::exists("NotAColormap"), PythonException); + } +}; + +#endif // MANTIDCOLORMAPTEST_H diff --git a/qt/widgets/mplcpp/test/MplCppTestInitialization.h b/qt/widgets/mplcpp/test/MplCppTestInitialization.h index 98dd2fd8fe4a87760387c3bc16cb995337a1918e..b881938f139f4802fafaa2968330047723f10187 100644 --- a/qt/widgets/mplcpp/test/MplCppTestInitialization.h +++ b/qt/widgets/mplcpp/test/MplCppTestInitialization.h @@ -1,3 +1,9 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPPTESTGLOBALINITIALIZATION_H #define MPLCPPTESTGLOBALINITIALIZATION_H @@ -17,6 +23,7 @@ class PythonInterpreter : CxxTest::GlobalFixture { public: bool setUpWorld() override { Py_Initialize(); + PyEval_InitThreads(); Mantid::PythonInterface::importNumpy(); return Py_IsInitialized(); } diff --git a/qt/widgets/mplcpp/test/ScalarMappableTest.h b/qt/widgets/mplcpp/test/ScalarMappableTest.h index 8aa8bb59bf16f043e5b0d57ab5749b4344f719fe..65e69285d56caae8de75f911c53b5ab557d2426d 100644 --- a/qt/widgets/mplcpp/test/ScalarMappableTest.h +++ b/qt/widgets/mplcpp/test/ScalarMappableTest.h @@ -1,3 +1,9 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_SCALARMAPPABLETEST_H #define MPLCPP_SCALARMAPPABLETEST_H @@ -10,8 +16,10 @@ #include <cxxtest/TestSuite.h> using MantidQt::Widgets::MplCpp::Normalize; +using MantidQt::Widgets::MplCpp::PowerNorm; using MantidQt::Widgets::MplCpp::ScalarMappable; using MantidQt::Widgets::MplCpp::getCMap; +namespace Python = MantidQt::Widgets::MplCpp::Python; class ScalarMappableTest : public CxxTest::TestSuite { public: @@ -25,6 +33,68 @@ public: ScalarMappable mappable(Normalize(-1, 1), getCMap("jet"))); } + void testConstructionWithValidCMapAsStringAndNormalize() { + TS_ASSERT_THROWS_NOTHING(ScalarMappable mappable(Normalize(-1, 1), "jet")); + } + + void testSetCMapAsStringResetsColormap() { + ScalarMappable mappable(Normalize(-1, 1), "jet"); + mappable.setCmap("coolwarm"); + + TS_ASSERT_EQUALS("coolwarm", mappable.pyobj().attr("cmap").attr("name")); + } + + void testSetCMapResetsColormap() { + ScalarMappable mappable(Normalize(-1, 1), "jet"); + mappable.setCmap(getCMap("coolwarm")); + + TS_ASSERT_EQUALS("coolwarm", mappable.pyobj().attr("cmap").attr("name")); + } + + void testSetNormResetsNormalizeInstance() { + ScalarMappable mappable(Normalize(-1, 1), "jet"); + mappable.setNorm(PowerNorm(2, 0, 1)); + + auto norm = Python::Object(mappable.pyobj().attr("norm")); + TS_ASSERT(PyObject_HasAttrString(norm.ptr(), "gamma")); + } + + void testSetCLimSetsMinAndMaxWhenProvided() { + ScalarMappable mappable(Normalize(-1, 1), "jet"); + mappable.setClim(-10, 10); + auto norm = mappable.pyobj().attr("norm"); + TS_ASSERT_EQUALS(-10, norm.attr("vmin")); + TS_ASSERT_EQUALS(10, norm.attr("vmax")); + } + + void testSetCLimSetsMinOnlyWhenMaxNotProvided() { + ScalarMappable mappable(Normalize(-1, 1), "jet"); + mappable.setClim(-10); + auto norm = mappable.pyobj().attr("norm"); + TS_ASSERT_EQUALS(-10, norm.attr("vmin")); + TS_ASSERT_EQUALS(1, norm.attr("vmax")); + } + + void testSetCLimSetsMaxOnlyWhenMinNotProvided() { + ScalarMappable mappable(Normalize(-1, 1), "jet"); + mappable.setClim(boost::none, 10); + auto norm = mappable.pyobj().attr("norm"); + TS_ASSERT_EQUALS(-1, norm.attr("vmin")); + TS_ASSERT_EQUALS(10, norm.attr("vmax")); + } + + void testSetCLimSetsNothingWhenNothingProvided() { + ScalarMappable mappable(Normalize(-1, 1), "jet"); + mappable.setClim(boost::none, boost::none); + auto norm = mappable.pyobj().attr("norm"); + TS_ASSERT_EQUALS(-1, norm.attr("vmin")); + TS_ASSERT_EQUALS(1, norm.attr("vmax")); + + mappable.setClim(); + TS_ASSERT_EQUALS(-1, norm.attr("vmin")); + TS_ASSERT_EQUALS(1, norm.attr("vmax")); + } + void testtoRGBAWithNoAlphaGivesDefault() { ScalarMappable mappable(Normalize(-1, 1), getCMap("jet")); auto rgba = mappable.toRGBA(0.0); @@ -42,6 +112,39 @@ public: TS_ASSERT_EQUALS(121, qBlue(rgba)); TS_ASSERT_EQUALS(127, qAlpha(rgba)); } + + void testtoRGBAArrayWithNoAlphaGivesDefault() { + ScalarMappable mappable(Normalize(-1, 1), getCMap("jet")); + auto rgba = mappable.toRGBA({0.0, 0.75}); + TS_ASSERT_EQUALS(2, rgba.size()); + + TS_ASSERT_EQUALS(124, qRed(rgba[0])); + TS_ASSERT_EQUALS(255, qGreen(rgba[0])); + TS_ASSERT_EQUALS(121, qBlue(rgba[0])); + TS_ASSERT_EQUALS(255, qAlpha(rgba[0])); + + TS_ASSERT_EQUALS(255, qRed(rgba[1])); + TS_ASSERT_EQUALS(29, qGreen(rgba[1])); + TS_ASSERT_EQUALS(0, qBlue(rgba[1])); + TS_ASSERT_EQUALS(255, qAlpha(rgba[1])); + } + + void testtoRGBAArrayWithAlpha() { + ScalarMappable mappable(Normalize(-1, 1), getCMap("jet")); + const double alpha = 0.5; + auto rgba = mappable.toRGBA({0.0, 0.75}, alpha); + TS_ASSERT_EQUALS(2, rgba.size()); + + TS_ASSERT_EQUALS(124, qRed(rgba[0])); + TS_ASSERT_EQUALS(255, qGreen(rgba[0])); + TS_ASSERT_EQUALS(121, qBlue(rgba[0])); + TS_ASSERT_EQUALS(static_cast<int>(alpha * 255), qAlpha(rgba[0])); + + TS_ASSERT_EQUALS(255, qRed(rgba[1])); + TS_ASSERT_EQUALS(29, qGreen(rgba[1])); + TS_ASSERT_EQUALS(0, qBlue(rgba[1])); + TS_ASSERT_EQUALS(static_cast<int>(alpha * 255), qAlpha(rgba[1])); + } }; #endif // MPLCPP_SCALARMAPPABLETEST_H diff --git a/qt/widgets/mplcpp/test/SipTest.h b/qt/widgets/mplcpp/test/SipTest.h index 7e72b106d6860d5a533cd050c43dc9be3cc1166f..1884f8f405094c082988699d5d4f6493c8933b98 100644 --- a/qt/widgets/mplcpp/test/SipTest.h +++ b/qt/widgets/mplcpp/test/SipTest.h @@ -1,3 +1,9 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + #ifndef MPLCPP_SIPTEST_H #define MPLCPP_SIPTEST_H diff --git a/qt/widgets/mplcpp/test/ZoomerTest.h b/qt/widgets/mplcpp/test/ZoomerTest.h new file mode 100644 index 0000000000000000000000000000000000000000..99e2a43b64b7a570ea13307f24da4eea40af27f8 --- /dev/null +++ b/qt/widgets/mplcpp/test/ZoomerTest.h @@ -0,0 +1,81 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef MPLCPP_ZOOMERTEST_H +#define MPLCPP_ZOOMERTEST_H + +#include "MantidQtWidgets/MplCpp/FigureCanvasQt.h" +#include "MantidQtWidgets/MplCpp/Zoomer.h" + +#include <cxxtest/TestSuite.h> + +using MantidQt::Widgets::MplCpp::FigureCanvasQt; +using MantidQt::Widgets::MplCpp::Zoomer; +namespace Python = MantidQt::Widgets::MplCpp::Python; + +class ZoomerTest : public CxxTest::TestSuite { +public: + static ZoomerTest *createSuite() { return new ZoomerTest; } + static void destroySuite(ZoomerTest *suite) { delete suite; } + +public: + // ----------------------------- success tests ------------------------------- + void testConstructionWithFigureCanvasSucceeds() { + FigureCanvasQt canvas{111}; + TS_ASSERT_THROWS_NOTHING(Zoomer zoomer(&canvas)); + } + + void testDefaultHasZoomDisabled() { + FigureCanvasQt canvas{111}; + Zoomer zoomer(&canvas); + + TS_ASSERT_EQUALS(false, zoomer.isZoomEnabled()); + } + + void testZoomOutDoesNotThrow() { + FigureCanvasQt canvas{111}; + canvas.gca().plot({1, 2, 3, 4, 5}, {1, 2, 3, 4, 5}); + Zoomer zoomer(&canvas); + zoomIn(&zoomer); + + TS_ASSERT_THROWS_NOTHING(zoomer.zoomOut()); + auto xlim(Python::Object(canvas.gca().pyobj().attr("get_xlim")())); + // Do the axis limits get back to somewhere "close" to what is expected + TS_ASSERT_DELTA(1.0, PyFloat_AsDouble(Python::Object(xlim[0]).ptr()), 0.25); + TS_ASSERT_DELTA(5.0, PyFloat_AsDouble(Python::Object(xlim[1]).ptr()), 0.25); + } + +private: + void zoomIn(Zoomer *zoomer) { + zoomer->pyobj().attr("press_zoom")(createDummyMplMouseEvent(100, 100)); + // events myst be >=5 pixels apart to count + zoomer->pyobj().attr("release_zoom")(createDummyMplMouseEvent(110, 110)); + } + + Python::Object createDummyMplMouseEvent(double xpos, double ypos) { + try { + auto mainModule = Python::NewRef(PyImport_ImportModule("__main__")); + auto builtinsDict = + Python::BorrowedRef(PyModule_GetDict(mainModule.ptr())); + auto createMouseEventFnSrc = + QString("def createDummyMouseEvent(xpos, ypos):\n" + " class MouseEvent(object):\n" + " x, y = xpos, ypos\n" + " button = 1\n" + " key = None\n" + " return MouseEvent()\n"); + Python::Dict context; + context.update(builtinsDict); + Python::NewRef(PyRun_String(createMouseEventFnSrc.toLatin1().constData(), + Py_file_input, context.ptr(), context.ptr())); + return context["createDummyMouseEvent"](xpos, ypos); + } catch (Python::ErrorAlreadySet &) { + throw Mantid::PythonInterface::PythonException(); + } + } +}; + +#endif // MPLCPP_ZOOMERTEST_H diff --git a/qt/widgets/sliceviewer/src/ConcretePeaksPresenter.cpp b/qt/widgets/sliceviewer/src/ConcretePeaksPresenter.cpp index 6f362931818287f80b2bb71b925706467bf013fd..fe78678997f5a3e85c5636af1bd4bcd510d734d2 100644 --- a/qt/widgets/sliceviewer/src/ConcretePeaksPresenter.cpp +++ b/qt/widgets/sliceviewer/src/ConcretePeaksPresenter.cpp @@ -21,7 +21,6 @@ #include "MantidQtWidgets/SliceViewer/UpdateableOnDemand.h" #include "MantidQtWidgets/SliceViewer/ZoomableOnDemand.h" #include <boost/regex.hpp> -#include <boost/scoped_ptr.hpp> using namespace Mantid::API; using namespace Mantid::Kernel; diff --git a/qt/widgets/sliceviewer/test/ConcretePeaksPresenterTest.h b/qt/widgets/sliceviewer/test/ConcretePeaksPresenterTest.h index 8a6123ee64034a476ef523c62cfc8eb7b640c111..b6ecdaa7c7d47842749bc3cf8b116debb467e72b 100644 --- a/qt/widgets/sliceviewer/test/ConcretePeaksPresenterTest.h +++ b/qt/widgets/sliceviewer/test/ConcretePeaksPresenterTest.h @@ -29,7 +29,6 @@ using namespace Mantid::API; using namespace Mantid::Geometry; using namespace Mantid::Kernel; using namespace testing; -using boost::regex; // Alias. using MDGeometry_sptr = boost::shared_ptr<Mantid::API::MDGeometry>; diff --git a/qt/widgets/spectrumviewer/src/SVConnections.cpp b/qt/widgets/spectrumviewer/src/SVConnections.cpp index df9013d44cd963bc9105b886cad0ef1208ee86ad..8c4d21c664524249fb4280216b26e70b41f2c8d3 100644 --- a/qt/widgets/spectrumviewer/src/SVConnections.cpp +++ b/qt/widgets/spectrumviewer/src/SVConnections.cpp @@ -546,7 +546,7 @@ void SVConnections::loadColorMap(const QString &file_name) { m_colorMapFileName = MantidColorMap::chooseColorMap("", m_svMainWindow); MantidColorMap *mantid_color_map = - new MantidColorMap(file_name, GraphOptions::Linear); + new MantidColorMap(file_name, MantidColorMap::ScaleType::Linear); QwtDoubleInterval interval(0.0, 255.0); QVector<QRgb> mantid_color_table; diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index e8f37ca00141f958e5504c2b3085d939c102d9d5..264fcbbcb5680083b1034649aa5422a7d0f1b43a 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -1,26 +1,21 @@ # Subdirectories from which ui files need processing to py files -add_subdirectory(FilterEvents) +# FilterEvents doesn't need any special work add_subdirectory(Interface/ui) add_subdirectory(TofConverter) -add_subdirectory(HFIR_4Circle_Reduction) add_subdirectory(ErrorReporter) # Chain all required interface custom targets into CompilePyUI add_custom_target(CompilePyUI DEPENDS - CompileUIFilterEvents CompileUITofConverter CompileUIUI - CompileUIHFIR_4Circle_Reduction CompileUIErrorReporter ) # Put them into the 'CompileUI' folder or group in VS and the like, for convenience set_property ( TARGET CompilePyUI PROPERTY FOLDER "CompilePyUI" ) -set_property ( TARGET CompileUIFilterEvents PROPERTY FOLDER "CompilePyUI" ) set_property ( TARGET CompileUITofConverter PROPERTY FOLDER "CompilePyUI" ) set_property ( TARGET CompileUIUI PROPERTY FOLDER "CompilePyUI" ) -set_property ( TARGET CompileUIHFIR_4Circle_Reduction PROPERTY FOLDER "CompilePyUI" ) set_property ( TARGET CompileUIErrorReporter PROPERTY FOLDER "CompilePyUI" ) # External GUIs diff --git a/scripts/DGSPlanner.py b/scripts/DGSPlanner.py index 6472c1efe7877ddc41f34f5162b54ce6a6d9d9de..b6407a94c248f9f83e1fb7965122cef41546b355 100644 --- a/scripts/DGSPlanner.py +++ b/scripts/DGSPlanner.py @@ -7,23 +7,11 @@ #pylint: disable=invalid-name,unused-import from __future__ import (absolute_import, division, print_function) import sys -from PyQt4 import QtGui from DGSPlanner import DGSPlannerGUI +from gui_helper import get_qapplication - -def qapp(): - if QtGui.QApplication.instance(): - _app = QtGui.QApplication.instance() - else: - _app = QtGui.QApplication(sys.argv) - return _app - - -if __name__ == '__main__': - app = qapp() - planner = DGSPlannerGUI.DGSPlannerGUI() - planner.show() - try: #check if started from within mantidplot - import mantidplot # noqa - except ImportError: - sys.exit(app.exec_()) +app, within_mantid = get_qapplication() +planner = DGSPlannerGUI.DGSPlannerGUI() +planner.show() +if not within_mantid: + sys.exit(app.exec_()) diff --git a/scripts/DGSPlanner/ClassicUBInputWidget.py b/scripts/DGSPlanner/ClassicUBInputWidget.py index 9ecdefabee9acb93bef8b5982c6e56030ab502db..33d719916512ad03edff2cf4dcacbff1f1834977 100644 --- a/scripts/DGSPlanner/ClassicUBInputWidget.py +++ b/scripts/DGSPlanner/ClassicUBInputWidget.py @@ -6,20 +6,21 @@ # SPDX - License - Identifier: GPL - 3.0 + #pylint: disable=invalid-name,no-name-in-module,too-many-instance-attributes from __future__ import (absolute_import, division, print_function) -from PyQt4 import QtCore, QtGui +from qtpy import QtWidgets, QtGui, QtCore import sys import mantid import numpy from DGSPlanner.ValidateOL import ValidateOL + try: - from PyQt4.QtCore import QString + from qtpy.QtCore import QString except ImportError: QString = type("") -class ClassicUBInputWidget(QtGui.QWidget): +class ClassicUBInputWidget(QtWidgets.QWidget): #signal when lattice is changed and valid - changed=QtCore.pyqtSignal(mantid.geometry.OrientedLattice) + changed=QtCore.Signal(mantid.geometry.OrientedLattice) def __init__(self,ol=None,parent=None): # pylint: disable=unused-argument,super-on-old-class @@ -47,43 +48,43 @@ class ClassicUBInputWidget(QtGui.QWidget): else: self.ol=mantid.geometry.OrientedLattice() #labels - self._labela=QtGui.QLabel('a') - self._labelb=QtGui.QLabel('b') - self._labelc=QtGui.QLabel('c') - self._labelalpha=QtGui.QLabel('alpha') - self._labelbeta=QtGui.QLabel(' beta') - self._labelgamma=QtGui.QLabel('gamma') - self._labelux=QtGui.QLabel('ux') - self._labeluy=QtGui.QLabel('uy') - self._labeluz=QtGui.QLabel('uz') - self._labelvx=QtGui.QLabel('vx') - self._labelvy=QtGui.QLabel('vy') - self._labelvz=QtGui.QLabel('vz') - self._labelclassic=QtGui.QLabel('Lattice parameters') + self._labela=QtWidgets.QLabel('a') + self._labelb=QtWidgets.QLabel('b') + self._labelc=QtWidgets.QLabel('c') + self._labelalpha=QtWidgets.QLabel('alpha') + self._labelbeta=QtWidgets.QLabel(' beta') + self._labelgamma=QtWidgets.QLabel('gamma') + self._labelux=QtWidgets.QLabel('ux') + self._labeluy=QtWidgets.QLabel('uy') + self._labeluz=QtWidgets.QLabel('uz') + self._labelvx=QtWidgets.QLabel('vx') + self._labelvy=QtWidgets.QLabel('vy') + self._labelvz=QtWidgets.QLabel('vz') + self._labelclassic=QtWidgets.QLabel('Lattice parameters') #lineedits - self._edita=QtGui.QLineEdit() + self._edita=QtWidgets.QLineEdit() self._edita.setValidator(self.latticeLengthValidator) - self._editb=QtGui.QLineEdit() + self._editb=QtWidgets.QLineEdit() self._editb.setValidator(self.latticeLengthValidator) - self._editc=QtGui.QLineEdit() + self._editc=QtWidgets.QLineEdit() self._editc.setValidator(self.latticeLengthValidator) - self._editalpha=QtGui.QLineEdit() + self._editalpha=QtWidgets.QLineEdit() self._editalpha.setValidator(self.latticeAngleValidator) - self._editbeta=QtGui.QLineEdit() + self._editbeta=QtWidgets.QLineEdit() self._editbeta.setValidator(self.latticeAngleValidator) - self._editgamma=QtGui.QLineEdit() + self._editgamma=QtWidgets.QLineEdit() self._editgamma.setValidator(self.latticeAngleValidator) - self._editux=QtGui.QLineEdit() + self._editux=QtWidgets.QLineEdit() self._editux.setValidator(self.doubleValidator) - self._edituy=QtGui.QLineEdit() + self._edituy=QtWidgets.QLineEdit() self._edituy.setValidator(self.doubleValidator) - self._edituz=QtGui.QLineEdit() + self._edituz=QtWidgets.QLineEdit() self._edituz.setValidator(self.doubleValidator) - self._editvx=QtGui.QLineEdit() + self._editvx=QtWidgets.QLineEdit() self._editvx.setValidator(self.doubleValidator) - self._editvy=QtGui.QLineEdit() + self._editvy=QtWidgets.QLineEdit() self._editvy.setValidator(self.doubleValidator) - self._editvz=QtGui.QLineEdit() + self._editvz=QtWidgets.QLineEdit() self._editvz.setValidator(self.doubleValidator) self._edita.setFixedWidth(metrics.width("8888.88888")) self._editb.setFixedWidth(metrics.width("8888.88888")) @@ -98,7 +99,7 @@ class ClassicUBInputWidget(QtGui.QWidget): self._editvy.setFixedWidth(metrics.width("8888.88888")) self._editvz.setFixedWidth(metrics.width("8888.88888")) #layout - grid = QtGui.QGridLayout() + grid = QtWidgets.QGridLayout() self.setLayout(grid) grid.addWidget(self._labelclassic,0,0,1,3) grid.addWidget(self._labela,1,0,QtCore.Qt.AlignRight) @@ -225,7 +226,7 @@ class ClassicUBInputWidget(QtGui.QWidget): if __name__=='__main__': - app=QtGui.QApplication(sys.argv) + app=QtWidgets.QApplication(sys.argv) mainForm=ClassicUBInputWidget() mainForm.show() sys.exit(app.exec_()) diff --git a/scripts/DGSPlanner/DGSPlannerGUI.py b/scripts/DGSPlanner/DGSPlannerGUI.py index 226757e4019071c23ffef434a998cd007e0a6364..73ea29bb0f6ce06ae2dd975050cbaf0f36b38ca7 100644 --- a/scripts/DGSPlanner/DGSPlannerGUI.py +++ b/scripts/DGSPlanner/DGSPlannerGUI.py @@ -10,19 +10,13 @@ from . import InstrumentSetupWidget from . import ClassicUBInputWidget from . import MatrixUBInputWidget from . import DimensionSelectorWidget -from PyQt4 import QtCore, QtGui +from qtpy import QtCore, QtWidgets import sys import mantid -import mantidqtpython as mqt from .ValidateOL import ValidateOL -from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas -try: - from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar -except ImportError: - try: - from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar - except ImportError: - raise ImportError("Cannot import navigation toolbar") +import matplotlib +from gui_helper import show_interface_help +from MPLwidgets import * from matplotlib.figure import Figure from mpl_toolkits.axisartist.grid_helper_curvelinear import GridHelperCurveLinear from mpl_toolkits.axisartist import Subplot @@ -41,11 +35,11 @@ def float2Input(x): # pylint: disable=too-many-instance-attributes -class CustomNavigationToolbar(NavigationToolbar): - toolitems = [t for t in NavigationToolbar.toolitems if t[0] in ('Home', 'Pan', 'Zoom')] +class CustomNavigationToolbar(NavigationToolbar2QT): + toolitems = [t for t in NavigationToolbar2QT.toolitems if t[0] in ('Home', 'Pan', 'Zoom')] -class DGSPlannerGUI(QtGui.QWidget): +class DGSPlannerGUI(QtWidgets.QWidget): def __init__(self, ol=None, parent=None): # pylint: disable=unused-argument,super-on-old-class super(DGSPlannerGUI, self).__init__(parent) @@ -59,10 +53,10 @@ class DGSPlannerGUI(QtGui.QWidget): self.updatedOL = False self.wg = None # workspace group self.instrumentWidget = InstrumentSetupWidget.InstrumentSetupWidget(self) - self.setLayout(QtGui.QHBoxLayout()) - controlLayout = QtGui.QVBoxLayout() + self.setLayout(QtWidgets.QHBoxLayout()) + controlLayout = QtWidgets.QVBoxLayout() controlLayout.addWidget(self.instrumentWidget) - self.ublayout = QtGui.QHBoxLayout() + self.ublayout = QtWidgets.QHBoxLayout() self.classic = ClassicUBInputWidget.ClassicUBInputWidget(self.ol) self.ublayout.addWidget(self.classic, alignment=QtCore.Qt.AlignTop, stretch=1) self.matrix = MatrixUBInputWidget.MatrixUBInputWidget(self.ol) @@ -70,16 +64,16 @@ class DGSPlannerGUI(QtGui.QWidget): controlLayout.addLayout(self.ublayout) self.dimensionWidget = DimensionSelectorWidget.DimensionSelectorWidget(self) controlLayout.addWidget(self.dimensionWidget) - plotControlLayout = QtGui.QGridLayout() - self.plotButton = QtGui.QPushButton("Plot", self) - self.oplotButton = QtGui.QPushButton("Overplot", self) - self.helpButton = QtGui.QPushButton("?", self) - self.colorLabel = QtGui.QLabel('Color by angle', self) - self.colorButton = QtGui.QCheckBox(self) + plotControlLayout = QtWidgets.QGridLayout() + self.plotButton = QtWidgets.QPushButton("Plot", self) + self.oplotButton = QtWidgets.QPushButton("Overplot", self) + self.helpButton = QtWidgets.QPushButton("?", self) + self.colorLabel = QtWidgets.QLabel('Color by angle', self) + self.colorButton = QtWidgets.QCheckBox(self) self.colorButton.toggle() - self.aspectLabel = QtGui.QLabel('Aspect ratio 1:1', self) - self.aspectButton = QtGui.QCheckBox(self) - self.saveButton = QtGui.QPushButton("Save Figure", self) + self.aspectLabel = QtWidgets.QLabel('Aspect ratio 1:1', self) + self.aspectButton = QtWidgets.QCheckBox(self) + self.saveButton = QtWidgets.QPushButton("Save Figure", self) plotControlLayout.addWidget(self.plotButton, 0, 0) plotControlLayout.addWidget(self.oplotButton, 0, 1) plotControlLayout.addWidget(self.colorLabel, 0, 2, QtCore.Qt.AlignRight) @@ -97,10 +91,11 @@ class DGSPlannerGUI(QtGui.QWidget): self.canvas = FigureCanvas(self.figure) self.grid_helper = GridHelperCurveLinear((self.tr, self.inv_tr)) self.trajfig = Subplot(self.figure, 1, 1, 1, grid_helper=self.grid_helper) - self.trajfig.hold(True) + if matplotlib.compare_versions('2.1.0',matplotlib.__version__): + self.trajfig.hold(True) # hold is deprecated since 2.1.0, true by default self.figure.add_subplot(self.trajfig) self.toolbar = CustomNavigationToolbar(self.canvas, self) - figureLayout = QtGui.QVBoxLayout() + figureLayout = QtWidgets.QVBoxLayout() figureLayout.addWidget(self.toolbar,0) figureLayout.addWidget(self.canvas,1) self.layout().addLayout(figureLayout) @@ -122,12 +117,13 @@ class DGSPlannerGUI(QtGui.QWidget): self.instrumentWidget.updateAll() self.dimensionWidget.updateChanges() # help - self.assistantProcess = QtCore.QProcess(self) + self.assistant_process = QtCore.QProcess(self) # pylint: disable=protected-access - self.collectionFile = os.path.join(mantid._bindir, '../docs/qthelp/MantidProject.qhc') + self.mantidplot_name='DGS Planner' + self.collection_file = os.path.join(mantid._bindir, '../docs/qthelp/MantidProject.qhc') version = ".".join(mantid.__version__.split(".")[:2]) - self.qtUrl = 'qthelp://org.sphinx.mantidproject.' + version + '/doc/interfaces/DGS Planner.html' - self.externalUrl = 'http://docs.mantidproject.org/nightly/interfaces/DGS Planner.html' + self.qt_url = 'qthelp://org.sphinx.mantidproject.' + version + '/doc/interfaces/DGS Planner.html' + self.external_url = 'http://docs.mantidproject.org/nightly/interfaces/DGS Planner.html' # control for cancel button self.iterations = 0 self.progress_canceled = False @@ -135,13 +131,13 @@ class DGSPlannerGUI(QtGui.QWidget): # register startup mantid.UsageService.registerFeatureUsage("Interface", "DGSPlanner", False) - @QtCore.pyqtSlot(mantid.geometry.OrientedLattice) + @QtCore.Slot(mantid.geometry.OrientedLattice) def updateUB(self, ol): self.ol = ol self.updatedOL = True self.trajfig.clear() - @QtCore.pyqtSlot(dict) + @QtCore.Slot(dict) def updateParams(self, d): if self.sender() is self.instrumentWidget: self.updatedInstrument = True @@ -152,25 +148,15 @@ class DGSPlannerGUI(QtGui.QWidget): self.masterDict.update(copy.deepcopy(d)) def help(self): - try: - import pymantidplot - pymantidplot.proxies.showCustomInterfaceHelp('DGS Planner') - except ImportError: - self.assistantProcess.close() - self.assistantProcess.waitForFinished() - helpapp = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.BinariesPath) + QtCore.QDir.separator() - helpapp += 'assistant' - args = ['-enableRemoteControl', '-collectionFile', self.collectionFile, '-showUrl', self.qtUrl] - if os.path.isfile(helpapp) and os.path.isfile(self.collectionFile): - self.assistantProcess.close() - self.assistantProcess.waitForFinished() - self.assistantProcess.start(helpapp, args) - else: - mqt.MantidQt.API.MantidDesktopServices.openUrl(QtCore.QUrl(self.externalUrl)) + show_interface_help(self.mantidplot_name, + self.assistant_process, + self.collection_file, + self.qt_url, + self.external_url) def closeEvent(self, event): - self.assistantProcess.close() - self.assistantProcess.waitForFinished() + self.assistant_process.close() + self.assistant_process.waitForFinished() event.accept() def _create_goniometer_workspaces(self, gonioAxis0values, gonioAxis1values, gonioAxis2values, progressDialog): @@ -183,7 +169,7 @@ class DGSPlannerGUI(QtGui.QWidget): i += 1 progressDialog.setValue(i) progressDialog.setLabelText("Creating workspace %d of %d..." % (i, self.iterations)) - QtGui.qApp.processEvents() + QtWidgets.qApp.processEvents() if progressDialog.wasCanceled(): self.progress_canceled = True progressDialog.close() @@ -217,11 +203,12 @@ class DGSPlannerGUI(QtGui.QWidget): self.masterDict['gonioSteps'][2]) self.iterations = len(gonioAxis0values) * len(gonioAxis1values) * len(gonioAxis2values) if self.iterations > 10: - reply = QtGui.QMessageBox.warning(self, 'Goniometer', - "More than 10 goniometer settings. This might be long.\n" - "Are you sure you want to proceed?", - QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) - if reply == QtGui.QMessageBox.No: + reply = QtWidgets.QMessageBox.warning(self, 'Goniometer', + "More than 10 goniometer settings. This might be long.\n" + "Are you sure you want to proceed?", + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, + QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.No: return if self.wg is not None: @@ -249,19 +236,19 @@ class DGSPlannerGUI(QtGui.QWidget): __maskWS = mantid.simpleapi.Load(self.masterDict['maskFilename']) mantid.simpleapi.MaskDetectors(Workspace="__temp_instrument", MaskedWorkspace=__maskWS) except (ValueError, RuntimeError) as e: - reply = QtGui.QMessageBox.critical(self, 'Error', - "The following error has occurred in loading the mask:\n" + - str(e) + "\nDo you want to continue without mask?", - QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, - QtGui.QMessageBox.No) - if reply == QtGui.QMessageBox.No: + reply = QtWidgets.QMessageBox.critical(self, 'Error', + "The following error has occurred in loading the mask:\n" + + str(e) + "\nDo you want to continue without mask?", + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, + QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.No: return if self.masterDict['makeFast']: sp = list(range(mantid.mtd["__temp_instrument"].getNumberHistograms())) tomask = sp[1::4] + sp[2::4] + sp[3::4] mantid.simpleapi.MaskDetectors("__temp_instrument", SpectraList=tomask) - progressDialog = QtGui.QProgressDialog(self) + progressDialog = QtWidgets.QProgressDialog(self) progressDialog.setMinimumDuration(0) progressDialog.setCancelButtonText("&Cancel") progressDialog.setRange(0, self.iterations) @@ -282,7 +269,7 @@ class DGSPlannerGUI(QtGui.QWidget): self.updatedOL = False # calculate coverage dimensions = ['Q1', 'Q2', 'Q3', 'DeltaE'] - progressDialog = QtGui.QProgressDialog(self) + progressDialog = QtWidgets.QProgressDialog(self) progressDialog.setMinimumDuration(0) progressDialog.setCancelButtonText("&Cancel") progressDialog.setRange(0, self.iterations) @@ -290,7 +277,7 @@ class DGSPlannerGUI(QtGui.QWidget): for i in range(self.iterations): progressDialog.setValue(i) progressDialog.setLabelText("Calculating orientation %d of %d..." % (i, self.iterations)) - QtGui.qApp.processEvents() + QtWidgets.qApp.processEvents() if progressDialog.wasCanceled(): self.progress_canceled = True progressDialog.close() @@ -351,7 +338,11 @@ class DGSPlannerGUI(QtGui.QWidget): mantid.simpleapi.DeleteWorkspace(__mdws) def save(self): - fileName = str(QtGui.QFileDialog.getSaveFileName(self, 'Save Plot', self.saveDir, '*.png')) + fileName = QtWidgets.QFileDialog.getSaveFileName(self, 'Save Plot', self.saveDir, '*.png') + if isinstance(fileName,tuple): + fileName = fileName[0] + if not fileName: + return data = "Instrument " + self.masterDict['instrument'] + '\n' if self.masterDict['instrument'] == 'HYSPEC': data += "S2 = " + str(self.masterDict['S2']) + '\n' @@ -413,7 +404,7 @@ class DGSPlannerGUI(QtGui.QWidget): if __name__ == '__main__': - app = QtGui.QApplication(sys.argv) + app = QtWidgets.QApplication(sys.argv) orl = mantid.geometry.OrientedLattice(2, 3, 4, 90, 90, 90) mainForm = DGSPlannerGUI() mainForm.show() diff --git a/scripts/DGSPlanner/DimensionSelectorWidget.py b/scripts/DGSPlanner/DimensionSelectorWidget.py index 0c9562200a3e8177989d17f174394a13b2963c7d..8be9f500bbd93404669ca72f4b8db0e493ca7268 100644 --- a/scripts/DGSPlanner/DimensionSelectorWidget.py +++ b/scripts/DGSPlanner/DimensionSelectorWidget.py @@ -6,11 +6,12 @@ # SPDX - License - Identifier: GPL - 3.0 + #pylint: disable=invalid-name,no-name-in-module,too-many-instance-attributes,super-on-old-class,too-few-public-methods from __future__ import (absolute_import, division, print_function) -from PyQt4 import QtGui, QtCore +from qtpy import QtGui, QtCore, QtWidgets import sys import numpy + try: - from PyQt4.QtCore import QString + from qtpy.QtCore import QString except ImportError: QString = type("") @@ -84,8 +85,8 @@ def translation(number,character): return str(number)+character -class DimensionSelectorWidget(QtGui.QWidget): - changed=QtCore.pyqtSignal(dict) +class DimensionSelectorWidget(QtWidgets.QWidget): + changed=QtCore.Signal(dict) def __init__(self,parent=None): # pylint: disable=unused-argument,super-on-old-class @@ -96,44 +97,44 @@ class DimensionSelectorWidget(QtGui.QWidget): self.positiveDoubleValidator.setBottom(1e-10) self.V3DValidator=V3DValidator(self) #labels - self._labelBasis=QtGui.QLabel(' Projection Basis') - self._labelBasis1=QtGui.QLabel(' Projection u') - self._labelBasis2=QtGui.QLabel(' Projection v') - self._labelBasis3=QtGui.QLabel(' Projection w') - self._labelMin=QtGui.QLabel('Min') - self._labelMax=QtGui.QLabel('Max') - self._labelStep=QtGui.QLabel('Step') + self._labelBasis=QtWidgets.QLabel(' Projection Basis') + self._labelBasis1=QtWidgets.QLabel(' Projection u') + self._labelBasis2=QtWidgets.QLabel(' Projection v') + self._labelBasis3=QtWidgets.QLabel(' Projection w') + self._labelMin=QtWidgets.QLabel('Min') + self._labelMax=QtWidgets.QLabel('Max') + self._labelStep=QtWidgets.QLabel('Step') #lineedits - self._editBasis1=QtGui.QLineEdit() + self._editBasis1=QtWidgets.QLineEdit() self._editBasis1.setValidator(self.V3DValidator) - self._editBasis2=QtGui.QLineEdit() + self._editBasis2=QtWidgets.QLineEdit() self._editBasis2.setValidator(self.V3DValidator) - self._editBasis3=QtGui.QLineEdit() + self._editBasis3=QtWidgets.QLineEdit() self._editBasis3.setValidator(self.V3DValidator) - self._editMin1=QtGui.QLineEdit() + self._editMin1=QtWidgets.QLineEdit() self._editMin1.setValidator(self.doubleValidator) - self._editMin2=QtGui.QLineEdit() + self._editMin2=QtWidgets.QLineEdit() self._editMin2.setValidator(self.doubleValidator) - self._editMin3=QtGui.QLineEdit() + self._editMin3=QtWidgets.QLineEdit() self._editMin3.setValidator(self.doubleValidator) - self._editMin4=QtGui.QLineEdit() + self._editMin4=QtWidgets.QLineEdit() self._editMin4.setValidator(self.doubleValidator) - self._editMax1=QtGui.QLineEdit() + self._editMax1=QtWidgets.QLineEdit() self._editMax1.setValidator(self.doubleValidator) - self._editMax2=QtGui.QLineEdit() + self._editMax2=QtWidgets.QLineEdit() self._editMax2.setValidator(self.doubleValidator) - self._editMax3=QtGui.QLineEdit() + self._editMax3=QtWidgets.QLineEdit() self._editMax3.setValidator(self.doubleValidator) - self._editMax4=QtGui.QLineEdit() + self._editMax4=QtWidgets.QLineEdit() self._editMax4.setValidator(self.doubleValidator) - self._editStep1=QtGui.QLineEdit() + self._editStep1=QtWidgets.QLineEdit() self._editStep1.setValidator(self.positiveDoubleValidator) - self._editStep2=QtGui.QLineEdit() + self._editStep2=QtWidgets.QLineEdit() self._editStep2.setValidator(self.positiveDoubleValidator) - self._comboDim1=QtGui.QComboBox(self) - self._comboDim2=QtGui.QComboBox(self) - self._comboDim3=QtGui.QComboBox(self) - self._comboDim4=QtGui.QComboBox(self) + self._comboDim1=QtWidgets.QComboBox(self) + self._comboDim2=QtWidgets.QComboBox(self) + self._comboDim3=QtWidgets.QComboBox(self) + self._comboDim4=QtWidgets.QComboBox(self) self._comboDim4.setMinimumContentsLength(12) #basis self.basis=['1,0,0','0,1,0','0,0,1'] @@ -144,7 +145,7 @@ class DimensionSelectorWidget(QtGui.QWidget): self.dimStep=[0.05,0.05,0.05,1] self.dimIndex=[0,1,2,3] #layout - grid = QtGui.QGridLayout() + grid = QtWidgets.QGridLayout() self.setLayout(grid) grid.addWidget(self._labelMin,0,1) grid.addWidget(self._labelMax,0,2) @@ -351,7 +352,7 @@ class DimensionSelectorWidget(QtGui.QWidget): if __name__=='__main__': - app=QtGui.QApplication(sys.argv) + app=QtWidgets.QApplication(sys.argv) mainForm=DimensionSelectorWidget() mainForm.show() sys.exit(app.exec_()) diff --git a/scripts/DGSPlanner/InstrumentSetupWidget.py b/scripts/DGSPlanner/InstrumentSetupWidget.py index db76761c76fc2c785f54da70725d8766ab8b7591..d7261a9a3f7d60d866b9060f35ee884c3e90f68c 100644 --- a/scripts/DGSPlanner/InstrumentSetupWidget.py +++ b/scripts/DGSPlanner/InstrumentSetupWidget.py @@ -6,20 +6,20 @@ # SPDX - License - Identifier: GPL - 3.0 + #pylint: disable=invalid-name,no-name-in-module,too-many-instance-attributes,too-many-public-methods from __future__ import (absolute_import, division, print_function) -from PyQt4 import QtGui, QtCore +from qtpy import QtGui, QtCore, QtWidgets import sys import mantid import numpy -import matplotlib -matplotlib.use('Qt4Agg') -matplotlib.rcParams['backend.qt4']='PyQt4' -#the following matplotlib imports cannot be placed before the use command, so we ignore flake8 warnings -from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas # noqa + +sys.path.append('..') +#the following matplotlib imports cannot be placed before the setting of the backend, so we ignore flake8 warnings +from MPLwidgets import * # noqa from matplotlib.figure import Figure # noqa from mpl_toolkits.mplot3d import Axes3D # noqa import matplotlib.pyplot # noqa + try: - from PyQt4.QtCore import QString + from qtpy.QtCore import QString except ImportError: QString = type("") @@ -28,7 +28,7 @@ class GonioTableModel(QtCore.QAbstractTableModel): """ Dealing with the goniometer input """ - changed=QtCore.pyqtSignal(dict) #each value is a list + changed=QtCore.Signal(dict) #each value is a list def __init__(self, axes, parent = None): QtCore.QAbstractTableModel.__init__(self, parent) @@ -146,7 +146,7 @@ class GonioTableModel(QtCore.QAbstractTableModel): return True -class InstrumentSetupWidget(QtGui.QWidget): +class InstrumentSetupWidget(QtWidgets.QWidget): #signal when things change and valid changed=QtCore.pyqtSignal(dict) @@ -157,7 +157,7 @@ class InstrumentSetupWidget(QtGui.QWidget): self.signaldict=dict() #instrument selector self.instrumentList=['ARCS','CNCS','DNS','EXED','FOCUS','HET','HYSPEC','LET','MAPS','MARI','MERLIN','SEQUOIA'] - self.combo = QtGui.QComboBox(self) + self.combo = QtWidgets.QComboBox(self) for inst in self.instrumentList: self.combo.addItem(inst) defaultInstrument=mantid.config.getInstrument().name() @@ -168,7 +168,7 @@ class InstrumentSetupWidget(QtGui.QWidget): self.instrument=self.instrumentList[0] self.combo.setCurrentIndex(0) self.signaldict['instrument']=self.instrument - self.labelInst=QtGui.QLabel('Instrument') + self.labelInst=QtWidgets.QLabel('Instrument') #S2 and Ei edits self.S2=0.0 self.Ei=10.0 @@ -176,30 +176,30 @@ class InstrumentSetupWidget(QtGui.QWidget): self.signaldict['Ei']=self.Ei self.validatorS2=QtGui.QDoubleValidator(-90.,90.,5,self) self.validatorEi=QtGui.QDoubleValidator(1.,10000.,5,self) - self.labelS2=QtGui.QLabel('S2') - self.labelEi=QtGui.QLabel('Incident Energy') - self.editS2=QtGui.QLineEdit() + self.labelS2=QtWidgets.QLabel('S2') + self.labelEi=QtWidgets.QLabel('Incident Energy') + self.editS2=QtWidgets.QLineEdit() self.editS2.setValidator(self.validatorS2) - self.editEi=QtGui.QLineEdit() + self.editEi=QtWidgets.QLineEdit() self.editEi.setValidator(self.validatorEi) self.editS2.setText(QString(format(self.S2,'.2f'))) self.editEi.setText(QString(format(self.Ei,'.1f'))) self.editEi.setFixedWidth(metrics.width("8888.88")) self.editS2.setFixedWidth(metrics.width("888.88")) #fast checkbox - self.fast=QtGui.QCheckBox("Fast",self) + self.fast=QtWidgets.QCheckBox("Fast",self) self.fast.toggle() self.updateFast() #masking - self.labelMask=QtGui.QLabel('Mask file') - self.editMask=QtGui.QLineEdit() - self.buttonMask=QtGui.QPushButton("LoadMask") + self.labelMask=QtWidgets.QLabel('Mask file') + self.editMask=QtWidgets.QLineEdit() + self.buttonMask=QtWidgets.QPushButton("LoadMask") #goniometer settings - self.labelGon=QtGui.QLabel('Goniometer') - self.tableViewGon = QtGui.QTableView(self) + self.labelGon=QtWidgets.QLabel('Goniometer') + self.tableViewGon = QtWidgets.QTableView(self) self.tableViewGon.setMinimumWidth(metrics.width("Minimum ")*8) - self.tableViewGon.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch) - self.tableViewGon.verticalHeader().setResizeMode(QtGui.QHeaderView.Stretch) + self.tableViewGon.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) + self.tableViewGon.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) self.goniometerNames=['psi','gl','gs'] self.goniometerDirections=['0,1,0','0,0,1','1,0,0'] self.goniometerRotationSense=[1,1,1] @@ -215,11 +215,11 @@ class InstrumentSetupWidget(QtGui.QWidget): #goniometer figure self.figure=Figure(figsize=(2,3)) self.figure.patch.set_facecolor('white') - self.canvas=FigureCanvas(self.figure) + self.canvas=FigureCanvasQTAgg(self.figure) self.gonfig=None self.updateFigure() #layout - self.gridI = QtGui.QGridLayout() + self.gridI = QtWidgets.QGridLayout() self.gridI.addWidget(self.labelInst,0,0) self.gridI.addWidget(self.combo,0,1) self.gridI.addWidget(self.labelEi,0,2) @@ -227,9 +227,9 @@ class InstrumentSetupWidget(QtGui.QWidget): self.gridI.addWidget(self.labelS2,0,4) self.gridI.addWidget(self.editS2,0,5) self.gridI.addWidget(self.fast,0,6) - self.setLayout(QtGui.QHBoxLayout()) - self.rightside=QtGui.QVBoxLayout() - self.maskLayout=QtGui.QHBoxLayout() + self.setLayout(QtWidgets.QHBoxLayout()) + self.rightside=QtWidgets.QVBoxLayout() + self.maskLayout=QtWidgets.QHBoxLayout() self.maskLayout.addWidget(self.labelMask) self.maskLayout.addWidget(self.editMask) self.maskLayout.addWidget(self.buttonMask) @@ -257,7 +257,8 @@ class InstrumentSetupWidget(QtGui.QWidget): if self.gonfig is not None: self.gonfig.clear() self.gonfig = Axes3D(self.figure) - self.gonfig.hold(True) + if matplotlib.compare_versions('2.1.0',matplotlib.__version__): + self.gonfig.hold(True) # hold is deprecated since 2.1.0, true by default self.gonfig.set_frame_on(False) self.gonfig.set_xlim3d(-0.6,0.6) self.gonfig.set_ylim3d(-0.6,0.6) @@ -270,8 +271,7 @@ class InstrumentSetupWidget(QtGui.QWidget): self.gonfig.text(1,0,-2.5,'X',zdir=None,color='black') self.gonfig.plot([0,0],[-3,-3],[-2,-0.5],zdir='y',color='black',linewidth=3) self.gonfig.text(0,-1,-2.5,'Beam',zdir=None,color='black') - - matplotlib.pyplot.gca().set_aspect('equal', adjustable='datalim') + self.gonfig.set_aspect('equal', adjustable='datalim') self.gonfig.view_init(10,45) colors=['b','g','r'] @@ -332,11 +332,13 @@ class InstrumentSetupWidget(QtGui.QWidget): self.updateAll(**d) def loadMaskFromFile(self): - fileName = QtGui.QFileDialog.getOpenFileName(self, - "Open Mask File", '', - "Processed Nexus (*.nxs);;All Files (*)") + fileName = QtWidgets.QFileDialog.getOpenFileName(self, + "Open Mask File", '', + "Processed Nexus (*.nxs);;All Files (*)") if not fileName: return + if isinstance(fileName,tuple): + fileName=fileName[0] self.editMask.setText(QString(fileName)) self.setMaskFile() @@ -372,7 +374,7 @@ class InstrumentSetupWidget(QtGui.QWidget): if __name__=='__main__': - app=QtGui.QApplication(sys.argv) + app=QtWidgets.QApplication(sys.argv) mainForm=InstrumentSetupWidget() mainForm.show() sys.exit(app.exec_()) diff --git a/scripts/DGSPlanner/MatrixUBInputWidget.py b/scripts/DGSPlanner/MatrixUBInputWidget.py index 87f2035bf5ed04a4a244623a0dce5b913eea9bb6..dbccd8e20603d269a9500b810274ec7c3c574ebf 100644 --- a/scripts/DGSPlanner/MatrixUBInputWidget.py +++ b/scripts/DGSPlanner/MatrixUBInputWidget.py @@ -6,19 +6,20 @@ # SPDX - License - Identifier: GPL - 3.0 + #pylint: disable=invalid-name,no-name-in-module,too-many-public-methods from __future__ import (absolute_import, division, print_function) -from PyQt4 import QtCore, QtGui +from qtpy import QtCore, QtGui, QtWidgets import sys import mantid from DGSPlanner.ValidateOL import ValidateUB from DGSPlanner.LoadNexusUB import LoadNexusUB + try: - from PyQt4.QtCore import QString + from qtpy.QtCore import QString except ImportError: QString = type("") class UBTableModel(QtCore.QAbstractTableModel): - changed=QtCore.pyqtSignal(mantid.geometry.OrientedLattice) + changed=QtCore.Signal(mantid.geometry.OrientedLattice) def __init__(self, lattice, parent = None): QtCore.QAbstractTableModel.__init__(self, parent) @@ -77,22 +78,22 @@ class UBTableModel(QtCore.QAbstractTableModel): self.endResetModel() -class MatrixUBInputWidget(QtGui.QWidget): +class MatrixUBInputWidget(QtWidgets.QWidget): # pylint: disable=too-few-public-methods def __init__(self,ol,parent=None): # pylint: disable=unused-argument,super-on-old-class super(MatrixUBInputWidget,self).__init__(parent) - self.setLayout(QtGui.QVBoxLayout()) - self._tableView = QtGui.QTableView(self) + self.setLayout(QtWidgets.QVBoxLayout()) + self._tableView = QtWidgets.QTableView(self) self._tableView.horizontalHeader().hide() self._tableView.verticalHeader().hide() - self._tableView.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch) - self._tableView.verticalHeader().setResizeMode(QtGui.QHeaderView.Stretch) - self.LoadIsawUBButton=QtGui.QPushButton("LoadIsawUB") - self.LoadNexusUBButton=QtGui.QPushButton("LoadNexusUB") - self.layout().addWidget(QtGui.QLabel('UB matrix')) + self._tableView.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) + self._tableView.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) + self.LoadIsawUBButton=QtWidgets.QPushButton("LoadIsawUB") + self.LoadNexusUBButton=QtWidgets.QPushButton("LoadNexusUB") + self.layout().addWidget(QtWidgets.QLabel('UB matrix')) self.layout().addWidget(self._tableView) - self.hbox = QtGui.QHBoxLayout() + self.hbox = QtWidgets.QHBoxLayout() self.hbox.addStretch(1) self.hbox.addWidget(self.LoadIsawUBButton) self.hbox.addWidget(self.LoadNexusUBButton) @@ -110,7 +111,11 @@ class MatrixUBInputWidget(QtGui.QWidget): def loadIsawUBDialog(self): # pylint: disable=bare-except try: - fname = QtGui.QFileDialog.getOpenFileName(self, 'Open ISAW UB file',filter=QString('Mat file (*.mat);;All Files (*)')) + fname = QtWidgets.QFileDialog.getOpenFileName(self, 'Open ISAW UB file',filter=QString('Mat file (*.mat);;All Files (*)')) + if isinstance(fname,tuple): + fname=fname[0] + if not fname: + return __tempws=mantid.simpleapi.CreateSingleValuedWorkspace(0.) mantid.simpleapi.LoadIsawUB(__tempws,str(fname)) ol=mantid.geometry.OrientedLattice(__tempws.sample().getOrientedLattice()) @@ -118,25 +123,29 @@ class MatrixUBInputWidget(QtGui.QWidget): self.UBmodel.updateOL(ol) self.UBmodel.sendSignal() mantid.simpleapi.DeleteWorkspace(__tempws) - except: - mantid.logger.error("Could not open the file, or not a valid UB matrix") + except Exception as e: + mantid.logger.error("Could not open the file, or not a valid UB matrix: {}".format(e)) def loadNexusUBDialog(self): # pylint: disable=bare-except try: - fname = QtGui.QFileDialog.getOpenFileName( + fname = QtWidgets.QFileDialog.getOpenFileName( self, 'Open Nexus file to extract UB matrix', filter=QString('Nexus file (*.nxs.h5);;All Files (*)')) + if isinstance(fname,tuple): + fname=fname[0] + if not fname: + return __tempUB = LoadNexusUB(str(fname)) ol=mantid.geometry.OrientedLattice() ol.setUB(__tempUB) self.UBmodel.updateOL(ol) self.UBmodel.sendSignal() - except: - mantid.logger.error("Could not open the Nexus file, or could not find UB matrix") + except Exception as e: + mantid.logger.error("Could not open the Nexus file, or could not find UB matrix: {}".format(e)) if __name__ == '__main__': - app = QtGui.QApplication(sys.argv) + app = QtWidgets.QApplication(sys.argv) inputol=mantid.geometry.OrientedLattice(2,3,4,90,90,90) mainForm=MatrixUBInputWidget(inputol) diff --git a/scripts/Diffraction/isis_powder/gem.py b/scripts/Diffraction/isis_powder/gem.py index 94934c9b3c8a43fa06db590b0a8fa20e32ad5f1e..2773d6a431ddec8017c9c8ddc2fea86d5ffceeb6 100644 --- a/scripts/Diffraction/isis_powder/gem.py +++ b/scripts/Diffraction/isis_powder/gem.py @@ -29,15 +29,16 @@ class Gem(AbstractInst): # Public API def focus(self, **kwargs): - self._switch_texture_mode_specific_inst_settings(kwargs.get("texture_mode")) self._inst_settings.update_attributes(kwargs=kwargs) + self._switch_texture_mode_specific_inst_settings(kwargs.get("texture_mode")) + return self._focus( run_number_string=self._inst_settings.run_number, do_van_normalisation=self._inst_settings.do_van_norm, do_absorb_corrections=self._inst_settings.do_absorb_corrections) def create_vanadium(self, **kwargs): - self._switch_texture_mode_specific_inst_settings(kwargs.get("texture_mode")) self._inst_settings.update_attributes(kwargs=kwargs) + self._switch_texture_mode_specific_inst_settings(kwargs.get("texture_mode")) return self._create_vanadium(run_number_string=self._inst_settings.run_in_range, do_absorb_corrections=self._inst_settings.do_absorb_corrections) @@ -50,6 +51,9 @@ class Gem(AbstractInst): " set the following argument: " + kwarg_name) self._sample_details = sample_details_obj + def should_subtract_empty_inst(self): + return self._inst_settings.subtract_empty_inst + # Private methods def _get_run_details(self, run_number_string): @@ -176,8 +180,10 @@ class Gem(AbstractInst): if mode is None and hasattr(self._inst_settings, "texture_mode"): mode = self._inst_settings.texture_mode save_all = not hasattr(self._inst_settings, "save_all") - self._inst_settings.update_attributes(advanced_config=gem_advanced_config.get_mode_specific_variables(mode, - save_all), + adv_config_variables = gem_advanced_config.get_mode_specific_variables(mode, save_all) + if self.should_subtract_empty_inst() is None: + adv_config_variables.update({"subtract_empty_instrument": True}) + self._inst_settings.update_attributes(advanced_config=adv_config_variables, suppress_warnings=True) diff --git a/scripts/Diffraction/isis_powder/gem_routines/gem_param_mapping.py b/scripts/Diffraction/isis_powder/gem_routines/gem_param_mapping.py index 79323970d80aa090720660273f4d8dbb5c8a6393..188b23b556ab7c2e8a7b1c3599ebd3b429c19904 100644 --- a/scripts/Diffraction/isis_powder/gem_routines/gem_param_mapping.py +++ b/scripts/Diffraction/isis_powder/gem_routines/gem_param_mapping.py @@ -35,6 +35,7 @@ attr_mapping = \ ParamMapEntry(ext_name="save_maud_calib", int_name="save_maud_calib"), ParamMapEntry(ext_name="save_maud", int_name="save_maud"), ParamMapEntry(ext_name="spline_coefficient", int_name="spline_coeff"), + ParamMapEntry(ext_name="subtract_empty_instrument", int_name="subtract_empty_inst", optional =True), ParamMapEntry(ext_name="suffix", int_name="suffix", optional=True), ParamMapEntry(ext_name="texture_mode", int_name="texture_mode", optional=True), ParamMapEntry(ext_name="output_directory", int_name="output_dir"), diff --git a/scripts/Diffraction/isis_powder/pearl_routines/pearl_output.py b/scripts/Diffraction/isis_powder/pearl_routines/pearl_output.py index f9a24606a60a4a005daff48290d9cf0a0361fb85..2814850c728e20f35a274a6e878a548b74cabe6c 100644 --- a/scripts/Diffraction/isis_powder/pearl_routines/pearl_output.py +++ b/scripts/Diffraction/isis_powder/pearl_routines/pearl_output.py @@ -47,7 +47,9 @@ def _attenuate_workspace(output_file_paths, attenuated_ws, attenuation_filepath) def _focus_mode_all(output_file_paths, processed_spectra, attenuation_filepath): summed_spectra_name = output_file_paths["output_name"] + "_mods1-9" summed_spectra = mantid.MergeRuns(InputWorkspaces=processed_spectra[:9], OutputWorkspace=summed_spectra_name) + xList = summed_spectra.readX(0) + summed_spectra = mantid.CropWorkspace(InputWorkspace=summed_spectra, XMin=xList[1], Xmax=xList[-2]) summed_spectra = mantid.Scale(InputWorkspace=summed_spectra, Factor=0.111111111111111, OutputWorkspace=summed_spectra_name) if attenuation_filepath: @@ -62,7 +64,8 @@ def _focus_mode_all(output_file_paths, processed_spectra, attenuation_filepath): summed_spectra = mantid.ConvertUnits(InputWorkspace=summed_spectra, Target="dSpacing", OutputWorkspace=summed_spectra_name) mantid.SaveNexus(Filename=output_file_paths["nxs_filename"], InputWorkspace=summed_spectra, Append=False) - + mantid.SaveFocusedXYE(InputWorkspace=summed_spectra_name, Filename=output_file_paths["tof_xye_filename"], + Append=False, IncludeHeader=False) output_list = [summed_spectra] for i in range(0, 5): spectra_index = (i + 9) # Compensate for 0 based index @@ -71,6 +74,9 @@ def _focus_mode_all(output_file_paths, processed_spectra, attenuation_filepath): ws_to_save = mantid.ConvertUnits(InputWorkspace=ws_to_save, OutputWorkspace=ws_to_save, Target="TOF") mantid.SaveGSS(InputWorkspace=ws_to_save, Filename=output_file_paths["gss_filename"], Append=True, Bank=i + 2) + splits = output_file_paths["tof_xye_filename"].split(".") + tof_xye_name = splits[0] + "-" + str(i + 10) + "." + splits[1] + mantid.SaveFocusedXYE(InputWorkspace=ws_to_save, Filename=tof_xye_name, Append=False, IncludeHeader=False) ws_to_save = mantid.ConvertUnits(InputWorkspace=ws_to_save, OutputWorkspace=output_name, Target="dSpacing") mantid.SaveNexus(Filename=output_file_paths["nxs_filename"], InputWorkspace=ws_to_save, Append=True) @@ -86,6 +92,9 @@ def _focus_mode_groups(output_file_paths, calibrated_spectra): workspaces_4_to_9_name = output_file_paths["output_name"] + "_mods4-9" workspaces_4_to_9 = mantid.MergeRuns(InputWorkspaces=calibrated_spectra[3:9], OutputWorkspace=workspaces_4_to_9_name) + xList = workspaces_4_to_9.readX(0) + + workspaces_4_to_9 = mantid.CropWorkspace(InputWorkspace=workspaces_4_to_9 , XMin=xList[1], Xmax=xList[-2]) workspaces_4_to_9 = mantid.Scale(InputWorkspace=workspaces_4_to_9, Factor=0.5, OutputWorkspace=workspaces_4_to_9_name) to_save.append(workspaces_4_to_9) @@ -95,7 +104,8 @@ def _focus_mode_groups(output_file_paths, calibrated_spectra): ws = mantid.ConvertUnits(InputWorkspace=ws, OutputWorkspace=ws, Target="TOF") mantid.SaveGSS(InputWorkspace=ws, Filename=output_file_paths["gss_filename"], Append=False, Bank=index) - + mantid.SaveFocusedXYE(InputWorkspace=ws, Filename=output_file_paths["tof_xye_filename"], + Append=False, IncludeHeader=False) workspace_names = ws.name() ws = mantid.ConvertUnits(InputWorkspace=ws, OutputWorkspace=workspace_names, Target="dSpacing") output_list.append(ws) @@ -111,7 +121,10 @@ def _focus_mode_groups(output_file_paths, calibrated_spectra): to_save = mantid.CloneWorkspace(InputWorkspace=monitor_ws, OutputWorkspace=monitor_ws_name) to_save = mantid.ConvertUnits(InputWorkspace=to_save, OutputWorkspace=to_save, Target="TOF") + splits = output_file_paths["tof_xye_filename"].split(".") + tof_xye_name = splits[0] + "-" + str(i+10) + "." + splits[1] mantid.SaveGSS(InputWorkspace=to_save, Filename=output_file_paths["gss_filename"], Append=True, Bank=i + 5) + mantid.SaveFocusedXYE(InputWorkspace=to_save, Filename=tof_xye_name, Append=False, IncludeHeader=False) to_save = mantid.ConvertUnits(InputWorkspace=to_save, OutputWorkspace=monitor_ws_name, Target="dSpacing") mantid.SaveNexus(Filename=output_file_paths["nxs_filename"], InputWorkspace=to_save, Append=True) @@ -127,6 +140,8 @@ def _focus_mode_mods(output_file_paths, calibrated_spectra): output_name = output_file_paths["output_name"] + "_mod" + str(index + 1) tof_ws = mantid.ConvertUnits(InputWorkspace=ws, OutputWorkspace=output_name, Target=WORKSPACE_UNITS.tof) + mantid.SaveFocusedXYE(InputWorkspace=tof_ws, Filename=output_file_paths["tof_xye_filename"], + Append=False, IncludeHeader=False) mantid.SaveGSS(InputWorkspace=tof_ws, Filename=output_file_paths["gss_filename"], Append=append, Bank=index + 1) dspacing_ws = mantid.ConvertUnits(InputWorkspace=ws, OutputWorkspace=output_name, Target=WORKSPACE_UNITS.d_spacing) @@ -139,6 +154,9 @@ def _focus_mode_mods(output_file_paths, calibrated_spectra): def _focus_mode_trans(output_file_paths, attenuation_filepath, calibrated_spectra): summed_ws = mantid.MergeRuns(InputWorkspaces=calibrated_spectra[:9]) + xList = summed_ws.readX(0) + + summed_ws = mantid.CropWorkspace(InputWorkspace=summed_ws, XMin=xList[1], Xmax=xList[-2]) summed_ws = mantid.Scale(InputWorkspace=summed_ws, Factor=0.111111111111111) if attenuation_filepath: @@ -147,6 +165,7 @@ def _focus_mode_trans(output_file_paths, attenuation_filepath, calibrated_spectr summed_ws = mantid.ConvertUnits(InputWorkspace=summed_ws, Target="TOF") mantid.SaveGSS(InputWorkspace=summed_ws, Filename=output_file_paths["gss_filename"], Append=False, Bank=1) + mantid.SaveFocusedXYE(InputWorkspace=summed_ws, Filename=output_file_paths["tof_xye_filename"], Append=False, IncludeHeader=False) @@ -179,6 +198,9 @@ def _sum_groups_of_three_ws(calibrated_spectra, output_file_names): ws_name = output_file_names["output_name"] + "_mods{}-{}".format(i * 3 + 1, (i + 1) * 3) summed_spectra = mantid.MergeRuns(InputWorkspaces=calibrated_spectra[i * 3: (i + 1) * 3], OutputWorkspace=ws_name) + xList = summed_spectra.readX(0) + + summed_spectra = mantid.CropWorkspace(InputWorkspace=summed_spectra, XMin=xList[1], Xmax=xList[-2]) scaled = mantid.Scale(InputWorkspace=summed_spectra, Factor=1./3, OutputWorkspace=ws_name) output_list.append(scaled) diff --git a/scripts/Diffraction/isis_powder/routines/focus.py b/scripts/Diffraction/isis_powder/routines/focus.py index d683467fb44605ff5f691add09ef3b3b06ac63f1..92014e2b090233e8c518675d0cb39428e353e8ae 100644 --- a/scripts/Diffraction/isis_powder/routines/focus.py +++ b/scripts/Diffraction/isis_powder/routines/focus.py @@ -126,7 +126,8 @@ def _divide_one_spectrum_by_spline(spectrum, spline, instrument): if instrument.get_instrument_prefix() == "GEM": divided = mantid.Divide(LHSWorkspace=spectrum, RHSWorkspace=rebinned_spline, OutputWorkspace=spectrum, StoreInADS=False) - return _crop_spline_to_percent_of_max(rebinned_spline, divided, spectrum) + # crop based off max between 1000 and 2000 tof as the vanadium peak on Gem will always occur here + return _crop_spline_to_percent_of_max(rebinned_spline, divided, spectrum, 1000, 2000) divided = mantid.Divide(LHSWorkspace=spectrum, RHSWorkspace=rebinned_spline, OutputWorkspace=spectrum) return divided @@ -172,11 +173,17 @@ def _test_splined_vanadium_exists(instrument, run_details): " \nHave you run the method to create a Vanadium spline with these settings yet?\n") -def _crop_spline_to_percent_of_max(spline, input_ws, output_workspace): +def _crop_spline_to_percent_of_max(spline, input_ws, output_workspace, min_value, max_value): spline_spectrum = spline.readY(0) - y_val = numpy.amax(spline_spectrum) - y_val = y_val / 100 + if not spline_spectrum.any(): + return mantid.CloneWorkspace(inputWorkspace=input_ws, OutputWorkspace=output_workspace) + x_list = input_ws.readX(0) + min_index = x_list.searchsorted(min_value) + max_index = x_list.searchsorted(max_value) + sliced_spline_spectrum = spline_spectrum[min_index:max_index:1] + y_val = numpy.amax(sliced_spline_spectrum) + y_val = y_val / 100 small_spline_indecies = numpy.nonzero(spline_spectrum > y_val)[0] x_max = x_list[small_spline_indecies[-1]] x_min = x_list[small_spline_indecies[0]] diff --git a/scripts/Elemental_Analysis.py b/scripts/Elemental_Analysis.py index 26c0c1392904e1c53bb0f92a43d6ae4246b677f1..c021728c00cb5c47b9ee429d7fa863a0a3af8dba 100644 --- a/scripts/Elemental_Analysis.py +++ b/scripts/Elemental_Analysis.py @@ -36,6 +36,7 @@ import mantid.simpleapi as mantid class ElementalAnalysisGui(QtGui.QMainWindow): + def __init__(self, parent=None): super(ElementalAnalysisGui, self).__init__(parent) self.menu = self.menuBar() @@ -77,6 +78,8 @@ class ElementalAnalysisGui(QtGui.QMainWindow): self.plotting = PlotPresenter(PlotView()) self.plotting.view.setMinimumSize(self.plotting.view.sizeHint()) + self.plotting.removeSubplotConnection(self.subplotRemoved) + self.box = QtGui.QHBoxLayout() self.box.addWidget(self.ptable.view) self.box.addLayout(self.widget_list) @@ -345,6 +348,11 @@ class ElementalAnalysisGui(QtGui.QMainWindow): if not self.plotting.get_subplots(): self.plotting.view.close() + def subplotRemoved(self, name): + # need to change the state without sending signal + # as the plot has already been removed + self.detectors.setStateQuietly(name, False) + def qapp(): if QtGui.QApplication.instance(): diff --git a/scripts/FilterEvents.py b/scripts/FilterEvents.py index 09f5733e3e1026f28bea0e59470348eeeb899e1b..a695b6621447f8a43d4e019ec932263a936c8f72 100644 --- a/scripts/FilterEvents.py +++ b/scripts/FilterEvents.py @@ -4,23 +4,16 @@ # NScD Oak Ridge National Laboratory, European Spallation Source # & Institut Laue - Langevin # SPDX - License - Identifier: GPL - 3.0 + -#pylint: disable=invalid-name +# pylint: disable=invalid-name from __future__ import (absolute_import, division, print_function) -from FilterEvents import eventFilterGUI -from PyQt4 import QtGui import sys +from gui_helper import set_matplotlib_backend, get_qapplication +set_matplotlib_backend() # must be called before anything tries to use matplotlib +from FilterEvents import eventFilterGUI # noqa - -def qapp(): - if QtGui.QApplication.instance(): - _app = QtGui.QApplication.instance() - else: - _app = QtGui.QApplication(sys.argv) - return _app - - -app = qapp() +app, within_mantid = get_qapplication() reducer = eventFilterGUI.MainWindow() #the main ui class in this file is called MainWindow reducer.show() -app.exec_() +if not within_mantid: + sys.exit(app.exec_()) diff --git a/scripts/FilterEvents/CMakeLists.txt b/scripts/FilterEvents/CMakeLists.txt deleted file mode 100644 index 81b5a396dde8a6589ac81911b7ce246b5abd70fb..0000000000000000000000000000000000000000 --- a/scripts/FilterEvents/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -include(UiToPy) - -# List of UIs to Auto convert -set( UI_FILES - ErrorMessage.ui - MainWindow.ui -) - -UiToPy( UI_FILES CompileUIFilterEvents) - - diff --git a/scripts/FilterEvents/ErrorMessage.ui b/scripts/FilterEvents/ErrorMessage.ui deleted file mode 100644 index 9c54a0c41fcf40bad9046df36dad1d195f1a8ffb..0000000000000000000000000000000000000000 --- a/scripts/FilterEvents/ErrorMessage.ui +++ /dev/null @@ -1,230 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>Dialog</class> - <widget class="QDialog" name="Dialog"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>400</width> - <height>225</height> - </rect> - </property> - <property name="minimumSize"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - <property name="windowTitle"> - <string>Error</string> - </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="0"> - <spacer name="verticalSpacer_3"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>17</width> - <height>37</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_5"> - <property name="sizeConstraint"> - <enum>QLayout::SetMinimumSize</enum> - </property> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QLabel" name="label_title"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>80</width> - <height>40</height> - </size> - </property> - <property name="font"> - <font> - <pointsize>16</pointsize> - <weight>75</weight> - <bold>true</bold> - </font> - </property> - <property name="text"> - <string><html><head/><body><p><span style=" color:#ff0000;">Error</span></p></body></html></string> - </property> - <property name="textFormat"> - <enum>Qt::RichText</enum> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item> - <spacer name="verticalSpacer_2"> - <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> - <layout class="QHBoxLayout" name="horizontalLayout_6"> - <property name="sizeConstraint"> - <enum>QLayout::SetMinimumSize</enum> - </property> - <item> - <spacer name="horizontalSpacer_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>41</width> - <height>146</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QLabel" name="label_errmsg"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>300</width> - <height>80</height> - </size> - </property> - <property name="text"> - <string>TextLabel</string> - </property> - <property name="textFormat"> - <enum>Qt::RichText</enum> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_4"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item> - <spacer name="verticalSpacer"> - <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> - <layout class="QHBoxLayout" name="horizontalLayout_7"> - <property name="sizeConstraint"> - <enum>QLayout::SetMinimumSize</enum> - </property> - <item> - <spacer name="horizontalSpacer_5"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="pushButton_quit"> - <property name="text"> - <string>Close</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_6"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - </layout> - </item> - </layout> - </widget> - <resources/> - <connections/> -</ui> diff --git a/scripts/FilterEvents/MainWindow.ui b/scripts/FilterEvents/MainWindow.ui index 8689e5a399e64313b5b616a6633edff7afbe038b..ca06ab574e570d1161802357710bb0788addabfd 100644 --- a/scripts/FilterEvents/MainWindow.ui +++ b/scripts/FilterEvents/MainWindow.ui @@ -208,7 +208,7 @@ <item> <layout class="QHBoxLayout" name="horizontalLayout_4"> <item> - <widget class="MplFigureCanvas" name="graphicsView"/> + <widget class="QFrame" name="graphicsView"/> </item> <item> <layout class="QVBoxLayout" name="verticalLayout"> @@ -1326,13 +1326,6 @@ </property> </widget> </widget> - <customwidgets> - <customwidget> - <class>MplFigureCanvas</class> - <extends>QGraphicsView</extends> - <header>MplFigureCanvas.h</header> - </customwidget> - </customwidgets> <tabstops> <tabstop>pushButton_browse</tabstop> <tabstop>pushButton_load</tabstop> diff --git a/scripts/FilterEvents/MplFigureCanvas.py b/scripts/FilterEvents/MplFigureCanvas.py deleted file mode 100644 index 5b515ba21ae849283bcd55752b88db8c2d1af064..0000000000000000000000000000000000000000 --- a/scripts/FilterEvents/MplFigureCanvas.py +++ /dev/null @@ -1,54 +0,0 @@ -# Mantid Repository : https://github.com/mantidproject/mantid -# -# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, -# NScD Oak Ridge National Laboratory, European Spallation Source -# & Institut Laue - Langevin -# SPDX - License - Identifier: GPL - 3.0 + -#pylint: disable=invalid-name -from __future__ import (absolute_import, division, print_function) -from PyQt4 import QtGui - -from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.figure import Figure - - -class MplFigureCanvas(FigureCanvas): - """ A customized Qt widget for matplotlib figure. - It can be used to replace GraphicsView of QtGui - """ - - def __init__(self, parent): - """ Initialization - """ - # Instantialize matplotlib Figure - self.fig = Figure() - self.axes = self.fig.add_subplot(111) - - # Initialize parent class and set parent - FigureCanvas.__init__(self, self.fig) - self.setParent(parent) - - # Set size policy to be able to expanding and resizable with frame - FigureCanvas.setSizePolicy(self, QtGui.QSizePolicy.Expanding, - QtGui.QSizePolicy.Expanding) - - FigureCanvas.updateGeometry(self) - - return - - def plot(self, x, y): - """ Plot a set of data - Argument: - - x: numpy array X - - y: numpy array Y - """ - self.x = x - self.y = y - self.axes.plot(self.x, self.y) - - return - - def getPlot(self): - """ return figure's axes to expose the matplotlib figure to PyQt client - """ - return self.axes diff --git a/scripts/FilterEvents/eventFilterGUI.py b/scripts/FilterEvents/eventFilterGUI.py index 6677164f18cdb5b088c592c3e9d014c5b586df39..696c4748b733876cde4940e540b35df66da00097 100644 --- a/scripts/FilterEvents/eventFilterGUI.py +++ b/scripts/FilterEvents/eventFilterGUI.py @@ -4,16 +4,13 @@ # NScD Oak Ridge National Laboratory, European Spallation Source # & Institut Laue - Langevin # SPDX - License - Identifier: GPL - 3.0 + -#pylint: disable=invalid-name, too-many-lines, too-many-instance-attributes +# pylint: disable=invalid-name, too-many-lines, too-many-instance-attributes from __future__ import (absolute_import, division, print_function) import numpy -from FilterEvents.ui_MainWindow import Ui_MainWindow #import line for the UI python class -from PyQt4 import QtCore, QtGui -from PyQt4.QtCore import * -from PyQt4.QtGui import * +from qtpy.QtWidgets import (QFileDialog, QMainWindow, QMessageBox, QSlider, QVBoxLayout, QWidget) # noqa +from qtpy.QtGui import (QDoubleValidator) # noqa -from matplotlib.pyplot import setp import mantid import mantid.simpleapi as api @@ -22,62 +19,22 @@ from mantid.kernel import Logger from mantid.simpleapi import AnalysisDataService from mantid.kernel import ConfigService - +from MPLwidgets import FigureCanvasQTAgg as FigureCanvas +from matplotlib.pyplot import (Figure, setp) import os +try: + from mantidqt.utils.qt import load_ui +except ImportError: + Logger("Filter_Events").information('Using legacy ui importer') + from mantidplot import load_ui + HUGE_FAST = 10000 HUGE_PARALLEL = 100000 MAXTIMEBINSIZE = 3000 -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - def _fromUtf8(s): - return s - - -class MyPopErrorMsg(QWidget): - """ Pop up dialog window - """ - - def __init__(self): - """ Init - """ - import FilterEvents.ui_ErrorMessage as errui - QWidget.__init__(self) - - self.ui = errui.Ui_Dialog() - self.ui.setupUi(self) - - QtCore.QObject.connect(self.ui.pushButton_quit, QtCore.SIGNAL('clicked()'), self.quit) - - def setMessage(self, errmsg): - """ Set message - """ - self.ui.label_errmsg.setWordWrap(True) - self.ui.label_errmsg.setText(errmsg) - - return - - def quit(self): - """ Quit - """ - self.close() - - return - - def XpaintEvent(self, _): - """ ??? - """ - import FilterEvents.ui_ErrorMessage as errui - - self.ui = errui.Ui_Dialog() - self.ui.setupUi(self) - - return - -class MainWindow(QtGui.QMainWindow): +class MainWindow(QMainWindow): """ Class of Main Window (top) """ @@ -87,19 +44,23 @@ class MainWindow(QtGui.QMainWindow): """ Initialization and set up """ # Base class - QtGui.QMainWindow.__init__(self,parent) + QMainWindow.__init__(self, parent) # Mantid configuration config = ConfigService.Instance() self._instrument = config["default.instrument"] # Central widget - self.centralwidget = QtGui.QWidget(self) + self.centralwidget = QWidget(self) # UI Window (from Qt Designer) - self.ui = Ui_MainWindow() - self.ui.setupUi(self) - self.ui.mainplot = self.ui.graphicsView.getPlot() + self.ui = load_ui(__file__, 'MainWindow.ui', baseinstance=self) + mpl_layout = QVBoxLayout() + self.ui.graphicsView.setLayout(mpl_layout) + self.fig = Figure() + self.canvas = FigureCanvas(self.fig) + self.ui.mainplot = self.fig.add_subplot(111, projection='mantid') + mpl_layout.addWidget(self.canvas) # Do initialize plotting vecx, vecy, xlim, ylim = self.computeMock() @@ -119,7 +80,7 @@ class MainWindow(QtGui.QMainWindow): lowery = [ylim[0], ylim[0]] self.lowerslideline = self.ui.mainplot.plot(lowerx, lowery, 'g--') - self.ui.graphicsView.mpl_connect('button_press_event', self.on_mouseDownEvent) + self.canvas.mpl_connect('button_press_event', self.on_mouseDownEvent) # Set up horizontal slide (integer) and string value self._leftSlideValue = 0 @@ -129,48 +90,46 @@ class MainWindow(QtGui.QMainWindow): self.ui.horizontalSlider.setValue(self._leftSlideValue) self.ui.horizontalSlider.setTracking(True) self.ui.horizontalSlider.setTickPosition(QSlider.NoTicks) - self.connect(self.ui.horizontalSlider, SIGNAL('valueChanged(int)'), self.move_leftSlider) + self.ui.horizontalSlider.valueChanged.connect(self.move_leftSlider) self.ui.horizontalSlider_2.setRange(0, 100) self.ui.horizontalSlider_2.setValue(self._rightSlideValue) self.ui.horizontalSlider_2.setTracking(True) self.ui.horizontalSlider_2.setTickPosition(QSlider.NoTicks) - self.connect(self.ui.horizontalSlider_2, SIGNAL('valueChanged(int)'), self.move_rightSlider) + self.ui.horizontalSlider_2.valueChanged.connect(self.move_rightSlider) # self.connect(self.ui.lineEdit_3, QtCore.SIGNAL("textChanged(QString)"), # self.set_startTime) - self.ui.lineEdit_3.setValidator(QtGui.QDoubleValidator(self.ui.lineEdit_3)) - self.connect(self.ui.pushButton_setT0, QtCore.SIGNAL("clicked()"), self.set_startTime) + self.ui.lineEdit_3.setValidator(QDoubleValidator(self.ui.lineEdit_3)) + self.ui.pushButton_setT0.clicked.connect(self.set_startTime) # self.connect(self.ui.lineEdit_4, QtCore.SIGNAL("textChanged(QString)"), # self.set_stopTime) - self.ui.lineEdit_4.setValidator(QtGui.QDoubleValidator(self.ui.lineEdit_4)) - self.connect(self.ui.pushButton_setTf, QtCore.SIGNAL("clicked()"), self.set_stopTime) + self.ui.lineEdit_4.setValidator(QDoubleValidator(self.ui.lineEdit_4)) + self.ui.pushButton_setTf.clicked.connect(self.set_stopTime) # File loader self.scanEventWorkspaces() - self.connect(self.ui.pushButton_refreshWS, SIGNAL('clicked()'), self.scanEventWorkspaces) - self.connect(self.ui.pushButton_browse, SIGNAL('clicked()'), self.browse_File) - self.connect(self.ui.pushButton_load, SIGNAL('clicked()'), self.load_File) - self.connect(self.ui.pushButton_3, SIGNAL('clicked()'), self.use_existWS) + self.ui.pushButton_refreshWS.clicked.connect(self.scanEventWorkspaces) + self.ui.pushButton_browse.clicked.connect(self.browse_File) + self.ui.pushButton_load.clicked.connect(self.load_File) + self.ui.pushButton_3.clicked.connect(self.use_existWS) # Set up time - self.ui.lineEdit_3.setValidator(QtGui.QDoubleValidator(self.ui.lineEdit_3)) - self.ui.lineEdit_4.setValidator(QtGui.QDoubleValidator(self.ui.lineEdit_4)) + self.ui.lineEdit_3.setValidator(QDoubleValidator(self.ui.lineEdit_3)) + self.ui.lineEdit_4.setValidator(QDoubleValidator(self.ui.lineEdit_4)) # Filter by time - self.connect(self.ui.pushButton_filterTime, SIGNAL('clicked()'), self.filterByTime) + self.ui.pushButton_filterTime.clicked.connect(self.filterByTime) # Filter by log value - self.ui.lineEdit_5.setValidator(QtGui.QDoubleValidator(self.ui.lineEdit_5)) - self.ui.lineEdit_6.setValidator(QtGui.QDoubleValidator(self.ui.lineEdit_6)) - self.ui.lineEdit_7.setValidator(QtGui.QDoubleValidator(self.ui.lineEdit_7)) - self.ui.lineEdit_8.setValidator(QtGui.QDoubleValidator(self.ui.lineEdit_8)) - self.ui.lineEdit_9.setValidator(QtGui.QDoubleValidator(self.ui.lineEdit_9)) + self.ui.lineEdit_5.setValidator(QDoubleValidator(self.ui.lineEdit_5)) + self.ui.lineEdit_6.setValidator(QDoubleValidator(self.ui.lineEdit_6)) + self.ui.lineEdit_7.setValidator(QDoubleValidator(self.ui.lineEdit_7)) + self.ui.lineEdit_8.setValidator(QDoubleValidator(self.ui.lineEdit_8)) + self.ui.lineEdit_9.setValidator(QDoubleValidator(self.ui.lineEdit_9)) - self.connect(self.ui.lineEdit_5, QtCore.SIGNAL("textChanged(QString)"), - self.set_minLogValue) - self.connect(self.ui.lineEdit_6, QtCore.SIGNAL("textChanged(QString)"), - self.set_maxLogValue) + self.ui.lineEdit_5.textChanged.connect(self.set_minLogValue) + self.ui.lineEdit_6.textChanged.connect(self.set_maxLogValue) dirchangeops = ["Both", "Increase", "Decrease"] self.ui.comboBox_4.addItems(dirchangeops) @@ -178,12 +137,12 @@ class MainWindow(QtGui.QMainWindow): logboundops = ["Centre", "Left"] self.ui.comboBox_5.addItems(logboundops) - self.connect(self.ui.pushButton_4, SIGNAL('clicked()'), self.plotLogValue) + self.ui.pushButton_4.clicked.connect(self.plotLogValue) - self.connect(self.ui.pushButton_filterLog, SIGNAL('clicked()'), self.filterByLogValue) + self.ui.pushButton_filterLog.clicked.connect(self.filterByLogValue) - #Set up help button - self.connect(self.ui.helpBtn, QtCore.SIGNAL('clicked()'), self.helpClicked) + # Set up help button + self.ui.helpBtn.clicked.connect(self.helpClicked) # Set up vertical slide self._upperSlideValue = 99 @@ -192,12 +151,12 @@ class MainWindow(QtGui.QMainWindow): self.ui.verticalSlider.setRange(0, 100) self.ui.verticalSlider.setValue(self._upperSlideValue) self.ui.verticalSlider.setTracking(True) - self.connect(self.ui.verticalSlider, SIGNAL('valueChanged(int)'), self.move_upperSlider) + self.ui.verticalSlider.valueChanged.connect(self.move_upperSlider) self.ui.verticalSlider_2.setRange(0, 100) self.ui.verticalSlider_2.setValue(self._lowerSlideValue) self.ui.verticalSlider_2.setTracking(True) - self.connect(self.ui.verticalSlider_2, SIGNAL('valueChanged(int)'), self.move_lowerSlider) + self.ui.verticalSlider_2.valueChanged.connect(self.move_lowerSlider) # Set up for filtering (advanced setup) self._tofcorrection = False @@ -206,10 +165,10 @@ class MainWindow(QtGui.QMainWindow): self.ui.checkBox_from1.setChecked(False) self.ui.checkBox_groupWS.setChecked(True) - self.connect(self.ui.comboBox_tofCorr, SIGNAL('currentIndexChanged(int)'), self.showHideEi) - self.connect(self.ui.pushButton_refreshCorrWSList, SIGNAL('clicked()'), self._searchTableWorkspaces) + self.ui.comboBox_tofCorr.currentIndexChanged.connect(self.showHideEi) + self.ui.pushButton_refreshCorrWSList.clicked.connect(self._searchTableWorkspaces) - self.ui.lineEdit_Ei.setValidator(QtGui.QDoubleValidator(self.ui.lineEdit_Ei)) + self.ui.lineEdit_Ei.setValidator(QDoubleValidator(self.ui.lineEdit_Ei)) self.ui.label_Ei.hide() self.ui.lineEdit_Ei.hide() @@ -217,11 +176,6 @@ class MainWindow(QtGui.QMainWindow): self.ui.comboBox_corrWS.hide() self.ui.pushButton_refreshCorrWSList.hide() - # Error message - # self.connect(self.ui.pushButton_clearerror, SIGNAL('clicked()'), self._clearErrorMsg) - # self.ui.plainTextEdit_ErrorMsg.setReadOnly(True) - # self.ui.label_error.hide() - # Set up for workspaces self._dataWS = None self._sampleLogNames = [] @@ -242,19 +196,8 @@ class MainWindow(QtGui.QMainWindow): # Default self._defaultdir = os.getcwd() - # self.ui.InputVal.setValidator(QtGui.QDoubleValidator(self.ui.InputVal)) - - # QtCore.QObject.connect(self.ui.convert, QtCore.SIGNAL("clicked()"), self.convert ) - # QtCore.QObject.connect(self.ui.inputUnits, QtCore.SIGNAL("currentIndexChanged(QString)"), self.setInstrumentInputs ) - # QtCore.QObject.connect(self.ui.outputUnits, QtCore.SIGNAL("currentIndexChanged(QString)"), self.setInstrumentInputs ) - # self.setInstrumentInputs() - - ##defaults - - #register startup - mantid.UsageService.registerFeatureUsage("Interface","EventFilter",False) - - return + # register startup + mantid.UsageService.registerFeatureUsage("Interface", "EventFilter", False) def on_mouseDownEvent(self, event): """ Respond to pick up a value with mouse down event @@ -266,8 +209,6 @@ class MainWindow(QtGui.QMainWindow): msg = "You've clicked on a bar with coords:\n %f, %f" % (x, y) QMessageBox.information(self, "Click!", msg) - return - def computeMock(self): """ Compute vecx and vecy as mocking """ @@ -305,8 +246,7 @@ class MainWindow(QtGui.QMainWindow): leftx = [newx, newx] lefty = self.ui.mainplot.get_ylim() setp(self.leftslideline, xdata=leftx, ydata=lefty) - - self.ui.graphicsView.draw() + self.canvas.draw() # Change value self.ui.lineEdit_3.setText(str(newx)) @@ -315,8 +255,6 @@ class MainWindow(QtGui.QMainWindow): # Reset the value to original value self.ui.horizontalSlider.setValue(self._leftSlideValue) - return - def set_startTime(self): """ Set the starting time and left slide bar """ @@ -332,7 +270,7 @@ class MainWindow(QtGui.QMainWindow): newtime0 = float(inps) # Convert to integer slide value - ileftvalue = int( (newtime0-xlim[0])/(xlim[1] - xlim[0])*100 ) + ileftvalue = int((newtime0-xlim[0])/(xlim[1] - xlim[0])*100) debug_msg = "iLeftSlide = %s" % str(ileftvalue) Logger("Filter_Events").debug(debug_msg) @@ -353,7 +291,8 @@ class MainWindow(QtGui.QMainWindow): if resetT is True: newtime0 = xlim[0] + ileftvalue*(xlim[1]-xlim[0])*0.01 - info_msg = "Corrected iLeftSlide = %s (vs. right = %s)" % (str(ileftvalue), str(self._rightSlideValue)) + info_msg = 'Corrected iLeftSlide = {} (vs. right = {})'.format(ileftvalue, + self._rightSlideValue) Logger("Filter_Events").information(info_msg) # Move the slide bar (left) @@ -363,8 +302,7 @@ class MainWindow(QtGui.QMainWindow): leftx = [newtime0, newtime0] lefty = self.ui.mainplot.get_ylim() setp(self.leftslideline, xdata=leftx, ydata=lefty) - - self.ui.graphicsView.draw() + self.canvas.draw() # Set the value to left slider self.ui.horizontalSlider.setValue(self._leftSlideValue) @@ -372,8 +310,6 @@ class MainWindow(QtGui.QMainWindow): if resetT is True: self.ui.lineEdit_3.setText(str(newtime0)) - return - def move_rightSlider(self): """ Re-setup left range line in figure. Triggered by a change in Qt Widget. NO EVENT is required. @@ -388,8 +324,7 @@ class MainWindow(QtGui.QMainWindow): leftx = [newx, newx] lefty = self.ui.mainplot.get_ylim() setp(self.rightslideline, xdata=leftx, ydata=lefty) - - self.ui.graphicsView.draw() + self.canvas.draw() # Change value self.ui.lineEdit_4.setText(str(newx)) @@ -398,14 +333,11 @@ class MainWindow(QtGui.QMainWindow): # Reset the value self.ui.horizontalSlider_2.setValue(self._rightSlideValue) - return - def set_stopTime(self): """ Set the starting time and left slide bar """ inps = str(self.ui.lineEdit_4.text()) - info_msg = "Stopping time = %s" % (inps) - Logger("Filter_Events").information(info_msg) + Logger("Filter_Events").information('Stopping time = {}'.format(inps)) xlim = self.ui.mainplot.get_xlim() if inps == "": @@ -416,9 +348,8 @@ class MainWindow(QtGui.QMainWindow): newtimef = float(inps) # Convert to integer slide value - irightvalue = int( (newtimef-xlim[0])/(xlim[1] - xlim[0])*100 ) - info_msg = "iRightSlide = %s" % str(irightvalue) - Logger("Filter_Events").information(info_msg) + irightvalue = int((newtimef-xlim[0])/(xlim[1] - xlim[0])*100) + Logger("Filter_Events").information('iRightSlide = {}'.format(irightvalue)) # Return if no change if irightvalue == self._rightSlideValue: @@ -443,8 +374,7 @@ class MainWindow(QtGui.QMainWindow): rightx = [newtimef, newtimef] righty = self.ui.mainplot.get_ylim() setp(self.rightslideline, xdata=rightx, ydata=righty) - - self.ui.graphicsView.draw() + self.canvas.draw() # Set the value to left slider self.ui.horizontalSlider_2.setValue(self._rightSlideValue) @@ -453,14 +383,13 @@ class MainWindow(QtGui.QMainWindow): if resetT: self.ui.lineEdit_4.setText(str(newtimef)) - return - def move_lowerSlider(self): """ Re-setup upper range line in figure. Triggered by a change in Qt Widget. NO EVENT is required. """ inewy = self.ui.verticalSlider_2.value() - debug_msg = "LowerSlFider is set with value %s vs. class variable %s" % (str(inewy), str(self._lowerSlideValue)) + debug_msg = 'LowerSlFider is set with value {} vs. class variable {}'.format(inewy, + self._lowerSlideValue) Logger("Filter_Events").debug(debug_msg) # Return with no change @@ -483,8 +412,7 @@ class MainWindow(QtGui.QMainWindow): lowerx = self.ui.mainplot.get_xlim() lowery = [newy, newy] setp(self.lowerslideline, xdata=lowerx, ydata=lowery) - - self.ui.graphicsView.draw() + self.canvas.draw() # Set line edit input if setLineEdit is True: @@ -493,12 +421,10 @@ class MainWindow(QtGui.QMainWindow): # Reset the class variable self._lowerSlideValue = inewy - return - def set_minLogValue(self): """ Set the starting time and left slide bar """ - debug_msg = "Minimum Log Value = %s" %(str(self.ui.lineEdit_5.text())) + debug_msg = 'Minimum Log Value = {}'.format(self.ui.lineEdit_5.text()) Logger("Filter_Events").debug(debug_msg) ylim = self.ui.mainplot.get_ylim() @@ -511,9 +437,8 @@ class MainWindow(QtGui.QMainWindow): newminY = float(self.ui.lineEdit_5.text()) # Convert to integer slide value - iminlogval = int( (newminY-ylim[0])/(ylim[1] - ylim[0])*100 ) - debug_msg = "ilowerSlide = %s" % str(iminlogval) - Logger("Filter_Events").debug(debug_msg) + iminlogval = int((newminY-ylim[0])/(ylim[1] - ylim[0])*100) + Logger("Filter_Events").debug('ilowerSlide = {}'.format(iminlogval)) # Return if no change if iminlogval == self._lowerSlideValue: @@ -530,15 +455,14 @@ class MainWindow(QtGui.QMainWindow): newminY = ylim[0] + iminlogval * (ylim[1]-ylim[0]) * 0.01 # Move the vertical line - lowerx = self.ui.mainplot.get_xlim() - lowery = [newminY, newminY] + lowerx = self.ui.mainplot.get_xlim() + lowery = [newminY, newminY] setp(self.lowerslideline, xdata=lowerx, ydata=lowery) - - self.ui.graphicsView.draw() + self.canvas.draw() # Move the slide bar (lower) self._lowerSlideValue = iminlogval - debug_msg = "LineEdit5 set slide to %s" % str(self._lowerSlideValue) + debug_msg = 'LineEdit5 set slide to {}'.format(self._lowerSlideValue) Logger("Filter_Events").debug(debug_msg) self.ui.verticalSlider_2.setValue(self._lowerSlideValue) @@ -546,8 +470,6 @@ class MainWindow(QtGui.QMainWindow): if resetL is True: self.ui.lineEdit_5.setText(str(newminY)) - return - def move_upperSlider(self): """ Re-setup upper range line in figure. Triggered by a change in Qt Widget. NO EVENT is required. @@ -568,27 +490,25 @@ class MainWindow(QtGui.QMainWindow): else: setLineEdit = True - # Move the upper value bar: upperx and uppery are real value (float but not (0,100)) of the figure + # Move the upper value bar: upperx and uppery are + # real value (float but not (0,100)) of the figure ylim = self.ui.mainplot.get_ylim() newy = ylim[0] + inewy*(ylim[1] - ylim[0])*0.01 upperx = self.ui.mainplot.get_xlim() uppery = [newy, newy] setp(self.upperslideline, xdata=upperx, ydata=uppery) + self.canvas.draw() - self.ui.graphicsView.draw() - - # Change value + # Change value if setLineEdit is True: self.ui.lineEdit_6.setText(str(newy)) self._upperSlideValue = inewy - return - def set_maxLogValue(self): """ Set maximum log value from line-edit """ inps = str(self.ui.lineEdit_6.text()) - debug_msg = "Maximum Log Value = %s" %(inps) + debug_msg = 'Maximum Log Value = {}'.format(inps) Logger("Filter_Events").debug(debug_msg) ylim = self.ui.mainplot.get_ylim() @@ -600,8 +520,8 @@ class MainWindow(QtGui.QMainWindow): newmaxY = float(inps) # Convert to integer slide value - imaxlogval = int( (newmaxY-ylim[0])/(ylim[1] - ylim[0])*100 ) - debug_msg = "iUpperSlide = %s" % str(imaxlogval) + imaxlogval = int((newmaxY-ylim[0])/(ylim[1] - ylim[0])*100) + debug_msg = 'iUpperSlide = {}'.format(imaxlogval) Logger("Filter_Events").debug(debug_msg) # Return if no change @@ -622,11 +542,10 @@ class MainWindow(QtGui.QMainWindow): newmaxY = ylim[0] + imaxlogval * (ylim[1] - ylim[0]) * 0.01 # Move the vertical line - upperx = self.ui.mainplot.get_xlim() - uppery = [newmaxY, newmaxY] + upperx = self.ui.mainplot.get_xlim() + uppery = [newmaxY, newmaxY] setp(self.upperslideline, xdata=upperx, ydata=uppery) - - self.ui.graphicsView.draw() + self.canvas.draw() # Set the value to upper slider self._upperSlideValue = imaxlogval @@ -636,36 +555,26 @@ class MainWindow(QtGui.QMainWindow): if resetL is True: self.ui.lineEdit_6.setText(str(newmaxY)) - return - def browse_File(self): """ Open a file dialog to get file """ - filename = QtGui.QFileDialog.getOpenFileName(self, 'Input File Dialog', - self._defaultdir, "Data (*.nxs *.dat);;All files (*)") + filename = QFileDialog.getOpenFileName(self, 'Input File Dialog', + self._defaultdir, "Data (*.nxs *.dat);;All files (*)") self.ui.lineEdit.setText(str(filename)) info_msg = "Selected file: %s." % str(filename) Logger("Filter_Events").information(info_msg) - return - def load_File(self): """ Load the file by file name or run number """ # Get file name from line editor filename = str(self.ui.lineEdit.text()) - # Find out it is relative path or absolute path - #if os.path.abspath(filename) == filename: - # isabspath = True - #else: - # isabspath = False - dataws = self._loadFile(str(filename)) if dataws is None: - error_msg = "Unable to locate run %s in default directory %s." % (filename, self._defaultdir) + error_msg = 'Unable to locate run {} in default directory {}.'.format(filename, self._defaultdir) Logger("Filter_Events").error(error_msg) self._setErrorMsg(error_msg) else: @@ -675,8 +584,6 @@ class MainWindow(QtGui.QMainWindow): # Reset GUI self._resetGUI(resetfilerun=False) - return - def use_existWS(self): """ Set up workspace to an existing one """ @@ -691,8 +598,6 @@ class MainWindow(QtGui.QMainWindow): # Reset GUI self._resetGUI(resetfilerun=True) - return - def plotLogValue(self): """ Plot log value """ @@ -711,12 +616,12 @@ class MainWindow(QtGui.QMainWindow): error_msg = "Empty log!" Logger("Filter_Events").error(error_msg) - #Convert absolute time to relative time in seconds + # Convert absolute time to relative time in seconds t0 = self._dataWS.getRun().getProperty("proton_charge").times[0] # append 1 more log if original log only has 1 value tf = self._dataWS.getRun().getProperty("proton_charge").times[-1] - vectimes = numpy.append(vectimes,tf) + vectimes = numpy.append(vectimes, tf) vecvalue = numpy.append(vecvalue, vecvalue[-1]) vecreltimes = (vectimes - t0) / numpy.timedelta64(1, 's') @@ -750,8 +655,7 @@ class MainWindow(QtGui.QMainWindow): self._upperSlideValue = 100 self.ui.verticalSlider.setValue(self._upperSlideValue) self.ui.lineEdit_6.setText("") - - self.ui.graphicsView.draw() + self.canvas.draw() # Load property's statistic and give suggestion on parallel and fast log timeavg = samplelog.timeAverageValue() @@ -773,9 +677,9 @@ class MainWindow(QtGui.QMainWindow): self.ui.label_logsize.show() self.ui.label_logsizevalue.show() - self.ui.label_meanvalue.setText("%.5e"%(mean)) - self.ui.label_timeAvgValue.setText("%.5e"%(timeavg)) - self.ui.label_freqValue.setText("%.5e"%(freq)) + self.ui.label_meanvalue.setText("%.5e" % (mean)) + self.ui.label_timeAvgValue.setText("%.5e" % (timeavg)) + self.ui.label_freqValue.setText("%.5e" % (freq)) self.ui.label_lognamevalue.setText(logname) self.ui.label_logsizevalue.setText(str(numentries)) @@ -801,8 +705,8 @@ class MainWindow(QtGui.QMainWindow): # Plot time counts errmsg = self._plotTimeCounts(dataws) if errmsg is not None: - errmsg = "Workspace %s has invalid sample logs for splitting. Loading \ - failure! \n%s\n" % (str(dataws), errmsg) + errmsg = 'Workspace {} has invalid sample logs for splitting. Loading \ + failure! \n{}\n'.format(dataws, errmsg) self._setErrorMsg(errmsg) return False @@ -858,8 +762,6 @@ class MainWindow(QtGui.QMainWindow): self.ui.comboBox.clear() self.ui.comboBox.addItems(eventwsnames) - return - def _loadFile(self, filename): """ Load file or run File will be loaded to a workspace shown in MantidPlot @@ -871,12 +773,12 @@ class MainWindow(QtGui.QMainWindow): # Construct a file name from run number runnumber = int(filename) if runnumber <= 0: - error_msg = "Run number cannot be less or equal to zero. User gives %s. " % (filename) + error_msg = 'Run number cannot be less or equal to zero. User gives {}.'.format(filename) Logger("Filter_Events").error(error_msg) return None else: ishort = config.getInstrument(self._instrument).shortName() - filename = "%s_%s" %(ishort, filename) + filename = '{}_{}'.format(ishort, filename) wsname = filename + "_event" elif filename.count(".") > 0: @@ -890,17 +792,17 @@ class MainWindow(QtGui.QMainWindow): if str_runnumber.isdigit() is True and int(str_runnumber) > 0: # Accepted format ishort = config.getInstrument(iname).shortName() - wsname = "%s_%s_event" % (ishort, str_runnumber) + wsname = '{}_{}_event'.format(ishort, str_runnumber) else: # Non-supported - error_msg = "File name / run number in such format %s is not supported. " % (filename) + error_msg = 'File name / run number in such format {} is not supported.'.format(filename) Logger("Filter_Events").error(error_msg) return None else: # Unsupported format - error_msg = "File name / run number in such format %s is not supported. " % (filename) + error_msg = 'File name / run number in such format {} is not supported.'.format(filename) Logger("Filter_Events").error(error_msg) return None @@ -941,11 +843,11 @@ class MainWindow(QtGui.QMainWindow): if timeres < 1.0: timeres = 1.0 - sumwsname = "_Summed_%s"%(str(wksp)) + sumwsname = '_Summed_{}'.format(wksp) if AnalysisDataService.doesExist(sumwsname) is False: sumws = api.SumSpectra(InputWorkspace=wksp, OutputWorkspace=sumwsname) - sumws = api.RebinByPulseTimes(InputWorkspace=sumws, OutputWorkspace = sumwsname, - Params="%f"%(timeres)) + sumws = api.RebinByPulseTimes(InputWorkspace=sumws, OutputWorkspace=sumwsname, + Params='{}'.format(timeres)) sumws = api.ConvertToPointData(InputWorkspace=sumws, OutputWorkspace=sumwsname) else: sumws = AnalysisDataService.retrieve(sumwsname) @@ -978,10 +880,7 @@ class MainWindow(QtGui.QMainWindow): newrightx = xmin + (xmax-xmin)*self._rightSlideValue*0.01 setp(self.rightslideline, xdata=[newrightx, newrightx], ydata=newslidery) - - self.ui.graphicsView.draw() - - return + self.canvas.draw() def filterByTime(self): """ Filter by time @@ -1004,18 +903,16 @@ class MainWindow(QtGui.QMainWindow): title = str(self.ui.lineEdit_title.text()) fastLog = self.ui.checkBox_fastLog.isChecked() - splitws, infows = api.GenerateEventsFilter( - InputWorkspace = self._dataWS, - UnitOfTime = "Seconds", - TitleOfSplitters = title, - OutputWorkspace = splitwsname, - FastLog = fastLog, - InformationWorkspace = splitinfowsname, **kwargs) + splitws, infows = api.GenerateEventsFilter(InputWorkspace=self._dataWS, + UnitOfTime="Seconds", + TitleOfSplitters=title, + OutputWorkspace=splitwsname, + FastLog=fastLog, + InformationWorkspace=splitinfowsname, + **kwargs) self.splitWksp(splitws, infows) - return - def filterByLogValue(self): """ Filter by log value """ @@ -1065,22 +962,20 @@ class MainWindow(QtGui.QMainWindow): title = str(self.ui.lineEdit_title.text()) - splitws, infows = api.GenerateEventsFilter( - InputWorkspace = self._dataWS, - UnitOfTime = "Seconds", - TitleOfSplitters = title, - OutputWorkspace = splitwsname, - LogName = samplelog, - FastLog = fastLog, - InformationWorkspace = splitinfowsname, **kwargs) + splitws, infows = api.GenerateEventsFilter(InputWorkspace=self._dataWS, + UnitOfTime="Seconds", + TitleOfSplitters=title, + OutputWorkspace=splitwsname, + LogName=samplelog, + FastLog=fastLog, + InformationWorkspace=splitinfowsname, + **kwargs) try: self.splitWksp(splitws, infows) except RuntimeError as e: self._setErrorMsg("Splitting Failed!\n %s" % (str(e))) - return - def splitWksp(self, splitws, infows): """ Run FilterEvents """ @@ -1106,20 +1001,17 @@ class MainWindow(QtGui.QMainWindow): outbasewsname = "tempsplitted" self.ui.lineEdit_outwsname.setText(outbasewsname) - api.FilterEvents( - InputWorkspace = self._dataWS, - SplitterWorkspace = splitws, - InformationWorkspace = infows, - OutputWorkspaceBaseName = outbasewsname, - GroupWorkspaces = dogroupws, - FilterByPulseTime = filterbypulse, - CorrectionToSample = corr2sample, - SpectrumWithoutDetector = how2skip, - SplitSampleLogs = splitsamplelog, - OutputWorkspaceIndexedFrom1 = startfrom1, - OutputTOFCorrectionWorkspace = 'TOFCorrTable', **kwargs) - - return + api.FilterEvents(InputWorkspace=self._dataWS, + SplitterWorkspace=splitws, + InformationWorkspace=infows, + OutputWorkspaceBaseName=outbasewsname, + GroupWorkspaces=dogroupws, + FilterByPulseTime=filterbypulse, + CorrectionToSample=corr2sample, + SpectrumWithoutDetector=how2skip, + SplitSampleLogs=splitsamplelog, + OutputWorkspaceIndexedFrom1=startfrom1, + OutputTOFCorrectionWorkspace='TOFCorrTable', **kwargs) def showHideEi(self): """ @@ -1148,8 +1040,6 @@ class MainWindow(QtGui.QMainWindow): self.ui.comboBox_corrWS.hide() self.ui.pushButton_refreshCorrWSList.hide() - return - def _searchTableWorkspaces(self): """ Search table workspaces and add to 'comboBox_corrWS' """ @@ -1166,28 +1056,17 @@ class MainWindow(QtGui.QMainWindow): if len(tablewsnames) > 0: self.ui.comboBox_corrWS.addItems(tablewsnames) - return - - def _clearErrorMsg(self): - """ Clear error message - """ - #self.ui.plainTextEdit_ErrorMsg.setPlainText("") - #self.ui.label_error.hide() - - return - def _setErrorMsg(self, errmsg): """ Clear error message """ - #self.ui.plainTextEdit_ErrorMsg.setPlainText(errmsg) - #self.ui.label_error.show() - - #print "Testing Pop-up Error Message Window: %s" % (errmsg) - self._errMsgWindow = MyPopErrorMsg() - self._errMsgWindow.setMessage(errmsg) - self._errMsgWindow.show() + self._errMsgWindow = QMessageBox() + self._errMsgWindow.setIcon(QMessageBox.Critical) + self._errMsgWindow.setWindowTitle('Error') + self._errMsgWindow.setStandardButtons(QMessageBox.Ok) + self._errMsgWindow.setText(errmsg) + result = self._errMsgWindow.exec_() - return + return result def helpClicked(self): from pymantidplot.proxies import showCustomInterfaceHelp @@ -1221,7 +1100,6 @@ class MainWindow(QtGui.QMainWindow): xlim = self.ui.mainplot.get_xlim() setp(self.lowerslideline, xdata=xlim, ydata=[miny, miny]) setp(self.upperslideline, xdata=xlim, ydata=[maxy, maxy]) - self.ui.graphicsView.draw() self.ui.lineEdit_7.clear() self.ui.lineEdit_8.clear() @@ -1244,7 +1122,4 @@ class MainWindow(QtGui.QMainWindow): self.ui.checkBox_groupWS.setCheckState(True) self.ui.checkBox_splitLog.setCheckState(False) - # Error message - # self.ui.plainTextEdit_ErrorMsg.clear() - - return + self.canvas.draw() diff --git a/scripts/HFIR_4Circle_Reduction.py b/scripts/HFIR_4Circle_Reduction.py index d4fe41a0e9048d570e8c30bcd5ddf33b2a21da7d..8742a608399e95830a34684f8a605119d7a84022 100644 --- a/scripts/HFIR_4Circle_Reduction.py +++ b/scripts/HFIR_4Circle_Reduction.py @@ -6,23 +6,13 @@ # SPDX - License - Identifier: GPL - 3.0 + #pylint: disable=invalid-name from __future__ import (absolute_import, division, print_function) -from HFIR_4Circle_Reduction import reduce4circleGUI -from PyQt4 import QtGui, QtCore import sys +from gui_helper import set_matplotlib_backend, get_qapplication +set_matplotlib_backend() # must be called before anything tries to use matplotlib +from HFIR_4Circle_Reduction import reduce4circleGUI # noqa - -def qapp(): - if QtGui.QApplication.instance(): - _app = QtGui.QApplication.instance() - else: - _app = QtGui.QApplication(sys.argv) - return _app - - -# try to defeat X11 unsafe thread -QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) -app = qapp() - -reducer = reduce4circleGUI.MainWindow() #the main ui class in this file is called MainWindow +app, within_mantid = get_qapplication() +reducer = reduce4circleGUI.MainWindow() # the main ui class in this file reducer.show() -app.exec_() +if not within_mantid: + sys.exit(app.exec_()) diff --git a/scripts/HFIR_4Circle_Reduction/CMakeLists.txt b/scripts/HFIR_4Circle_Reduction/CMakeLists.txt deleted file mode 100644 index 9e79b806c6355dc13d2cc8dd88c6338dce3c58f5..0000000000000000000000000000000000000000 --- a/scripts/HFIR_4Circle_Reduction/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -include(UiToPy) - -# List of UIs to Auto convert -set( UI_FILES - MainWindow.ui - messagebox.ui - View3DWidget.ui - OptimizeLattice.ui - RefineUbFftDialog.ui - SpiceViewerDialog.ui - UBSelectPeaksDialog.ui - AddUBPeaksDialog.ui - PeakIntegrationSpreadSheet.ui - httpserversetup.ui - preprocess_window.ui - peak_integration_info.ui -) - -UiToPy( UI_FILES CompileUIHFIR_4Circle_Reduction) diff --git a/scripts/HFIR_4Circle_Reduction/FindUBUtility.py b/scripts/HFIR_4Circle_Reduction/FindUBUtility.py index 7ff5cfa57b2c2f72d164c383d19d4469ce47faf0..8c1ccf6d7fa8f3b0a5f7f5fb1261074460b172ed 100644 --- a/scripts/HFIR_4Circle_Reduction/FindUBUtility.py +++ b/scripts/HFIR_4Circle_Reduction/FindUBUtility.py @@ -9,15 +9,17 @@ Containing a set of classes used for finding (calculating and refining) UB matri """ from __future__ import (absolute_import, division, print_function) import os - -from . import ui_AddUBPeaksDialog -from . import ui_UBSelectPeaksDialog from . import guiutility - -from PyQt4 import QtGui, QtCore +from qtpy.QtWidgets import (QDialog, QFileDialog) # noqa +from mantid.kernel import Logger +try: + from mantidqt.utils.qt import load_ui +except ImportError: + Logger("HFIR_4Circle_Reduction").information('Using legacy ui importer') + from mantidplot import load_ui -class AddScansForUBDialog(QtGui.QDialog): +class AddScansForUBDialog(QDialog): """ Dialog class to add scans to UB scans' table for calculating and """ @@ -30,25 +32,19 @@ class AddScansForUBDialog(QtGui.QDialog): self._myParent = parent # set up UI - self.ui = ui_AddUBPeaksDialog.Ui_Dialog() - self.ui.setupUi(self) + ui_path = "AddUBPeaksDialog.ui" + self.ui = load_ui(__file__, ui_path, baseinstance=self) # initialize widgets self.ui.checkBox_loadHKLfromFile.setChecked(True) - # define event handling - self.connect(self.ui.pushButton_findPeak, QtCore.SIGNAL('clicked()'), - self.do_find_peak) - self.connect(self.ui.pushButton_addPeakToCalUB, QtCore.SIGNAL('clicked()'), - self.do_add_single_scan) + self.ui.pushButton_findPeak.clicked.connect(self.do_find_peak) + self.ui.pushButton_addPeakToCalUB.clicked.connect(self.do_add_single_scan) - self.connect(self.ui.pushButton_loadScans, QtCore.SIGNAL('clicked()'), - self.do_load_scans) - self.connect(self.ui.pushButton_addScans, QtCore.SIGNAL('clicked()'), - self.do_add_scans) + self.ui.pushButton_loadScans.clicked.connect(self.do_load_scans) + self.ui.pushButton_addScans.clicked.connect(self.do_add_scans) - self.connect(self.ui.pushButton_quit, QtCore.SIGNAL('clicked()'), - self.do_quit) + self.ui.pushButton_quit.clicked.connect(self.do_quit) return @@ -136,7 +132,7 @@ class AddScansForUBDialog(QtGui.QDialog): return -class SelectUBMatrixScansDialog(QtGui.QDialog): +class SelectUBMatrixScansDialog(QDialog): """ Dialog to select scans for processing UB matrix """ @@ -149,19 +145,14 @@ class SelectUBMatrixScansDialog(QtGui.QDialog): self._myParent = parent # set ui - self.ui = ui_UBSelectPeaksDialog.Ui_Dialog() - self.ui.setupUi(self) + ui_path = "UBSelectPeaksDialog.ui" + self.ui = load_ui(__file__, ui_path, baseinstance=self) # define event handling methods - self.connect(self.ui.pushButton_selectScans, QtCore.SIGNAL('clicked()'), - self.do_select_scans) - self.connect(self.ui.pushButton_revertCurrentSelection, QtCore.SIGNAL('clicked()'), - self.do_revert_selection) - self.connect(self.ui.pushButton_exportSelectedScans, QtCore.SIGNAL('clicked()'), - self.do_export_selected_scans) - - self.connect(self.ui.pushButton_quit, QtCore.SIGNAL('clicked()'), - self.do_quit) + self.ui.pushButton_selectScans.clicked.connect(self.do_select_scans) + self.ui.pushButton_revertCurrentSelection.clicked.connect(self.do_revert_selection) + self.ui.pushButton_exportSelectedScans.clicked.connect(self.do_export_selected_scans) + self.ui.pushButton_quit.clicked.connect(self.do_quit) return @@ -193,7 +184,7 @@ class SelectUBMatrixScansDialog(QtGui.QDialog): # get the output file name file_filter = 'Text Files (*.dat);;All Files (*.*)' - file_name = str(QtGui.QFileDialog.getSaveFileName(self, 'File to export selected scans', + file_name = str(QFileDialog.getSaveFileName(self, 'File to export selected scans', self._myParent.working_directory, file_filter)) # write file diff --git a/scripts/HFIR_4Circle_Reduction/IntegrateSingePtSubWindow.py b/scripts/HFIR_4Circle_Reduction/IntegrateSingePtSubWindow.py new file mode 100644 index 0000000000000000000000000000000000000000..7b9f9a8650e062f4af787969d6d51a4606b7544e --- /dev/null +++ b/scripts/HFIR_4Circle_Reduction/IntegrateSingePtSubWindow.py @@ -0,0 +1,579 @@ +#pylint: disable=C0103 +from __future__ import (absolute_import, division, print_function) +from HFIR_4Circle_Reduction.hfctables import SinglePtIntegrationTable +from HFIR_4Circle_Reduction.integratedpeakview import SinglePtIntegrationView +import HFIR_4Circle_Reduction.guiutility as guiutility +import os +from qtpy.QtWidgets import (QMainWindow, QFileDialog) # noqa +from qtpy.QtCore import Signal as pyqtSignal +from mantid.kernel import Logger +try: + from mantidqt.utils.qt import load_ui +except ImportError: + Logger("HFIR_4Circle_Reduction").information('Using legacy ui importer') + from mantidplot import load_ui +from qtpy.QtWidgets import (QVBoxLayout) + + +class IntegrateSinglePtIntensityWindow(QMainWindow): + """ + Main window widget to set up parameters to optimize + """ + # establish signal for communicating from App2 to App1 - must be defined before the constructor + scanIntegratedSignal = pyqtSignal(dict, name='SinglePtIntegrated') + + def __init__(self, parent=None): + """ + Initialization + :param parent: + :return: + """ + # init + super(IntegrateSinglePtIntensityWindow, self).__init__(parent) + + assert parent is not None, 'Parent window cannot be None to set' + self._parent_window = parent + self._controller = parent.controller + + # connect signal handler + self.scanIntegratedSignal.connect(self._parent_window.process_single_pt_scan_intensity) + + # init UI + ui_path = "SinglePtIntegrationWindow.ui" + self.ui = load_ui(__file__, ui_path, baseinstance=self) + self._promote_widgets() + + # initialize widgets + self.ui.tableView_summary.setup() + self.ui.graphicsView_integration1DView.set_parent_window(self) + + # define event handlers for widgets + self.ui.pushButton_integrteDetectorCounts.clicked.connect(self.do_integrate_detector_counts) + self.ui.pushButton_load2thetaSigmaFile.clicked.connect(self.menu_load_gauss_sigma_file) + + self.ui.pushButton_exportIntensityToFile.clicked.connect(self.do_save_intensity) + self.ui.pushButton_exportIntensityToTable.clicked.connect(self.do_export_intensity_to_parent) + # self.ui.pushButton_refreshROI.clicked.connect(self.do_refresh_roi) + self.ui.pushButton_retrieveFWHM.clicked.connect(self.do_retrieve_fwhm) + self.ui.pushButton_integratePeaks.clicked.connect(self.do_integrate_single_pt) + self.ui.pushButton_plot.clicked.connect(self.do_plot_integrated_pt) + self.ui.pushButton_exportToMovie.clicked.connect(self.do_export_to_movie) + + # TODO - 20180809 - Implement the following...calling change_scan_number + self.ui.pushButton_rewindPlot.clicked.connect(self.do_plot_previous_scan) + self.ui.pushButton_forwardPlot.clicked.connect(self.do_plot_next_scan) + self.ui.actionDefine_2theta_FWHM_Function.triggered.connect(self.do_define_2theta_fwhm_function) + + # menu bar + self.ui.menuQuit.triggered.connect(self.do_close) + self.ui.actionSelect_All.triggered.connect(self.menu_table_select_all) + self.ui.actionDe_select_All.triggered.connect(self.menu_table_select_none) + self.ui.actionLoad_Gaussian_Sigma_File.triggered.connect(self.menu_load_gauss_sigma_file) + self.ui.actionLoad_Peak_Info_File.triggered.connect(self.do_load_peak_integration_table) + self.ui.actionRefresh_ROI_List.triggered.connect(self.do_refresh_roi) + + # class variable + self._working_dir = self._controller.get_working_directory() + self._exp_number = None + self._roiMutex = False + + # other things to do + self.do_refresh_roi() + + return + + def _promote_widgets(self): + graphicsView_integration1DView_layout = QVBoxLayout() + self.ui.frame_graphicsView_integration1DView.setLayout(graphicsView_integration1DView_layout) + self.ui.graphicsView_integration1DView = SinglePtIntegrationView(self) + graphicsView_integration1DView_layout.addWidget(self.ui.graphicsView_integration1DView) + + tableView_summary_layout = QVBoxLayout() + self.ui.frame_tableView_summary.setLayout(tableView_summary_layout) + self.ui.tableView_summary = SinglePtIntegrationTable(self) + tableView_summary_layout.addWidget(self.ui.tableView_summary) + + return + + def do_close(self): + """ + Quit the window + :return: + """ + self.close() + + return + + def do_define_2theta_fwhm_function(self): + """ + pop out a dialog for user to input the 2theta-FWHM formula + :return: + """ + formula = guiutility.get_value_from_dialog(parent=self, title='Input 2theta-FWHM function', + details='Example: y = 4.0 * x**2 - 1.2 * x + 1./x]=\n' + 'where y is FWHM and x is 2theta', + label_name='Equation: ') + + if formula is None: + # return if user cancels operation + return + + print ('[DB...BAT] User input 2theta formula: {}'.format(formula)) + state, error_message = self._controller.check_2theta_fwhm_formula(formula) + if not state: + guiutility.show_message(self, message=error_message, message_type='error') + return + + return + + def do_export_intensity_to_parent(self): + """ + export the integrated intensity to parent window's peak processing table + :return: + """ + # collect all scan/pt from table. value including intensity and ROI + intensity_dict = self.ui.tableView_summary.get_peak_intensities() + + # add to table including calculate peak center in Q-space + self.scanIntegratedSignal.emit(intensity_dict) + + return + + # TESTME - 20180727 - Complete it! + def do_export_to_movie(self): + """ + export the complete list of single-pt experiment to a movie + :return: + """ + # find out the directory to save the PNG files for making a move + movie_dir = self._controller.get_working_directory() + roi_name = str(self.ui.comboBox_roiList.currentText()) + direction = str(self.ui.comboBox_integrateDirection.currentText()).lower() + movie_dir = os.path.join(movie_dir, '{}_{}'.format(roi_name, direction)) + os.mkdir(movie_dir) + + # go through each line to plot and save the data + num_rows = self.ui.tableView_summary.rowCount() + file_list_str = '' + for i_row in range(num_rows): + # get run number and set to plot + scan_number = self.ui.tableView_summary.get_scan_number(i_row) + self.ui.lineEdit_Scan.setText('{}'.format(scan_number)) + png_file_name = os.path.join(movie_dir, + 'Scan{0:04d}_ROI{1}_{2}.png'.format(scan_number, roi_name, direction)) + self.do_plot_integrated_pt(show_plot=False, save_plot_to=png_file_name) + file_list_str += '{}\n'.format(png_file_name) + # END-IF + + # write out + png_list_file = open(os.path.join(movie_dir, 'MoviePNGList.txt'), 'w') + png_list_file.write(file_list_str) + png_list_file.close() + + # prompt how to make a movie + command_linux = 'ffmpeg -framerate 8 -pattern_type glob -i "*.png" -r 30 test.mp4' + guiutility.show_message(self, command_linux) + + return + + def do_integrate_detector_counts(self): + """ + sum over the (selected) scan's detector counts by ROI + :return: + """ + # get ROI + roi_name = str(self.ui.comboBox_roiList.currentText()) + if roi_name is None or roi_name == '': + guiutility.show_message('A region-of-interest must be chosen in order to integrate detector counts.') + return + + # integration direction and fit + direction = str(self.ui.comboBox_integrateDirection.currentText()).lower() + fit_gaussian = self.ui.checkBox_fitPeaks.isChecked() + + num_rows = self.ui.tableView_summary.rowCount() + print ('[DB...BAT] Number of rows = {}'.format(num_rows)) + scan_number_list = list() + for row_number in range(num_rows): + # integrate counts on detector + scan_number = self.ui.tableView_summary.get_scan_number(row_number) + scan_number_list.append(scan_number) + # END-FOR + print ('[DB...BAT] Scan numbers: {}'.format(scan_number_list)) + + peak_height_dict = self._controller.integrate_single_pt_scans_detectors_counts(self._exp_number, + scan_number_list, + roi_name, direction, + fit_gaussian) + + # set the value to the row to table + for row_number in range(self.ui.tableView_summary.rowCount()): + scan_number = self.ui.tableView_summary.get_scan_number(row_number) + pt_number = 1 + peak_height = peak_height_dict[scan_number] + self.ui.tableView_summary.set_peak_height(scan_number, pt_number, peak_height, roi_name) + # END-FOR (row_number) + + return + + def do_integrate_single_pt(self): + """ + integrate the 2D data inside region of interest along both axis-0 and axis-1 individually. + and the result (as 1D data) will be saved to ascii file. + the X values will be the corresponding pixel index either along axis-0 or axis-1 + :return: + """ + # get ROI + roi_name = str(self.ui.comboBox_roiList.currentText()) + if roi_name is None or roi_name == '': + guiutility.show_message('A region-of-interest must be chosen in order to integrate detector counts.') + return + + for row_number in range(self.ui.tableView_summary.rowCount()): + # integrate counts on detector + scan_number = self.ui.tableView_summary.get_scan_number(row_number) + pt_number = self.ui.tableView_summary.get_pt_number(row_number) + + # calculate peak intensity + ref_fwhm = self.ui.tableView_summary.get_fwhm(row_number) + + intensity = self._controller.calculate_intensity_single_pt(self._exp_number, scan_number, pt_number, + roi_name, ref_fwhm=ref_fwhm, is_fwhm=False) + + # add to table + self.ui.tableView_summary.set_intensity(scan_number, pt_number, intensity) + # END-FOR + + return + + # TESTME - Load a previously save integrated peaks file + # Question: What kind of peak integrtion table??? Need to find out and well documented! + def do_load_peak_integration_table(self): + """ + load peak integration table CSV file saved from peak integration table + :return: + """ + # get table file name + table_file = str(QFileDialog.getOpenFileName(self, 'Peak Integration Table', self._working_dir)) + if len(table_file) == 0 or os.path.exists(table_file) is False: + return + + # load + status, error_msg = self._controller.load_peak_integration_table(table_file) + if not status: + raise RuntimeError(error_msg) + + return + + def do_plot_integrated_pt(self, show_plot=True, save_plot_to=None): + """ plot integrated Pt with model and raw data + 1. selection include: 2-theta FWHM Model, Summed Single Pt. Counts (horizontal), + Summed Single Pt. Counts (vertical) from comboBox_plotType + :return: + """ + plot_type = str(self.ui.comboBox_plotType.currentText()) + + # reset the canvas + self.ui.graphicsView_integration1DView.clear_all_lines() + + if plot_type == '2-theta FWHM Model': + self.plot_2theta_fwhm_model() + else: + # plot summed single pt scan + self.plot_summed_single_pt_scan_counts(is_vertical_summed=plot_type.lower().count('vertical'), + figure_file=save_plot_to) + + return + + def do_plot_previous_scan(self): + """ plot previous scan if not in 2theta FWHM model + :return: + """ + plot_type = str(self.ui.comboBox_plotType.currentText()) + if plot_type == '2-theta FWHM Model': + return + + scan_number_str = str(self.ui.lineEdit_Scan.text()).strip() + if scan_number_str == '': + return + + scan_number = int(scan_number_str) + row_number = self.ui.tableView_summary.get_row_number_by_scan(scan_number) + if row_number == 0: + row_number = self.ui.tableView_summary.rowCount() - 1 + scan_number = self.ui.tableView_summary.get_scan_number(row_number) + self.ui.lineEdit_Scan.setText(scan_number) + + self.do_plot_integrated_pt() + + return + + def do_plot_next_scan(self): + """ plot next scan if not in 2theta FWHM model + :return: + """ + plot_type = str(self.ui.comboBox_plotType.currentText()) + if plot_type == '2-theta FWHM Model': + return + + scan_number_str = str(self.ui.lineEdit_Scan.text()).strip() + if scan_number_str == '': + return + + scan_number = int(scan_number_str) + row_number = self.ui.tableView_summary.get_row_number_by_scan(scan_number) + if row_number == self.ui.tableView_summary.rowCount() - 1: + row_number = 0 + scan_number = self.ui.tableView_summary.get_scan_number(row_number) + self.ui.lineEdit_Scan.setText(scan_number) + + self.do_plot_integrated_pt() + + return + + def plot_summed_single_pt_scan_counts(self, is_vertical_summed, figure_file=None, pop_error=False): + """ + plot single pt scanned counts + :param is_vertical_summed: + :param figure_file: + :param pop_error: + :return: + """ + # get scan number + scan_num_str = str(self.ui.lineEdit_Scan.text()).strip() + if len(scan_num_str) == 0: + scan_number = self.ui.tableView_summary.get_scan_number(0) + self.ui.lineEdit_Scan.setText('{}'.format(scan_number)) + else: + scan_number = int(scan_num_str) + roi_name = str(self.ui.comboBox_roiList.currentText()) + if is_vertical_summed: + direction = 'vertical' + else: + direction = 'horizontal' + + # get data: pt number is always 1 as it is a single Pt. measurement + model_y = None + if self.ui.checkBox_fitPeaks.isChecked(): + try: + vec_x, model_y = self._controller.get_single_scan_pt_model(self._exp_number, scan_number, + pt_number=1, roi_name=roi_name, + integration_direction=direction) + except RuntimeError as run_err: + err_msg = 'Unable to get single-pt scan model for {} {} {} due to {}' \ + ''.format(self._exp_number, scan_number, roi_name, run_err) + if pop_error: + raise RuntimeError(err_msg) + else: + print (err_msg) + # END-TRY-EXCEPT + # END-IF + + # get original data + vec_x, vec_y = self._controller.get_single_scan_pt_summed(self._exp_number, scan_number, + pt_number=1, roi_name=roi_name, + integration_direction=direction) + + # plot + self.ui.graphicsView_integration1DView.add_observed_data(vec_x, vec_y, label='Summed (raw) counts', + update_plot=False) + if model_y is not None: + self.ui.graphicsView_integration1DView.add_fit_data(vec_x, model_y, label='Gaussian model', + update_plot=True) + + # title + self.ui.graphicsView_integration1DView.set_title('Scan {} Pt {} {} Integration.' + ''.format(scan_number, 1, direction)) + + # save plot? + if figure_file is not None: + self.ui.graphicsView_integration1DView.canvas.save_figure(figure_file) + + return + + def plot_2theta_fwhm_model(self): + """ plot the loaded 2theta-FWHM model + :return: + """ + # TODO - 20180815 - Need to parse the range from self.ui.lineEdit_Scan + # default + two_theta_range = 10, 2.0, 110 # start, resolution, stop + + vec_2theta, vec_fwhm, vec_model = self._controller.get_2theta_fwhm_data(two_theta_range[0], two_theta_range[1], + two_theta_range[2]) + + self.ui.graphicsView_integration1DView.plot_2theta_model(vec_2theta, vec_fwhm, vec_model) + + return + + def do_refresh_roi(self): + """ + refresh ROI list from parent + :return: + """ + roi_list = self._controller.get_region_of_interest_list() + + # add ROI + self._roiMutex = True + + self.ui.comboBox_roiList.clear() + for roi_name in sorted(roi_list): + self.ui.comboBox_roiList.addItem(roi_name) + + self._roiMutex = False + + return + + def do_save_intensity(self): + """ + save intensity to file + :return: + """ + # get output file + out_file_name = str(QFileDialog.getSaveFileName(self, 'File to save integrated intensities', self._working_dir)) + if len(out_file_name) == 0: + return + + self.ui.tableView_summary.save_intensities_to_file(out_file_name) + + return + + def do_retrieve_fwhm(self): + """ + Get FWHM from integrated 'STRONG' peaks according to 2theta value + :return: + """ + row_number = self.ui.tableView_summary.rowCount() + error_messages = '' + for row_index in range(row_number): + # check whether FWHM value is set up + fwhm_i = self.ui.tableView_summary.get_fwhm(row_index) + if fwhm_i is not None and fwhm_i > 1.E-10: + continue + + # use interpolation to curve + two_theta = self.ui.tableView_summary.get_two_theta(row_index) + try: + gauss_sigma = self._controller.calculate_peak_integration_sigma(two_theta) + scan_number = self.ui.tableView_summary.get_scan_number(row_index) + pt_number = 1 + roi_name = self.ui.tableView_summary.get_region_of_interest_name(row_index) + self.ui.tableView_summary.set_gaussian_sigma(row_index, gauss_sigma) + self._controller.set_single_measure_peak_width(self._exp_number, scan_number, pt_number, + roi_name, gauss_sigma, is_fhwm=False) + + except RuntimeError as err: + # error! + error_messages += 'Unable to calculate sigma of row {0} due to {1}\n'.format(row_index, err) + continue + # END-IF-ELSE + + # show error message if necessary + if len(error_messages) > 0: + guiutility.show_message(self, error_messages) + + return + + def menu_load_gauss_sigma_file(self): + """ + load a Gaussian sigma curve for interpolation or matching + :return: + """ + # get the column ascii file name + file_filter = 'Data Files (*.dat);;All Files (*.*)' + twotheta_sigma_file_name = str(QFileDialog.getOpenFileName(self, self._working_dir, + '2theta Gaussian-Sigma File', + file_filter)) + if len(twotheta_sigma_file_name) == 0 or twotheta_sigma_file_name == 'None': + return + + # set the file to controller + try: + vec_x, vec_y = self._controller.import_2theta_gauss_sigma_file(twotheta_sigma_file_name) + self.ui.graphicsView_integration1DView.plot_2theta_model(vec_x, vec_y) + except RuntimeError as run_err: + guiutility.show_message(self, str(run_err)) + return + + return + + def menu_table_select_all(self): + """ + select all rows in table + :return: + """ + self.ui.tableView_summary.select_all_rows(True) + + def menu_table_select_none(self): + """ + de-select all rows in the able + :return: + """ + self.ui.tableView_summary.select_all_rows(False) + + def add_scans(self, scan_pt_list): + """ + add scans' information to table, i.e., add line + :param scan_pt_list: + :return: + """ + # check input + assert isinstance(scan_pt_list, list), 'Scan-Pt-Infos {} must be a list but not a {}.' \ + ''.format(scan_pt_list, type(scan_pt_list)) + + # sort the scans + scan_pt_list = sorted(scan_pt_list) + + for scan_pt_info in scan_pt_list: + scan_number, pt_number, hkl, two_theta = scan_pt_info + self.ui.tableView_summary.add_scan_pt(scan_number, pt_number, hkl, two_theta) + # END-FOR + + return + + def add_scan(self, scan_number, pt_number, hkl_str, two_theta): + """ + add single scan + :param scan_number: + :param pt_number: + :param hkl_str: + :param two_theta: + :return: + """ + self.ui.tableView_summary.add_scan_pt(scan_number, pt_number, hkl_str, two_theta) + + def change_scan_number(self, increment): + """ + change the scan number in the + :param increment: + :return: + """ + # FIXME - 20180809 - This behaviors weird... Need debugging output - TODO + # get the list of scan number from the table, in future, a real-time updated list shall be used. + run_number_list = list() + for irow in range(self.ui.tableView_summary.rowCount()): + run_number_list.append(self.ui.tableView_summary.get_scan_number(irow)) + curr_scan = int(self.ui.lineEdit_Scan.text()) + try: + curr_scan_index = run_number_list.index(curr_scan) + except IndexError: + curr_scan_index = 0 + + next_scan_index = curr_scan_index + increment + next_scan_index = (next_scan_index + len(run_number_list)) % len(run_number_list) + + # set + self.ui.lineEdit_Scan.setText('{}'.format(run_number_list[next_scan_index])) + + return + + def set_experiment(self, exp_number): + """ set experiment number to this window for convenience + :param exp_number: + :return: + """ + assert isinstance(exp_number, int) and exp_number > 0, 'Experiment number {} (of type {} now) must be a ' \ + 'positive integer'.format(exp_number, type(exp_number)) + self._exp_number = exp_number + + return diff --git a/scripts/HFIR_4Circle_Reduction/MainWindow.ui b/scripts/HFIR_4Circle_Reduction/MainWindow.ui index 62dad6cdb3483fa2f1ffe9e9b303182b91921007..f694e931e77a8aa9369f0a901aa58ca0f08c9b67 100644 --- a/scripts/HFIR_4Circle_Reduction/MainWindow.ui +++ b/scripts/HFIR_4Circle_Reduction/MainWindow.ui @@ -96,7 +96,7 @@ <item> <widget class="QPushButton" name="pushButton_setExp"> <property name="text"> - <string>Set</string> + <string>Set!</string> </property> </widget> </item> @@ -219,13 +219,225 @@ <attribute name="title"> <string>Setup && Data Access</string> </attribute> - <layout class="QGridLayout" name="gridLayout_5"> - <item row="5" column="0"> - <widget class="QGroupBox" name="groupBox_11"> + <layout class="QVBoxLayout" name="verticalLayout_25"> + <item> + <widget class="QTextEdit" name="textEdit"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="html"> + <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:14pt; font-weight:600; font-style:italic;">DON'T PANIC! IT IS </span><span style=" font-size:7pt; font-style:italic; vertical-align:sub;">(supposed to be)</span><span style=" font-size:14pt; font-weight:600; font-style:italic;"> EASY TO USE!</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt; font-weight:600;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600;">1. Configure the data reduction</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-style:italic;">(a) Set </span><span style=" font-size:10pt; font-weight:600; font-style:italic;">Experiment </span><span style=" font-size:10pt; font-style:italic;">;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-style:italic;">(b) Set up Data Directory if it is red;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-style:italic;">(c) Set up Working Directory if it is not what you want.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt; font-style:italic;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600;">2. Set experiment and calibration parameters</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-style:italic;">(a) If sample distance is not 0.375 m, set it up;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-style:italic;">(b) If wave length is not standard, set it up;</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-style:italic;">(c) Set up calibrated detector center</span></p></body></html></string> + </property> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_Setup"> + <property name="title"> + <string>File System Setup</string> + </property> + <layout class="QGridLayout" name="gridLayout_5"> + <item row="0" column="4"> + <widget class="QLineEdit" name="lineEdit_workDir"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Directory to save outcome of the data reduction</p></body></html></string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QPushButton" name="pushButton_browseLocalDataDir"> + <property name="text"> + <string>Browse</string> + </property> + </widget> + </item> + <item row="0" column="5"> + <widget class="QPushButton" name="pushButton_browseWorkDir"> + <property name="text"> + <string>Browse</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="lineEdit_preprocessedDir"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Pre-processed Data Directory</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_dir"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>80</width> + <height>0</height> + </size> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Directory for local data storage</p></body></html></string> + </property> + <property name="text"> + <string>Raw Experiment Data Directory</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="lineEdit_localSpiceDir"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Directory for local data storage</p></body></html></string> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="label_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>140</width> + <height>1</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>140</width> + <height>16777215</height> + </size> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Directory to save outcome of the data reduction</p></body></html></string> + </property> + <property name="text"> + <string>Working Direcotry</string> + </property> + </widget> + </item> + <item row="0" column="6"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="pushButton_browsePreprocessed"> + <property name="text"> + <string>Browse</string> + </property> + </widget> + </item> + <item row="1" column="6"> + <widget class="QPushButton" name="pushButton_applySetup"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="toolTip"> + <string><html><head/><body><p><span style=" font-weight:400;">Check directories and apply!</span></p></body></html></string> + </property> + <property name="text"> + <string>Apply File System Setup</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_instrumentCalibration"> <property name="title"> <string>Instrument Calibration</string> </property> <layout class="QGridLayout" name="gridLayout_16"> + <item row="1" column="6"> + <widget class="QPushButton" name="pushButton_applyUserDetCenter"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Apply</string> + </property> + </widget> + </item> + <item row="0" column="8"> + <widget class="QLabel" name="label_36"> + <property name="text"> + <string>Wavelength</string> + </property> + </widget> + </item> + <item row="0" column="10"> + <widget class="QPushButton" name="pushButton_applyUserWavelength"> + <property name="text"> + <string>Apply</string> + </property> + </widget> + </item> + <item row="1" column="10"> + <widget class="QPushButton" name="pushButton_applyDetectorSize"> + <property name="text"> + <string>Apply</string> + </property> + </widget> + </item> <item row="0" column="11"> <spacer name="horizontalSpacer_26"> <property name="orientation"> @@ -318,13 +530,6 @@ </property> </widget> </item> - <item row="0" column="8"> - <widget class="QLabel" name="label_36"> - <property name="text"> - <string>Wavelength</string> - </property> - </widget> - </item> <item row="1" column="4"> <widget class="QLabel" name="label_10"> <property name="text"> @@ -374,16 +579,6 @@ </item> </layout> </item> - <item row="1" column="6"> - <widget class="QPushButton" name="pushButton_applyUserDetCenter"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="text"> - <string>Apply</string> - </property> - </widget> - </item> <item row="0" column="4"> <widget class="QLabel" name="label_19"> <property name="text"> @@ -391,13 +586,6 @@ </property> </widget> </item> - <item row="0" column="10"> - <widget class="QPushButton" name="pushButton_applyUserWavelength"> - <property name="text"> - <string>Apply</string> - </property> - </widget> - </item> <item row="0" column="7"> <spacer name="horizontalSpacer_34"> <property name="orientation"> @@ -444,13 +632,6 @@ </property> </widget> </item> - <item row="1" column="10"> - <widget class="QPushButton" name="pushButton_applyDetectorSize"> - <property name="text"> - <string>Apply</string> - </property> - </widget> - </item> <item row="1" column="9"> <widget class="QComboBox" name="comboBox_detectorSize"> <item> @@ -468,250 +649,24 @@ </layout> </widget> </item> - <item row="2" column="0"> - <widget class="QGroupBox" name="groupBox"> - <property name="title"> - <string>Directories Setup</string> - </property> - <layout class="QGridLayout" name="gridLayout_11"> - <item row="1" column="2"> - <widget class="QPushButton" name="pushButton_browseLocalDataDir"> - <property name="text"> - <string>Browse</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLineEdit" name="lineEdit_localSpiceDir"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="toolTip"> - <string><html><head/><body><p>Directory for local data storage</p></body></html></string> - </property> - </widget> - </item> - <item row="1" column="3"> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Preferred</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_dir"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>80</width> - <height>0</height> - </size> - </property> - <property name="toolTip"> - <string><html><head/><body><p>Directory for local data storage</p></body></html></string> - </property> - <property name="text"> - <string>Data Directory</string> - </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="1" column="6"> - <widget class="QPushButton" name="pushButton_browseWorkDir"> - <property name="text"> - <string>Browse</string> - </property> - </widget> - </item> - <item row="1" column="5"> - <widget class="QLineEdit" name="lineEdit_workDir"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="toolTip"> - <string><html><head/><body><p>Directory to save outcome of the data reduction</p></body></html></string> - </property> - </widget> - </item> - <item row="1" column="4"> - <widget class="QLabel" name="label_2"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>140</width> - <height>1</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>140</width> - <height>16777215</height> - </size> - </property> - <property name="toolTip"> - <string><html><head/><body><p>Directory to save outcome of the data reduction</p></body></html></string> - </property> - <property name="text"> - <string>Working Directory</string> - </property> - </widget> - </item> - <item row="2" column="7"> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Preferred</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Pre-processed Data Directory</string> - </property> - </widget> - </item> - <item row="2" column="8"> - <widget class="QPushButton" name="pushButton_applySetup"> - <property name="font"> - <font> - <weight>75</weight> - <bold>true</bold> - </font> - </property> - <property name="toolTip"> - <string><html><head/><body><p><span style=" font-weight:400;">Check directories and apply!</span></p></body></html></string> - </property> - <property name="text"> - <string>Apply</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLineEdit" name="lineEdit_preprocessedDir"/> - </item> - <item row="2" column="2"> - <widget class="QPushButton" name="pushButton_browsePreprocessed"> - <property name="text"> - <string>Browse</string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QCheckBox" name="checkBox_searchPreprocessedFirst"> - <property name="text"> - <string>Search Pre-processed Scan First</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item row="6" column="0"> - <spacer name="verticalSpacer_2"> - <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="4" column="0"> - <spacer name="verticalSpacer_4"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Preferred</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="0"> - <widget class="QTextEdit" name="textEdit"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - <property name="html"> - <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:14pt; font-weight:600; font-style:italic;">DON'T PANIC! IT IS </span><span style=" font-size:7pt; font-style:italic; vertical-align:sub;">(supposed to be)</span><span style=" font-size:14pt; font-weight:600; font-style:italic;"> EASY TO USE!</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt; font-weight:600;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600;">1. Configure the data reduction</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-style:italic;">(a) Set </span><span style=" font-size:10pt; font-weight:600; font-style:italic;">Experiment </span><span style=" font-size:10pt; font-style:italic;">;</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-style:italic;">(b) Set up Data Directory if it is red;</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-style:italic;">(c) Set up Working Directory if it is not what you want.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt; font-style:italic;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600;">2. Set experiment and calibration parameters</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-style:italic;">(a) If sample distance is not 0.375 m, set it up;</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-style:italic;">(b) If wave length is not standard, set it up;</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-style:italic;">(c) Set up calibrated detector center</span></p></body></html></string> + <item> + <widget class="QPushButton" name="pushButton_applyAllCalibrationSetup"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Apply All Calibration Parameters</string> </property> </widget> </item> - <item row="1" column="0"> - <spacer name="verticalSpacer_32"> + <item> + <spacer name="verticalSpacer_2"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> - <property name="sizeType"> - <enum>QSizePolicy::Preferred</enum> - </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> @@ -832,7 +787,7 @@ p, li { white-space: pre-wrap; } </layout> </item> <item> - <widget class="ScanSurveyTable" name="tableWidget_surveyTable"/> + <widget class="QFrame" name="frame_tableWidget_surveyTable"/> </item> </layout> </item> @@ -846,24 +801,8 @@ p, li { white-space: pre-wrap; } </widget> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_18"> - <item> - <spacer name="horizontalSpacer_32"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Ignored</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> + <layout class="QGridLayout" name="gridLayout_12"> + <item row="0" column="0"> <widget class="QCheckBox" name="checkBox_surveySelectNuclearPeaks"> <property name="font"> <font> @@ -878,25 +817,22 @@ p, li { white-space: pre-wrap; } <bool>true</bool> </property> <property name="text"> - <string> nuclear peaks</string> + <string>Nuclear Peaks</string> </property> </widget> </item> - <item> - <spacer name="horizontalSpacer_33"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Ignored</enum> + <item row="1" column="0"> + <widget class="QCheckBox" name="checkBox_singlePtScans"> + <property name="font"> + <font> + <pointsize>9</pointsize> + <italic>true</italic> + </font> </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> + <property name="text"> + <string>Single Pt Scans</string> </property> - </spacer> + </widget> </item> </layout> </item> @@ -1185,12 +1121,15 @@ p, li { white-space: pre-wrap; } <pointsize>9</pointsize> </font> </property> + <property name="toolTip"> + <string><html><head/><body><p><span style=" font-size:11pt;">Normalized maximum detector counts among all Pts of certain scan.</span></p><p><span style=" font-size:11pt;">It is normalized by collection time.</span></p></body></html></string> + </property> <property name="text"> - <string>Detector Counts</string> + <string>Detector Counts (Rate)</string> </property> </widget> </item> - <item row="3" column="0"> + <item row="4" column="0"> <widget class="QCheckBox" name="checkBox_sortDescending"> <property name="font"> <font> @@ -1214,6 +1153,18 @@ p, li { white-space: pre-wrap; } </property> </widget> </item> + <item row="3" column="0"> + <widget class="QRadioButton" name="radioButton_sortBy2Theta"> + <property name="font"> + <font> + <pointsize>9</pointsize> + </font> + </property> + <property name="text"> + <string>2-theta</string> + </property> + </widget> + </item> </layout> </widget> </item> @@ -1458,7 +1409,7 @@ p, li { white-space: pre-wrap; } <item row="0" column="0"> <layout class="QGridLayout" name="gridLayout_3"> <item row="0" column="0"> - <widget class="Detector2DView" name="graphicsView_detector2dPlot"> + <widget class="QFrame" name="frame_graphicsView_detector2dPlot"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <horstretch>0</horstretch> @@ -1834,6 +1785,21 @@ p, li { white-space: pre-wrap; } </property> </spacer> </item> + <item row="8" column="0"> + <widget class="QPushButton" name="pushButton_exportToMovie"> + <property name="font"> + <font> + <pointsize>9</pointsize> + </font> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Apply the current ROI to all the listed measurements and make into a movie. </p></body></html></string> + </property> + <property name="text"> + <string>Export Movie</string> + </property> + </widget> + </item> </layout> </widget> </item> @@ -1885,7 +1851,7 @@ p, li { white-space: pre-wrap; } <item> <layout class="QVBoxLayout" name="verticalLayout_4"> <item> - <widget class="UBMatrixPeakTable" name="tableWidget_peaksCalUB"> + <widget class="QFrame" name="frame_tableWidget_peaksCalUB"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <horstretch>0</horstretch> @@ -2550,11 +2516,11 @@ p, li { white-space: pre-wrap; } <property name="title"> <string>UB Matrix</string> </property> - <layout class="QGridLayout" name="gridLayout_30"> - <item row="0" column="0"> - <widget class="UBMatrixTable" name="tableWidget_ubMatrix"> + <layout class="QHBoxLayout" name="horizontalLayout_18"> + <item> + <widget class="QFrame" name="frame_tableWidget_ubMatrix"> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> @@ -2576,44 +2542,9 @@ p, li { white-space: pre-wrap; } <pointsize>9</pointsize> </font> </property> - <row> - <property name="text"> - <string/> - </property> - </row> - <row> - <property name="text"> - <string/> - </property> - </row> - <row> - <property name="text"> - <string/> - </property> - </row> - <column> - <property name="text"> - <string/> - </property> - <property name="font"> - <font> - <pointsize>9</pointsize> - </font> - </property> - </column> - <column> - <property name="text"> - <string/> - </property> - </column> - <column> - <property name="text"> - <string/> - </property> - </column> </widget> </item> - <item row="0" column="1"> + <item> <widget class="QPushButton" name="pushButton_acceptUB"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> @@ -2717,6 +2648,22 @@ p, li { white-space: pre-wrap; } </property> </widget> </item> + <item row="3" column="0"> + <spacer name="verticalSpacer_32"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Preferred</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> </layout> </widget> </item> @@ -2742,6 +2689,16 @@ p, li { white-space: pre-wrap; } <string>Refined Lattice Parameters</string> </property> <layout class="QGridLayout" name="gridLayout_29"> + <item row="1" column="3"> + <widget class="QLineEdit" name="lineEdit_cUnitCell"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> <item row="1" column="2"> <widget class="QLineEdit" name="lineEdit_bUnitCell"> <property name="sizePolicy"> @@ -2839,16 +2796,6 @@ p, li { white-space: pre-wrap; } </property> </widget> </item> - <item row="1" column="3"> - <widget class="QLineEdit" name="lineEdit_cUnitCell"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - </widget> - </item> <item row="0" column="2"> <widget class="QLabel" name="label_43"> <property name="sizePolicy"> @@ -2976,6 +2923,22 @@ p, li { white-space: pre-wrap; } </property> </widget> </item> + <item row="3" column="3"> + <spacer name="verticalSpacer_4"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Preferred</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> </layout> </widget> </item> @@ -3005,7 +2968,7 @@ p, li { white-space: pre-wrap; } </widget> </item> <item> - <widget class="UBMatrixTable" name="tableWidget_ubInUse"> + <widget class="QFrame" name="frame_tableWidget_ubInUse"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <horstretch>0</horstretch> @@ -3018,51 +2981,6 @@ p, li { white-space: pre-wrap; } <height>150</height> </size> </property> - <row> - <property name="text"> - <string/> - </property> - </row> - <row> - <property name="text"> - <string/> - </property> - </row> - <row> - <property name="text"> - <string/> - </property> - </row> - <column> - <property name="text"> - <string/> - </property> - </column> - <column> - <property name="text"> - <string/> - </property> - </column> - <column> - <property name="text"> - <string/> - </property> - </column> - <item row="0" column="0"> - <property name="text"> - <string>1</string> - </property> - </item> - <item row="1" column="1"> - <property name="text"> - <string>1</string> - </property> - </item> - <item row="2" column="2"> - <property name="text"> - <string>1</string> - </property> - </item> </widget> </item> <item> @@ -3562,7 +3480,7 @@ p, li { white-space: pre-wrap; } </widget> </item> <item row="3" column="0"> - <widget class="ProcessTableWidget" name="tableWidget_mergeScans"> + <widget class="QFrame" name="frame_tableWidget_mergeScans"> <property name="font"> <font> <pointsize>9</pointsize> @@ -4187,7 +4105,7 @@ p, li { white-space: pre-wrap; } <item row="0" column="7"> <layout class="QHBoxLayout" name="horizontalLayout_23"> <item> - <widget class="QLineEdit" name="lineEdit_numPt4Background"> + <widget class="QLineEdit" name="lineEdit_numPt4BackgroundLeft"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <horstretch>0</horstretch> @@ -4512,7 +4430,7 @@ p, li { white-space: pre-wrap; } </layout> </item> <item row="2" column="3"> - <widget class="IntegratedPeakView" name="graphicsView_integratedPeakView"> + <widget class="QFrame" name="frame_graphicsView_integratedPeakView"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <horstretch>0</horstretch> @@ -4809,6 +4727,22 @@ p, li { white-space: pre-wrap; } </property> </widget> </item> + <item row="3" column="5"> + <spacer name="verticalSpacer_33"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Preferred</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> </layout> </item> <item> @@ -4981,6 +4915,22 @@ p, li { white-space: pre-wrap; } </property> </widget> </item> + <item row="2" column="3"> + <spacer name="verticalSpacer_34"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Preferred</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> </layout> </item> </layout> @@ -5002,7 +4952,7 @@ p, li { white-space: pre-wrap; } </spacer> </item> <item> - <widget class="MatrixTable" name="tableWidget_covariance"> + <widget class="QFrame" name="frame_tableWidget_covariance"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> <horstretch>0</horstretch> @@ -5014,56 +4964,6 @@ p, li { white-space: pre-wrap; } <pointsize>10</pointsize> </font> </property> - <row> - <property name="text"> - <string>X0</string> - </property> - </row> - <row> - <property name="text"> - <string>sigma</string> - </property> - </row> - <row> - <property name="text"> - <string>A</string> - </property> - </row> - <row> - <property name="text"> - <string>B</string> - </property> - </row> - <column> - <property name="text"> - <string>x0</string> - </property> - </column> - <column> - <property name="text"> - <string>sigma</string> - </property> - </column> - <column> - <property name="text"> - <string>A</string> - </property> - </column> - <column> - <property name="text"> - <string>B</string> - </property> - </column> - <item row="0" column="0"> - <property name="text"> - <string/> - </property> - <property name="font"> - <font> - <pointsize>10</pointsize> - </font> - </property> - </item> </widget> </item> <item> @@ -5226,7 +5126,30 @@ p, li { white-space: pre-wrap; } </layout> </item> <item> - <widget class="KShiftTableWidget" name="tableWidget_kShift"/> + <widget class="QFrame" name="frame_tableWidget_kShift"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_35"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Preferred</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> </item> </layout> </widget> @@ -5632,9 +5555,38 @@ p, li { white-space: pre-wrap; } <addaction name="actionPre_Processing"/> <addaction name="actionData_Downloading"/> </widget> + <widget class="QMenu" name="menuPeak_Integration"> + <property name="title"> + <string>Peak Integration</string> + </property> + <widget class="QMenu" name="menuPlots"> + <property name="title"> + <string>Plots</string> + </property> + <addaction name="action2theta_Sigma"/> + <addaction name="actionSave_2theta_Sigma"/> + </widget> + <addaction name="actionSingle_Pt_Integration"/> + <addaction name="menuPlots"/> + </widget> + <widget class="QMenu" name="menuTables"> + <property name="title"> + <string>Tables</string> + </property> + <widget class="QMenu" name="menuSurvey"> + <property name="title"> + <string>Survey</string> + </property> + <addaction name="actionSort_By_2Theta"/> + <addaction name="actionSort_By_Pt"/> + </widget> + <addaction name="menuSurvey"/> + </widget> <addaction name="menuFile"/> <addaction name="menuTools"/> + <addaction name="menuPeak_Integration"/> <addaction name="menuAdmin_Tools"/> + <addaction name="menuTables"/> </widget> <widget class="QStatusBar" name="statusbar"/> <action name="actionNew"> @@ -5755,49 +5707,32 @@ p, li { white-space: pre-wrap; } <string>Download Data</string> </property> </action> + <action name="actionSingle_Pt_Integration"> + <property name="text"> + <string>Single Pt Integration</string> + </property> + </action> + <action name="actionSort_By_2Theta"> + <property name="text"> + <string>Sort By 2Theta</string> + </property> + </action> + <action name="action2theta_Sigma"> + <property name="text"> + <string>2theta-Sigma</string> + </property> + </action> + <action name="actionSave_2theta_Sigma"> + <property name="text"> + <string>Save 2theta-Sigma</string> + </property> + </action> + <action name="actionSort_By_Pt"> + <property name="text"> + <string>Sort By Pt</string> + </property> + </action> </widget> - <customwidgets> - <customwidget> - <class>UBMatrixPeakTable</class> - <extends>QTableWidget</extends> - <header>HFIR_4Circle_Reduction/hfctables.h</header> - </customwidget> - <customwidget> - <class>UBMatrixTable</class> - <extends>QTableWidget</extends> - <header>HFIR_4Circle_Reduction/hfctables.h</header> - </customwidget> - <customwidget> - <class>ProcessTableWidget</class> - <extends>QTableWidget</extends> - <header>HFIR_4Circle_Reduction/hfctables.h</header> - </customwidget> - <customwidget> - <class>ScanSurveyTable</class> - <extends>QTableWidget</extends> - <header>HFIR_4Circle_Reduction/hfctables.h</header> - </customwidget> - <customwidget> - <class>IntegratedPeakView</class> - <extends>QGraphicsView</extends> - <header>HFIR_4Circle_Reduction/integratedpeakview.h</header> - </customwidget> - <customwidget> - <class>Detector2DView</class> - <extends>QGraphicsView</extends> - <header>HFIR_4Circle_Reduction/detector2dview.h</header> - </customwidget> - <customwidget> - <class>KShiftTableWidget</class> - <extends>QTableWidget</extends> - <header>HFIR_4Circle_Reduction/hfctables.h</header> - </customwidget> - <customwidget> - <class>MatrixTable</class> - <extends>QTableWidget</extends> - <header>HFIR_4Circle_Reduction/hfctables.h</header> - </customwidget> - </customwidgets> <resources/> <connections/> </ui> diff --git a/scripts/HFIR_4Circle_Reduction/NTableWidget.py b/scripts/HFIR_4Circle_Reduction/NTableWidget.py index cd5555c839f26d635b92edd2424a64f8ad376e2a..ac88c2deaf8bec7d8510730f5e432724ab41d7d0 100644 --- a/scripts/HFIR_4Circle_Reduction/NTableWidget.py +++ b/scripts/HFIR_4Circle_Reduction/NTableWidget.py @@ -9,20 +9,23 @@ from __future__ import (absolute_import, division, print_function) from six.moves import range import csv -from PyQt4 import QtGui, QtCore +from qtpy.QtWidgets import (QCheckBox, QTableWidget, QTableWidgetItem) # noqa +from qtpy import QtCore # noqa +import qtpy # noqa -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - def _fromUtf8(s): - return s +def _fromUtf8(s): + return s -class NTableWidget(QtGui.QTableWidget): + +class NTableWidget(QTableWidget): """ NdavTableWidget inherits from QTableWidget by extending the features for easy application. """ + # List of supported cell types (all in lower cases) + Supported_Cell_Types = ['checkbox', 'string', 'str', 'integer', 'int', + 'float', 'double'] def __init__(self, parent): """ @@ -30,7 +33,7 @@ class NTableWidget(QtGui.QTableWidget): :param parent: :return: """ - QtGui.QTableWidget.__init__(self, parent) + QTableWidget.__init__(self, parent) self._myParent = parent @@ -45,17 +48,24 @@ class NTableWidget(QtGui.QTableWidget): def append_row(self, row_value_list, type_list=None): """ - - :param row_value_list: - :return: 2-tuple as (boolean, message) + append a row to the table + :param row_value_list: row_value_list + :param type_list: + :return: 2-tuple as (boolean, message) """ # Check input - assert isinstance(row_value_list, list) + assert isinstance(row_value_list, list), 'Row values {0} must be given by a list but ' \ + 'not a {1}'.format(row_value_list, type(row_value_list)) if type_list is not None: - assert isinstance(type_list, list) - assert len(row_value_list) == len(type_list) + assert isinstance(type_list, list), 'Value types {0} must be given by a list but ' \ + 'not a {1}'.format(type_list, type(type_list)) + if len(row_value_list) != len(type_list): + raise RuntimeError('If value types are given, then they must have the same ' + 'numbers ({0}) and values ({1})'.format(len(row_value_list), + len(type_list))) else: type_list = self._myColumnTypeList + if len(row_value_list) != self.columnCount(): ret_msg = 'Input number of values (%d) is different from ' \ 'column number (%d).' % (len(row_value_list), self.columnCount()) @@ -69,7 +79,7 @@ class NTableWidget(QtGui.QTableWidget): # Set values for i_col in range(min(len(row_value_list), self.columnCount())): - item = QtGui.QTableWidgetItem() + item = QTableWidgetItem() if row_value_list[i_col] is None: item_value = '' else: @@ -100,8 +110,8 @@ class NTableWidget(QtGui.QTableWidget): return def export_table_csv(self, csv_file_name): - """ - + """ Export table to a CSV fie + :param csv_file_name: :return: """ # get title as header @@ -129,7 +139,7 @@ class NTableWidget(QtGui.QTableWidget): # END-FOR (row) with open(csv_file_name, 'w') as csv_file: - csv_writer = csv.writer(csv_file, delimiter=' ', quoting=csv.QUOTE_MINIMAL) + csv_writer = csv.writer(csv_file, delimiter='\t', quoting=csv.QUOTE_MINIMAL) # write header csv_writer.writerow(col_names) # write content @@ -150,10 +160,14 @@ class NTableWidget(QtGui.QTableWidget): :return: """ # check - assert isinstance(row_index, int), 'TODO' - assert isinstance(col_index, int), 'TODO' - assert 0 <= row_index < self.rowCount(), 'TODO' - assert 0 <= col_index < self.columnCount(), 'TODO' + assert isinstance(row_index, int), 'Row index {0} must be an integer'.format(row_index) + assert isinstance(col_index, int), 'Column index {0} must be an integer'.format(col_index) + if not 0 <= row_index < self.rowCount(): + raise RuntimeError('Row index {0} is out of range [0, {1})' + ''.format(row_index, self.rowCount())) + if not 0 <= col_index < self.columnCount(): + raise RuntimeError('Column index {0} is out of range [0, {1})' + ''.format(col_index, self.columnCount())) # get cell type cell_data_type = self._myColumnTypeList[col_index] @@ -161,20 +175,36 @@ class NTableWidget(QtGui.QTableWidget): if cell_data_type == 'checkbox': # Check box cell_i_j = self.cellWidget(row_index, col_index) - assert isinstance(cell_i_j, QtGui.QCheckBox) + # PyQt5 compatible issue! + assert isinstance(cell_i_j, QCheckBox), 'Cell {0} {1} must be of type QCheckBox but not a {2}' \ + ''.format(row_index, col_index, type(cell_i_j)) return_value = cell_i_j.isChecked() else: - # Regular cell for int, float and string + # Regular cell for int, float or string item_i_j = self.item(row_index, col_index) - assert isinstance(item_i_j, QtGui.QTableWidgetItem) + assert isinstance(item_i_j, QTableWidgetItem), 'Cell {0} {1} must be of type QTableWidgetItem but not a ' \ + '{2}'.format(row_index, col_index, type(item_i_j)) - return_value = str(item_i_j.text()) - if return_value == 'None': + # get the string of the cell + return_value = str(item_i_j.text()).strip() + + # cast to supported + if return_value == 'None' or len(return_value) == 0: + # None case return_value = None - elif cell_data_type == 'int': - return_value = int(return_value) + elif cell_data_type.startswith('str'): + # case as str of string + pass + elif cell_data_type.startswith('int'): + # integer + try: + return_value = int(return_value) + except ValueError as val_err: + raise RuntimeError('Unable to convert cell ({0}, {1}) with value "{2}" to integer due to {3}.' + ''.format(row_index, col_index, return_value, val_err)) elif cell_data_type == 'float' or cell_data_type == 'double': + # float or double try: return_value = float(return_value) except ValueError as val_err: @@ -211,13 +241,13 @@ class NTableWidget(QtGui.QTableWidget): if c_type == 'checkbox': # Check box cell_i_j = self.cellWidget(row_index, i_col) - assert isinstance(cell_i_j, QtGui.QCheckBox) + assert isinstance(cell_i_j, QCheckBox) is_checked = cell_i_j.isChecked() ret_list.append(is_checked) else: # Regular cell item_i_j = self.item(row_index, i_col) - assert isinstance(item_i_j, QtGui.QTableWidgetItem) + assert isinstance(item_i_j, QTableWidgetItem) value = str(item_i_j.text()).strip() if len(value) > 0: if c_type == 'int': @@ -439,7 +469,7 @@ class NTableWidget(QtGui.QTableWidget): self.cellWidget(row, col).setChecked(state) else: # case to add checkbox - checkbox = QtGui.QCheckBox() + checkbox = QCheckBox() checkbox.setText('') checkbox.setChecked(state) @@ -487,7 +517,7 @@ class NTableWidget(QtGui.QTableWidget): raise IndexError('Input row number or column number is out of range.') # Init cell - cell_item = QtGui.QTableWidgetItem() + cell_item = QTableWidgetItem() cell_item.setText(_fromUtf8(str(value))) cell_item.setFlags(cell_item.flags() & ~QtCore.Qt.ItemIsEditable) @@ -558,14 +588,14 @@ class NTableWidget(QtGui.QTableWidget): if cell_item is not None and cell_widget is None: # TableWidgetItem - assert isinstance(cell_item, QtGui.QTableWidgetItem) + assert isinstance(cell_item, QTableWidgetItem) if isinstance(value, float): cell_item.setText(_fromUtf8('%.7f' % value)) else: cell_item.setText(_fromUtf8(str(value))) elif cell_item is None and cell_widget is not None: # TableCellWidget - if isinstance(cell_widget, QtGui.QCheckBox) is True: + if isinstance(cell_widget, QCheckBox) is True: cell_widget.setChecked(value) else: raise TypeError('Cell of type %s is not supported.' % str(type(cell_item))) diff --git a/scripts/HFIR_4Circle_Reduction/PeakIntegrationSpreadSheet.ui b/scripts/HFIR_4Circle_Reduction/PeakIntegrationSpreadSheet.ui index 422f105e2d6a49e640457a766447dddf177a08e6..1c445d79c24a43c6521acfc8820f7637756d3c29 100644 --- a/scripts/HFIR_4Circle_Reduction/PeakIntegrationSpreadSheet.ui +++ b/scripts/HFIR_4Circle_Reduction/PeakIntegrationSpreadSheet.ui @@ -15,7 +15,7 @@ </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> - <widget class="PeaksIntegrationSpreadSheet" name="tableWidget_spreadsheet"/> + <widget class="QFrame" name="frame_tableWidget_spreadsheet"/> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout"> diff --git a/scripts/HFIR_4Circle_Reduction/PeaksIntegrationReport.py b/scripts/HFIR_4Circle_Reduction/PeaksIntegrationReport.py index ed219906df9e4c73e683a72e8f5dc7b90c6fb058..707a1a2488565ee251355bf5a976870ef03a5ee3 100644 --- a/scripts/HFIR_4Circle_Reduction/PeaksIntegrationReport.py +++ b/scripts/HFIR_4Circle_Reduction/PeaksIntegrationReport.py @@ -7,11 +7,18 @@ from __future__ import (absolute_import, division, print_function) import os -from PyQt4 import QtGui, QtCore -from . import ui_PeakIntegrationSpreadSheet - - -class PeaksIntegrationReportDialog(QtGui.QDialog): +from qtpy.QtWidgets import (QDialog, QFileDialog) # noqa +from mantid.kernel import Logger +try: + from mantidqt.utils.qt import load_ui +except ImportError: + Logger("HFIR_4Circle_Reduction").information('Using legacy ui importer') + from mantidplot import load_ui +from qtpy.QtWidgets import (QVBoxLayout) +from HFIR_4Circle_Reduction.hfctables import PeaksIntegrationSpreadSheet + + +class PeaksIntegrationReportDialog(QDialog): """ Dialog to report the details of peaks integration """ @@ -23,28 +30,34 @@ class PeaksIntegrationReportDialog(QtGui.QDialog): super(PeaksIntegrationReportDialog, self).__init__(parent) # set up UI - self.ui = ui_PeakIntegrationSpreadSheet.Ui_Dialog() - self.ui.setupUi(self) + ui_path = "PeakIntegrationSpreadSheet.ui" + self.ui = load_ui(__file__, ui_path, baseinstance=self) + self._promote_widgets() # initialize widget self.ui.tableWidget_spreadsheet.setup() # set up handlers - self.connect(self.ui.pushButton_exportTable, QtCore.SIGNAL('clicked()'), - self.do_export_table) + self.ui.pushButton_exportTable.clicked.connect(self.do_export_table) + self.ui.pushButton_quit.clicked.connect(self.do_quit) - self.connect(self.ui.pushButton_quit, QtCore.SIGNAL('clicked()'), - self.do_quit) + return + + def _promote_widgets(self): + tableWidget_spreadsheet_layout = QVBoxLayout() + self.ui.frame_tableWidget_spreadsheet.setLayout(tableWidget_spreadsheet_layout) + self.ui.tableWidget_spreadsheet = PeaksIntegrationSpreadSheet(self) + tableWidget_spreadsheet_layout.addWidget(self.ui.tableWidget_spreadsheet) return def do_export_table(self): """ - + export table to a file :return: """ default_dir = os.getcwd() - output_file = str(QtGui.QFileDialog.getSaveFileName(self, 'Export table to csv file', default_dir, + output_file = str(QFileDialog.getSaveFileName(self, 'Export table to csv file', default_dir, 'Data Files (*.dat);;All Files (*.*)')) # return if cancelled diff --git a/scripts/HFIR_4Circle_Reduction/PreprocessWindow.py b/scripts/HFIR_4Circle_Reduction/PreprocessWindow.py index d4482363b372c073c1d13503eecf3b595e454323..a6d0f7bc47057c6de65ff46aa64225b61b8e930c 100644 --- a/scripts/HFIR_4Circle_Reduction/PreprocessWindow.py +++ b/scripts/HFIR_4Circle_Reduction/PreprocessWindow.py @@ -7,15 +7,21 @@ import os import time import csv -from PyQt4 import QtGui, QtCore -import ui_preprocess_window import reduce4circleControl import guiutility as gui_util import HFIR_4Circle_Reduction.fourcircle_utility as fourcircle_utility import NTableWidget +from qtpy.QtWidgets import (QFileDialog, QMainWindow) # noqa +from mantid.kernel import Logger +try: + from mantidqt.utils.qt import load_ui +except ImportError: + Logger("HFIR_4Circle_Reduction").information('Using legacy ui importer') + from mantidplot import load_ui +from qtpy.QtWidgets import (QVBoxLayout) -class ScanPreProcessWindow(QtGui.QMainWindow): +class ScanPreProcessWindow(QMainWindow): """ Main window class to pre-process scans """ @@ -42,8 +48,9 @@ class ScanPreProcessWindow(QtGui.QMainWindow): self._outputDir = None # define UI - self.ui = ui_preprocess_window.Ui_PreprocessWindow() - self.ui.setupUi(self) + ui_path = "preprocess_window.ui" + self.ui = load_ui(__file__, ui_path, baseinstance=self) + self._promote_widgets() # initialize the widgets self.enable_calibration_settings(False) @@ -62,16 +69,19 @@ class ScanPreProcessWindow(QtGui.QMainWindow): self.ui.tableView_scanProcessState.resizeColumnsToContents() # define event handling - self.connect(self.ui.pushButton_browseOutputDir, QtCore.SIGNAL('clicked()'), - self.do_browse_output_dir) - self.connect(self.ui.pushButton_preProcessScan, QtCore.SIGNAL('clicked()'), - self.do_start_pre_process) - self.connect(self.ui.pushButton_changeSettings, QtCore.SIGNAL('clicked()'), - self.do_change_calibration_settings) - self.connect(self.ui.pushButton_fixSettings, QtCore.SIGNAL('clicked()'), - self.do_fix_calibration_settings) - self.connect(self.ui.actionExit, QtCore.SIGNAL('triggered()'), - self.do_quit) + self.ui.pushButton_browseOutputDir.clicked.connect(self.do_browse_output_dir) + self.ui.pushButton_preProcessScan.clicked.connect(self.do_start_pre_process) + self.ui.pushButton_changeSettings.clicked.connect(self.do_change_calibration_settings) + self.ui.pushButton_fixSettings.clicked.connect(self.do_fix_calibration_settings) + self.ui.actionExit.triggered.connect(self.do_quit) + + return + + def _promote_widgets(self): + tableView_scanProcessState_layout = QVBoxLayout() + self.ui.frame_tableView_scanProcessState.setLayout(tableView_scanProcessState_layout) + self.ui.tableView_scanProcessState = ScanPreProcessStatusTable(self) + tableView_scanProcessState_layout.addWidget(self.ui.tableView_scanProcessState) return @@ -100,12 +110,9 @@ class ScanPreProcessWindow(QtGui.QMainWindow): default_dir = os.path.join('/HFIR/HB3A/Exp{0}/shared/'.format(exp_number)) # get output directory - output_dir = str(QtGui.QFileDialog.getExistingDirectory(self, - 'Outputs for pre-processed scans', - default_dir)) + output_dir = str(QFileDialog.getExistingDirectory(self, 'Outputs for pre-processed scans', default_dir)) if output_dir is None or len(output_dir) == 0: return - self.ui.lineEdit_outputDir.setText(output_dir) return @@ -474,10 +481,10 @@ class ScanPreProcessStatusTable(NTableWidget.NTableWidget): """ Extended table widget for scans to process """ - TableSetup = [('Scan', 'int'), - ('Status', 'str'), - ('File', 'str'), - ('Note', 'str')] + Table_Setup = [('Scan', 'int'), + ('Status', 'str'), + ('File', 'str'), + ('Note', 'str')] def __init__(self, parent): """ diff --git a/scripts/HFIR_4Circle_Reduction/SinglePtIntegrationWindow.ui b/scripts/HFIR_4Circle_Reduction/SinglePtIntegrationWindow.ui new file mode 100644 index 0000000000000000000000000000000000000000..0e06ef1a70b95d9306db306c0c025e09ad40f8cf --- /dev/null +++ b/scripts/HFIR_4Circle_Reduction/SinglePtIntegrationWindow.ui @@ -0,0 +1,556 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1531</width> + <height>884</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QGridLayout" name="gridLayout"> + <item row="2" column="1"> + <widget class="QFrame" name="frame_graphicsView_integration1DView"/> + </item> + <item row="3" column="1"> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="3"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Expanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0"> + <widget class="QComboBox" name="comboBox_plotType"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <item> + <property name="text"> + <string>Summed Single Pt. Counts (horizontal)</string> + </property> + </item> + <item> + <property name="text"> + <string>Summed Single Pt. Counts (vertical)</string> + </property> + </item> + <item> + <property name="text"> + <string>2-theta FWHM Model</string> + </property> + </item> + </widget> + </item> + <item row="0" column="2"> + <widget class="QPushButton" name="pushButton_plot"> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>Plot</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="lineEdit_Scan"> + <property name="toolTip"> + <string><html><head/><body><p>Scan number (single Pt.) to plot</p></body></html></string> + </property> + </widget> + </item> + <item row="0" column="4"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QPushButton" name="pushButton_rewindPlot"> + <property name="maximumSize"> + <size> + <width>60</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>|<<</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pushButton_forwardPlot"> + <property name="maximumSize"> + <size> + <width>60</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>>>|</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="4"> + <widget class="QPushButton" name="pushButton_exportToMovie"> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>Export To Movie</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="0"> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>120</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>200</width> + <height>16777215</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>(Gaussian) Integration</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label_3"> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>ROI</string> + </property> + </widget> + </item> + <item row="1" column="5"> + <widget class="QPushButton" name="pushButton_integratePeaks"> + <property name="minimumSize"> + <size> + <width>130</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>100</width> + <height>16777215</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Step 4: Estimate/calculate peak intensities with Gaussian model</p></body></html></string> + </property> + <property name="text"> + <string>Integrate Peaks</string> + </property> + </widget> + </item> + <item row="0" column="7"> + <widget class="QPushButton" name="pushButton_exportIntensityToFile"> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>Export To File</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QComboBox" name="comboBox_roiList"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>140</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>140</width> + <height>160</height> + </size> + </property> + </widget> + </item> + <item row="1" column="6"> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Preferred</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>160</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>160</width> + <height>16777215</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>Sum Single Pt. Counts </string> + </property> + </widget> + </item> + <item row="1" column="4"> + <widget class="QPushButton" name="pushButton_retrieveFWHM"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>140</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>140</width> + <height>160</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Step 3: Use the loaded 2theta-sigma curve to calculate FWHM of the peaks in the table by interpolation</p></body></html></string> + </property> + <property name="text"> + <string>Calculate FWHM</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="pushButton_load2thetaSigmaFile"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>140</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>140</width> + <height>160</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Step 2: Load a (at least) two column file for 2theta - (Gaussian) sigma curve,</p><p>which will be used to calculate/estimate the peak's FWHM for single-measurement peaks</p></body></html></string> + </property> + <property name="text"> + <string>Load FWHM</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Ignored</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="7"> + <widget class="QPushButton" name="pushButton_exportIntensityToTable"> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>Export To Table</string> + </property> + </widget> + </item> + <item row="0" column="4"> + <widget class="QPushButton" name="pushButton_integrteDetectorCounts"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>140</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>140</width> + <height>160</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Step 1: Integrate detector counts by given ROI for all the (selected) scans in the table above</p></body></html></string> + </property> + <property name="text"> + <string>Sum Counts</string> + </property> + </widget> + </item> + <item row="0" column="5"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QComboBox" name="comboBox_integrateDirection"> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="toolTip"> + <string><html><head/><body><p>Counts sum/integration direction.</p><p><br/></p><p>Vertical: detector counts are summed in the vertical direction (along Y-axis). Output will be x vs counts.</p><p>Horizontal: detector counts are summed in the horizontal direction (along X-axis). Output will be y vs counts.</p></body></html></string> + </property> + <item> + <property name="text"> + <string>Horizontal</string> + </property> + </item> + <item> + <property name="text"> + <string>Vertical</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QCheckBox" name="checkBox_fitPeaks"> + <property name="font"> + <font> + <pointsize>9</pointsize> + </font> + </property> + <property name="toolTip"> + <string><html><head/><body><p>This check box is used with two push buttons</p><p><br/></p><p>1. &quot;Sum Counts&quot;: when checked, peaks will be fitted with Gaussian</p><p>2. &quot;Plot&quot; (to the right side): when checked, calculated data (from peak fitting) will be plot agains the original data</p></body></html></string> + </property> + <property name="text"> + <string>Fit</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item row="2" column="0"> + <widget class="QFrame" name="frame_tableView_summary"/> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1531</width> + <height>25</height> + </rect> + </property> + <widget class="QMenu" name="menuQuit"> + <property name="title"> + <string>Quit</string> + </property> + </widget> + <widget class="QMenu" name="menuView"> + <property name="title"> + <string>Table View</string> + </property> + <addaction name="actionSelect_All"/> + <addaction name="actionDe_select_All"/> + </widget> + <widget class="QMenu" name="menuFile"> + <property name="title"> + <string>File</string> + </property> + <addaction name="actionLoad_Gaussian_Sigma_File"/> + <addaction name="actionLoad_Peak_Info_File"/> + </widget> + <widget class="QMenu" name="menuView_2"> + <property name="title"> + <string>View</string> + </property> + <addaction name="actionRefresh_ROI_List"/> + </widget> + <widget class="QMenu" name="menuTools"> + <property name="title"> + <string>Tools</string> + </property> + <addaction name="actionCalculate_FWHM"/> + <addaction name="actionDefine_2theta_FWHM_Function"/> + </widget> + <addaction name="menuFile"/> + <addaction name="menuView_2"/> + <addaction name="menuView"/> + <addaction name="menuTools"/> + <addaction name="menuQuit"/> + </widget> + <widget class="QStatusBar" name="statusbar"/> + <action name="actionSelect_All"> + <property name="text"> + <string>Select All</string> + </property> + </action> + <action name="actionDe_select_All"> + <property name="text"> + <string>De-select All</string> + </property> + </action> + <action name="actionLoad_Gaussian_Sigma_File"> + <property name="text"> + <string>Load Gaussian-Sigma File</string> + </property> + </action> + <action name="actionLoad_Peak_Info_File"> + <property name="text"> + <string>Load Peak Info File</string> + </property> + </action> + <action name="actionRefresh_ROI_List"> + <property name="text"> + <string>Refresh ROI List</string> + </property> + </action> + <action name="actionCalculate_FWHM"> + <property name="text"> + <string>Calculate FWHM</string> + </property> + </action> + <action name="actionDefine_2theta_FWHM_Function"> + <property name="text"> + <string>Define $\2theta$-FWHM Function</string> + </property> + </action> + </widget> + <customwidgets> + <customwidget> + <class>SinglePtIntegrationTable</class> + <extends>QTableView</extends> + <header>HFIR_4Circle_Reduction/hfctables.h</header> + </customwidget> + <customwidget> + <class>SinglePtIntegrationView</class> + <extends>QGraphicsView</extends> + <header>HFIR_4Circle_Reduction/integratedpeakview.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/scripts/HFIR_4Circle_Reduction/View3DWidget.ui b/scripts/HFIR_4Circle_Reduction/View3DWidget.ui index 8a693e4611b26f6f1a68c868be32068e5dc8e7c5..6133b1d9184e7983bfd7b88f8dea6e86f6f5d4c7 100644 --- a/scripts/HFIR_4Circle_Reduction/View3DWidget.ui +++ b/scripts/HFIR_4Circle_Reduction/View3DWidget.ui @@ -18,7 +18,7 @@ <item row="0" column="0"> <layout class="QHBoxLayout" name="horizontalLayout"> <item> - <widget class="MplPlot3dCanvas" name="graphicsView"/> + <widget class="QFrame" name="frame_graphicsView"/> </item> <item> <layout class="QGridLayout" name="gridLayout_2"> diff --git a/scripts/HFIR_4Circle_Reduction/detector2dview.py b/scripts/HFIR_4Circle_Reduction/detector2dview.py index 5ef8eb3f5a63fdd71237bb431b12495561e6dd7a..af6ad2c5de77edf9645ad3202155bedbb4c2e575 100644 --- a/scripts/HFIR_4Circle_Reduction/detector2dview.py +++ b/scripts/HFIR_4Circle_Reduction/detector2dview.py @@ -7,9 +7,9 @@ #pylint: disable=W0403,R0902,R0903,R0904,W0212 from __future__ import (absolute_import, division, print_function) from HFIR_4Circle_Reduction import mpl2dgraphicsview -from PyQt4 import QtCore import numpy as np import os +from qtpy.QtCore import Signal as pyqtSignal class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView): @@ -22,7 +22,7 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView): LEFT = 1 RIGHT = 3 - newROIDefinedSignal = QtCore.pyqtSignal(int, int, int, int) # return coordinate of the + newROIDefinedSignal = pyqtSignal(int, int, int, int) # return coordinate of the def __init__(self, parent): """ @@ -61,25 +61,6 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView): return - # def add_roi(self, roi_start, roi_end): - # """ Add region of interest - # :param roi_start: - # :param roi_end: - # :return: - # """ - # # check - # assert isinstance(roi_start, tuple) and len(roi_start) == 2 - # assert isinstance(roi_end, tuple) and len(roi_end) == 2 - # - # # set - # self._roiStart = roi_start - # self._roiEnd = roi_end - # - # # plot - # self.plot_roi() - # - # return - def clear_canvas(self): """ clear canvas (override base class) @@ -98,17 +79,18 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView): return - def enter_roi_mode(self, state): + def enter_roi_mode(self, roi_state): """ Enter or leave the region of interest (ROI) selection mode :return: """ - assert isinstance(state, bool), 'blabla' + assert isinstance(roi_state, bool), 'ROI mode state {} must be a boolean but not a {}.' \ + ''.format(roi_state, type(roi_state)) # set - self._roiSelectMode = state + self._roiSelectMode = roi_state - if state: + if roi_state: # new in add-ROI mode self.remove_roi() else: @@ -116,11 +98,6 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView): self._roiStart = None self._roiEnd = None - # # reset _myPolygen - # if state is False: - # if self._myPolygon is not None: - # self.remove_roi() - return def integrate_roi_linear(self, exp_number, scan_number, pt_number, output_dir): @@ -212,7 +189,7 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView): return lower_left, upper_right - def plot_detector_counts(self, raw_det_data): + def plot_detector_counts(self, raw_det_data, title=None): """ plot detector counts as 2D plot :param raw_det_data: @@ -225,6 +202,9 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView): count_plot = self.add_plot_2d(raw_det_data, x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max, hold_prev_image=False) + if title is None: + title = 'No Title' + self.set_title(title) if self._myPolygon is not None: print ('[DB...BAT...] Add PATCH') @@ -241,9 +221,8 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView): :return: """ # check - # TODO FIXME - Fill blabla - assert self._roiStart is not None, 'blabla' - assert self._roiEnd is not None, 'blabla' + assert self._roiStart is not None, 'Starting point of region-of-interest cannot be None' + assert self._roiEnd is not None, 'Ending point of region-of-interest cannot be None' # create a vertex list of a rectangular vertex_array = np.ndarray(shape=(4, 2)) diff --git a/scripts/HFIR_4Circle_Reduction/downloaddialog.py b/scripts/HFIR_4Circle_Reduction/downloaddialog.py index 5f78aa16fec7032d71029178adebb103a8bdcb37..4eb4de0f3b2817bce2999e4441b183784faef2c7 100644 --- a/scripts/HFIR_4Circle_Reduction/downloaddialog.py +++ b/scripts/HFIR_4Circle_Reduction/downloaddialog.py @@ -8,20 +8,19 @@ # Dialog to set up HTTP data downloading server and download HB3A data to local ########## import os -from PyQt4 import QtCore -from PyQt4 import QtGui +from qtpy.QtWidgets import (QDialog, QFileDialog, QMessageBox) # noqa import HFIR_4Circle_Reduction.fourcircle_utility as hb3a_util -from HFIR_4Circle_Reduction import ui_httpserversetup as ui_http - +import qtpy # noqa +from mantid.kernel import Logger try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - def _fromUtf8(s): - return s + from mantidqt.utils.qt import load_ui +except ImportError: + Logger("HFIR_4Circle_Reduction").information('Using legacy ui importer') + from mantidplot import load_ui -class DataDownloadDialog(QtGui.QDialog): +class DataDownloadDialog(QDialog): """ dialog for set up HTTP server and download files to local computer This feature will be valid until SNS disables the HTTP server for HFIR data """ @@ -33,29 +32,22 @@ class DataDownloadDialog(QtGui.QDialog): super(DataDownloadDialog, self).__init__(parent) # set up UI - self.ui = ui_http.Ui_Dialog() - self.ui.setupUi(self) + ui_path = "httpserversetup.ui" + self.ui = load_ui(__file__, ui_path, baseinstance=self) # initialize widgets self._init_widgets() # define event handing - self.connect(self.ui.pushButton_testURLs, QtCore.SIGNAL('clicked()'), - self.do_test_url) + self.ui.pushButton_testURLs.clicked.connect(self.do_test_url) - self.connect(self.ui.pushButton_downloadExpData, QtCore.SIGNAL('clicked()'), - self.do_download_spice_data) + self.ui.pushButton_downloadExpData.clicked.connect(self.do_download_spice_data) - self.connect(self.ui.pushButton_ListScans, QtCore.SIGNAL('clicked()'), - self.do_list_scans) + self.ui.pushButton_ListScans.clicked.connect(self.do_list_scans) - self.connect(self.ui.comboBox_mode, QtCore.SIGNAL('currentIndexChanged(int)'), - self.do_change_data_access_mode) + self.ui.comboBox_mode.currentIndexChanged.connect(self.do_change_data_access_mode) - # self.connect(self.ui.pushButton_useDefaultDir, QtCore.SIGNAL('clicked()'), - # self.do_setup_dir_default) - self.connect(self.ui.pushButton_browseLocalCache, QtCore.SIGNAL('clicked()'), - self.do_browse_local_cache_dir) + self.ui.pushButton_browseLocalCache.clicked.connect(self.do_browse_local_cache_dir) # Set the URL red as it is better not check at this stage. Leave it to user self.ui.lineEdit_url.setStyleSheet("color: black;") @@ -86,9 +78,7 @@ class DataDownloadDialog(QtGui.QDialog): """ Browse local cache directory :return: """ - local_cache_dir = str(QtGui.QFileDialog.getExistingDirectory(self, - 'Get Local Cache Directory', - self._homeSrcDir)) + local_cache_dir = str(QFileDialog.getExistingDirectory(self, 'Get Local Cache Directory', self._homeSrcDir)) # Set local directory to control status, error_message = self._myControl.set_local_data_dir(local_cache_dir) @@ -218,7 +208,7 @@ class DataDownloadDialog(QtGui.QDialog): """ assert isinstance(message, str), 'Input message %s must a string but not %s.' \ '' % (str(message), type(message)) - QtGui.QMessageBox.information(self, '4-circle Data Reduction', message) + QMessageBox.information(self, '4-circle Data Reduction', message) return diff --git a/scripts/HFIR_4Circle_Reduction/fourcircle_utility.py b/scripts/HFIR_4Circle_Reduction/fourcircle_utility.py index dd98c6388ee53ef2a0c62923d91197cbd1c790c0..f61045cb499f7af46533f5f2d8010d6daf0355f9 100644 --- a/scripts/HFIR_4Circle_Reduction/fourcircle_utility.py +++ b/scripts/HFIR_4Circle_Reduction/fourcircle_utility.py @@ -442,6 +442,16 @@ def get_spice_file_url(server_url, instrument_name, exp_number, scan_number): return file_url +def get_spice_group_name(exp_number): + """ + get SPICE TableWorkspaces group name + :param exp_number: + :param scan_number: + :return: + """ + return 'HB3A_Exp{0}_SPICES'.format(exp_number) + + def get_spice_table_name(exp_number, scan_number): """ Form the name of the table workspace for SPICE :param exp_number: @@ -764,6 +774,62 @@ def convert_hkl_to_integer(index_h, index_k, index_l, magnetic_tolerance=0.2): return (index_h_r, index_k_r, index_l_r), round_error +def check_dictionary(var_name, var_value): + """ + check whether an input variable is a dictionary. + :param var_name: + :param var_value: + :return: + """ + assert isinstance(var_value, dict), \ + '{0} {1} must be a dictionary but not a {2}'.format(var_name, var_value, type(var_value)) + + +def check_integer(var_name, var_value): + """ + check whether an input variable is an integer. + :except AssertionError: because of wrong type + :param var_name: + :param var_value: + :return: + """ + assert isinstance(var_value, int), \ + '{0} {1} must be an integer but not a {2}'.format(var_name, var_value, type(var_value)) + + +def check_float(var_name, var_value): + """ check whether an input variable is a float (or an integer allowed). + :except AssertionError: because of wrong type + :param var_name: + :param var_value: + :return: + """ + assert isinstance(var_value, int) or isinstance(var_value, float), \ + '{0} {1} must be a float but not a {2}'.format(var_name, var_value, type(var_value)) + + +def check_list(var_name, var_value): + """ + check whether an input variable is a list + :param var_name: + :param var_value: + :return: + """ + assert isinstance(var_value, list), \ + '{0} {1} must be a list but not a {2}'.format(var_name, var_value, type(var_value)) + + +def check_string(var_name, var_value): + """ check whether an input variable is an integer. + :except AssertionError: because of wrong type + :param var_name: + :param var_value: + :return: + """ + assert isinstance(var_value, str), \ + '{0} {1} must be a string but not a {2}'.format(var_name, var_value, type(var_value)) + + def is_peak_nuclear(index_h, index_k, index_l, magnetic_tolerance=0.2): """ Check whether a peak is a nuclear peak by checking its index close enough to integers diff --git a/scripts/HFIR_4Circle_Reduction/general1dviewer.ui b/scripts/HFIR_4Circle_Reduction/general1dviewer.ui new file mode 100644 index 0000000000000000000000000000000000000000..5f945353b439575bd5ba719a783252d1c00b0dcc --- /dev/null +++ b/scripts/HFIR_4Circle_Reduction/general1dviewer.ui @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1092</width> + <height>780</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QFrame" name="frame_graphicsView_plotView"/> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pushButton_exportPlot2File"> + <property name="text"> + <string>Export</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pushButton_quit"> + <property name="text"> + <string>Quit</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1092</width> + <height>22</height> + </rect> + </property> + <widget class="QMenu" name="menuView"> + <property name="title"> + <string>View</string> + </property> + <addaction name="actionReset"/> + </widget> + <addaction name="menuView"/> + </widget> + <widget class="QStatusBar" name="statusbar"/> + <action name="actionReset"> + <property name="text"> + <string>Rest</string> + </property> + </action> + </widget> + <customwidgets> + <customwidget> + <class>GeneralPurposedPlotView</class> + <extends>QGraphicsView</extends> + <header>integratedpeakview.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/scripts/HFIR_4Circle_Reduction/generalplotview.py b/scripts/HFIR_4Circle_Reduction/generalplotview.py new file mode 100644 index 0000000000000000000000000000000000000000..46d23a8bd7f12d8538518eb7e9c6ea360f1a5c98 --- /dev/null +++ b/scripts/HFIR_4Circle_Reduction/generalplotview.py @@ -0,0 +1,119 @@ +import HFIR_4Circle_Reduction.fourcircle_utility as fourcircle_utility +from HFIR_4Circle_Reduction.integratedpeakview import GeneralPurposedPlotView +import os +from qtpy.QtWidgets import QMainWindow, QFileDialog +from mantid.kernel import Logger +try: + from mantidqt.utils.qt import load_ui +except ImportError: + Logger("HFIR_4Circle_Reduction").information('Using legacy ui importer') + from mantidplot import load_ui +from qtpy.QtWidgets import (QVBoxLayout) + + +class GeneralPlotWindow(QMainWindow): + """ + A window for general-purposed graphic (1-D plot) view + """ + def __init__(self, parent): + """ + initialization + :param parent: + """ + super(GeneralPlotWindow, self).__init__(parent) + + # set up UI + ui_path = "general1dviewer.ui" + self.ui = load_ui(__file__, ui_path, baseinstance=self) + self._promote_widgets() + + # set up the event handling + self.ui.pushButton_exportPlot2File.clicked.connect(self.do_export_plot) + self.ui.pushButton_quit.clicked.connect(self.do_quit) + self.ui.actionReset.triggered.connect(self.reset_window) + + # class variables + self._work_dir = os.getcwd() + + return + + def _promote_widgets(self): + graphicsView_plotView_layout = QVBoxLayout() + self.ui.frame_graphicsView_plotView.setLayout(graphicsView_plotView_layout) + self.ui.graphicsView_plotView = GeneralPurposedPlotView(self) + graphicsView_plotView_layout.addWidget(self.ui.graphicsView_plotView) + + return + + def do_export_plot(self): + """ + export plot + :return: + """ + # get directory + file_name = str(QFileDialog.getSaveFileName(self, caption='File to save the plot', + directory=self._work_dir, + filter='Data File(*.dat);;All Files(*.*')) + if len(file_name) == 0: + return + + self.ui.graphicsView_plotView.save_current_plot(None, file_name) + + return + + def do_quit(self): + """ + close the window + :return: + """ + self.close() + + return + + def menu_reset_window(self): + """ + reset the window, i.e., plot + :return: + """ + self.ui.graphicsView_plotView.reset_plots() + + return + + def plot_data(self, vec_x, vec_y, vec_e, x_label, y_label): # '2theta', 'Gaussian-Sigma') + """ + plot data on canvase + :param vec_x: + :param vec_y: + :param vec_e: + :param x_label: + :param y_label: + :return: + """ + self.ui.graphicsView_plotView.plot_data(vec_x, vec_y, vec_e, 'No title', x_label, y_label) + + return + + def set_working_dir(self, work_dir): + """ + :param work_dir: working directory + :return: + """ + # check + fourcircle_utility.check_str('Working directory', work_dir) + if os.path.exists(work_dir) and os.access(work_dir, os.W_OK) is False: + raise RuntimeError('Directory {0} is not writable.'.format(work_dir)) + elif not os.path.exists(work_dir): + raise RuntimeError('Directory {0} does not exist.'.format(work_dir)) + else: + self._work_dir = work_dir + + return + + def reset_window(self): + """ + reset the window to the initial state, such that no plot is made on the canvas + :return: + """ + self.ui.graphicsView_plotView.reset_plots() + + return diff --git a/scripts/HFIR_4Circle_Reduction/guiutility.py b/scripts/HFIR_4Circle_Reduction/guiutility.py index 2d8dd99ed874db8dea4ba730ed9f816192828b53..3912d6f5f9c977023f527b2c5abf81ed26d18fd2 100644 --- a/scripts/HFIR_4Circle_Reduction/guiutility.py +++ b/scripts/HFIR_4Circle_Reduction/guiutility.py @@ -12,7 +12,8 @@ from six.moves import range import math import numpy import os -from PyQt4 import QtGui, QtCore +from qtpy.QtWidgets import (QDialog, QLineEdit, QVBoxLayout, QDialogButtonBox, QLabel, QPlainTextEdit) # noqa +from qtpy import QtCore # noqa def convert_str_to_matrix(matrix_str, matrix_shape): @@ -274,10 +275,10 @@ def parse_float_editors(line_edits, allow_blank=False): # Set flag return_single_value = False - if isinstance(line_edits, QtGui.QLineEdit) is True: + if isinstance(line_edits, QLineEdit): line_edit_list = [line_edits] return_single_value = True - elif isinstance(line_edits, list) is True: + elif isinstance(line_edits, list): line_edit_list = line_edits else: raise RuntimeError('Input is not LineEdit or list of LineEdit.') @@ -286,7 +287,7 @@ def parse_float_editors(line_edits, allow_blank=False): float_list = [] for line_edit in line_edit_list: - assert isinstance(line_edit, QtGui.QLineEdit) + assert isinstance(line_edit, QLineEdit) str_value = str(line_edit.text()).strip() if len(str_value) == 0 and allow_blank: # allow blank and use None @@ -331,10 +332,10 @@ def parse_integers_editors(line_edits, allow_blank=False): # Set flag return_single_value = False - if isinstance(line_edits, QtGui.QLineEdit) is True: + if isinstance(line_edits, QLineEdit): line_edit_list = [line_edits] return_single_value = True - elif isinstance(line_edits, list) is True: + elif isinstance(line_edits, list): line_edit_list = line_edits else: raise RuntimeError('Input is not LineEdit or list of LineEdit.') @@ -343,7 +344,7 @@ def parse_integers_editors(line_edits, allow_blank=False): integer_list = list() for line_edit in line_edit_list: - assert isinstance(line_edit, QtGui.QLineEdit) + assert isinstance(line_edit, QLineEdit) str_value = str(line_edit.text()).strip() if len(str_value) == 0 and allow_blank: # allowed empty string @@ -353,7 +354,9 @@ def parse_integers_editors(line_edits, allow_blank=False): try: int_value = int(str_value) except ValueError as value_err: - error_message += 'Unable to parse a line edit with value %s to integer. %s\n' % (str_value, value_err) + # compose error message + error_message += 'Unable to parse a line edit {0} with value \'{1}\' to an integer due to {2}' \ + ''.format(line_edit.objectName(), str_value, value_err) else: if str_value != '%d' % int_value: error_message += 'Value %s is not a proper integer.\n' % str_value @@ -371,102 +374,126 @@ def parse_integers_editors(line_edits, allow_blank=False): return True, integer_list -class GetValueDialog(QtGui.QDialog): +class GetValueDialog(QDialog): """ A dialog that gets a single value """ - def __init__(self, parent=None): + def __init__(self, parent=None, label_name=''): """ - :param parent: + :param label_name """ super(GetValueDialog, self).__init__(parent) - layout = QtGui.QVBoxLayout(self) + layout = QVBoxLayout(self) - # nice widget for editing the date - self.value_edit = QtGui.QLineEdit(self) - layout.addWidget(self.value_edit) + # details information + self.info_line = QPlainTextEdit(self) + self.info_line.setEnabled(False) + layout.addWidget(self.info_line) - self.setWindowTitle('Workspace Name') + # input + self.label = QLabel(self) + self.value_edit = QLineEdit(self) + layout.addWidget(self.label) + layout.addWidget(self.value_edit) + # END-IF-ELSE + # nice widget for editing the date # self.datetime = QDateTimeEdit(self) # self.datetime.setCalendarPopup(True) # self.datetime.setDateTime(QDateTime.currentDateTime()) # layout.addWidget(self.datetime) # OK and Cancel buttons - buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel, - QtCore.Qt.Horizontal, self) + buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, + QtCore.Qt.Horizontal, self) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) layout.addWidget(buttons) - return + # set some values + self.setWindowTitle('Get user input') + self.label.setText(label_name) - # def accept(self): - # """ - # - # :return: - # """ - # self.close() - # - # def reject(self): - # """ - # - # :return: - # """ - # self.close() + return # get current date and time from the dialog def get_value(self): - """ - + """ get the value in string :return: """ return str(self.value_edit.text()) + def set_message_type(self, message_type): + """ -# static method to create the dialog and return (date, time, accepted) -def get_value(parent=None): - """ Get value from a pop-up dialog - :param parent: - :return: - """ - dialog = GetValueDialog(parent) - result = dialog.exec_() - value = dialog.get_value() + :param message_type: + :return: + """ + if message_type == 'error': + self.value_edit.setStyleSheet("color: rgb(255, 0, 0);") + else: + self.value_edit.setStyleSheet('color: blue') + + return - return value, result == QtGui.QDialog.Accepted + def set_title(self, title): + """ + set window/dialog title + :param title: + :return: + """ + self.setWindowTitle(title) + return -class DisplayDialog(QtGui.QDialog): - def __init__(self, parent=None): + def show_message(self, message): + """ + set or show message + :param message: + :return: """ + self.info_line.setPlainText(message) + + return +# END-DEF-CLASS + +class DisplayDialog(QDialog): + """ + This is a simple dialog display which can be configured by users + """ + def __init__(self, parent=None, name='Test'): + """ init :param parent: """ super(DisplayDialog, self).__init__(parent) - layout = QtGui.QVBoxLayout(self) + layout = QVBoxLayout(self) # nice widget for editing the date - self.message_edit = QtGui.QPlainTextEdit(self) + self.message_edit = QPlainTextEdit(self) self.message_edit.setReadOnly(True) layout.addWidget(self.message_edit) self.setWindowTitle('Merged Scans Workspace Names') # OK and Cancel buttons - buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok, - QtCore.Qt.Horizontal, self) + buttons = QDialogButtonBox(QDialogButtonBox.Ok, QtCore.Qt.Horizontal, self) buttons.accepted.connect(self.accept) layout.addWidget(buttons) + self.name = name + return + def set_name(self, new_name): + + self.name = new_name + def show_message(self, message): """ show message @@ -476,9 +503,49 @@ class DisplayDialog(QtGui.QDialog): self.message_edit.setPlainText(message) return +# END-DEF-CLASS + + +# static method to create the dialog and return (date, time, accepted) +def get_value(parent=None): + """ Get value from a pop-up dialog + :param parent: + :return: + """ + dialog = GetValueDialog(parent) + result = dialog.exec_() + value = dialog.get_value() + + return value, result == QDialog.Accepted + + +def get_value_from_dialog(parent, title, details, label_name='Equation'): + """ + pop a dialog with user-specified message and take a value (string) from the dialog to return + :param title: + :param details: + :param label_name: + :return: + """ + # start dialog + dialog = GetValueDialog(parent, label_name=label_name) + + # set up title + dialog.set_title(title) + dialog.show_message(details) + + # launch and get result + result = dialog.exec_() + print ('Method get_value_from_dialog: returned result is {}'.format(result)) + if result is False: + return None + + str_value = dialog.get_value() + return str_value -def show_message(parent=None, message='show message here!'): + +def show_message(parent=None, message='show message here!', message_type='info'): """ show message :param parent: @@ -486,8 +553,10 @@ def show_message(parent=None, message='show message here!'): :return: True for accepting. False for rejecting or cancelling """ dialog = DisplayDialog(parent) + # dialog.set_name('Teset new name') dialog.show_message(message) - + dialog.set_message_type(message) result = dialog.exec_() + # Dialog object: dialog still exists after exec_() return result diff --git a/scripts/HFIR_4Circle_Reduction/hfctables.py b/scripts/HFIR_4Circle_Reduction/hfctables.py index 897336eb32c70d3adbd23eb2dc0d612e47f38480..09e4551c940d03fe989071b3d9c76b3694325096 100644 --- a/scripts/HFIR_4Circle_Reduction/hfctables.py +++ b/scripts/HFIR_4Circle_Reduction/hfctables.py @@ -11,8 +11,10 @@ import numpy import sys from HFIR_4Circle_Reduction import fourcircle_utility from HFIR_4Circle_Reduction import guiutility - +from qtpy import QtCore # noqa +import math import HFIR_4Circle_Reduction.NTableWidget as tableBase +import os class KShiftTableWidget(tableBase.NTableWidget): @@ -110,6 +112,8 @@ class MatrixTable(tableBase.NTableWidget): assert isinstance(num_rows, int) and num_rows > 0, 'Number of rows larger than 0.' assert isinstance(num_cols, int) and num_cols > 0, 'Number of columns larger than 0.' + self.init_size(4, 4) + # think of reset if self.rowCount() != num_rows or self.columnCount() != num_cols: errmsg = 'Number of rows to set {0} is not equal to current number of rows {1} or ' \ @@ -566,7 +570,7 @@ class UBMatrixTable(tableBase.NTableWidget): Init setup :return: """ - # self.init_size(3, 3) + self.init_size(3, 3) for i in range(3): for j in range(3): @@ -577,22 +581,23 @@ class UBMatrixTable(tableBase.NTableWidget): return -# UB peak information table -UB_Peak_Table_Setup = [('Scan', 'int'), - ('Pt', 'int'), - ('Spice HKL', 'str'), - ('Calculated HKL', 'str'), - ('Q-Sample', 'str'), - ('Selected', 'checkbox'), - ('m1', 'float'), - ('Wavelength', 'float'), # wave length - ('Error', 'float')] - - -class UBMatrixPeakTable(tableBase.NTableWidget): +class ProcessTableWidget(tableBase.NTableWidget): """ - Extended table for peaks used to calculate UB matrix + Extended table for peaks used to process scans including peak integration, scan merging and etc. """ + TableSetup = [('Scan', 'int'), + ('Status', 'str'), + ('Intensity', 'float'), + ('F2', 'float'), # Lorenzian corrected + ('F2 Error', 'float'), + ('Integrate', 'str'), # integration type, Gaussian fit / simple summation + ('Mask', 'str'), # '' for no mask + ('HKL', 'str'), + ('Motor', 'str'), + ('Motor Step', 'str'), + ('Wavelength', 'float'), + ('K-Index', 'int'), + ('Select', 'checkbox')] def __init__(self, parent): """ @@ -602,171 +607,313 @@ class UBMatrixPeakTable(tableBase.NTableWidget): """ tableBase.NTableWidget.__init__(self, parent) - # define class variables - self._cachedSpiceHKL = dict() - - # class variables for column indexes + # some commonly used column index self._colIndexScan = None - self._colIndexSpiceHKL = None - self._colIndexCalculatedHKL = None - self._colIndexQSample = None - self._colIndexWavelength = None - self._colIndexError = None + self._colIndexIntensity = None + self._colIndexCorrInt = None + self._colIndexErrorBar = None + self._colIndexMask = None + self._colIndexIntType = None + self._colIndexHKL = None + self._colIndexStatus = None + self._colIndexPeak = None + # self._colIndexIndexFrom = None + self._colIndexMotor = None + self._colIndexMotorStep = None + self._colIndexWaveLength = None + self._colIndexKIndex = None - return + # cache dictionaries + self._workspaceCacheDict = dict() - def add_peak(self, scan_number, spice_hkl, q_sample, m1, wave_length): - """ + return + @staticmethod + def _generate_empty_row(scan_number, status='In-Queue', ws_name=''): + """ Generate a list for empty row with scan number :param scan_number: - :param spice_hkl: - :param q_sample: - :param m1: - :param wave_length: + :param status: + :param ws_name :return: """ # check inputs - assert isinstance(scan_number, int), 'Scan number integer' - assert len(spice_hkl) == 3, 'Spice HKL' - assert len(q_sample) == 3, 'Q-sample' - assert isinstance(m1, float) or m1 is None, 'm1' - assert isinstance(wave_length, float) or wave_length is None, 'wave length' + assert isinstance(scan_number, int) + assert isinstance(status, str) - # spice_hkl_str = '{0:.4f}, {1:.4f}, {2:.4f}'.format(spice_hkl[0], spice_hkl[1], spice_hkl[2]) - # q_sample_str = '{0:.4f}, {1:.4f}, {2:.4f}'.format(q_sample[0], q_sample[1], q_sample[2]) - spice_hkl_str = self.format_array(spice_hkl) - q_sample_str = self.format_array(q_sample) - self.append_row([scan_number, -1, spice_hkl_str, '', q_sample_str, False, m1, wave_length, '']) + intensity = None + corr_int = None + error = None + mask = '' + integrate_type = 'sum' + motor_name = None + motor_step = None + wave_length = 0 + hkl = '' - return True, '' + new_row = [scan_number, status, intensity, corr_int, error, integrate_type, mask, # peak_center, + hkl, motor_name, motor_step, wave_length, 0, False] - @staticmethod - def format_array(array): + return new_row + + def add_new_merged_data(self, exp_number, scan_number, ws_name): """ - output a formatted array with limited precision of float - :param array: + Append a new row with merged data + :param exp_number: + :param scan_number: + :param ws_name: :return: """ - format_str = '' - for index, number in enumerate(array): - if index > 0: - format_str += ', ' - if isinstance(number, float): - format_str += '{0:.4f}'.format(number) - else: - format_str += '{0}'.format(number) - # END-FOR + # check + assert isinstance(exp_number, int), 'Experiment number {0} must be an integer but not a {1}.' \ + ''.format(exp_number, type(exp_number)) + assert isinstance(scan_number, int), 'Scan number {0} must be an integer but not a {1}.' \ + ''.format(scan_number, type(scan_number)) + assert isinstance(ws_name, str), 'Workspace name {0} must be a string but not a {1}.' \ + ''.format(ws_name, type(ws_name)) - return format_str + # construct a row + new_row = self._generate_empty_row(scan_number, ws_name=ws_name) + self.append_row(new_row) - def get_exp_info(self, row_index): + return + + def add_single_measure_scan(self, scan_number, intensity, roi_name): """ - Get experiment information from a row - :param row_index: - :return: scan number, pt number + add a single measurement peak scan + :param scan_number: + :param intensity: + :param roi_name: + :return: """ - assert isinstance(row_index, int) + # construct a new row + new_row = self._generate_empty_row(scan_number, ws_name='single-pt') + self.append_row(new_row) - scan_number = self.get_cell_value(row_index, 0) - assert isinstance(scan_number, int) - pt_number = self.get_cell_value(row_index, 1) - assert isinstance(pt_number, int) + # set peak intensity + row_number = self.rowCount()-1 + self.set_peak_intensity(row_number=row_number, peak_intensity=intensity, + corrected_intensity=intensity, standard_error=math.sqrt(abs(intensity)), + integrate_method='single-pt') - return scan_number, pt_number + # ROI: use the unused workspace column for this information + self.update_cell_value(row_number, self._colIndexMask, roi_name) - def get_hkl(self, row_index, is_spice_hkl): - """ - Get reflection's miller index - :param row_index: - :param is_spice_hkl: - :return: 3-tuple as H, K, L + return + + def append_scans(self, scans, allow_duplicate_scans): + """ Append rows for merge in future + :param scans: + :param allow_duplicate_scans: does not allow duplicate scan + :return: """ - # check input - assert isinstance(row_index, int), 'Row index {0} must be an integer but not a {1}.' \ - ''.format(row_index, type(row_index)) + # Check + assert isinstance(scans, list) - # get the HKL either parsed from SPICE file or from calculation - if is_spice_hkl: - hkl_str = self.get_cell_value(row_index, self._colIndexSpiceHKL) + if allow_duplicate_scans is False: + scan_list = self.get_scan_list(output_row_number=False) else: - hkl_str = self.get_cell_value(row_index, self._colIndexCalculatedHKL) + scan_list = list() - # convert the recorded string to HKL - status, ret_obj = guiutility.parse_float_array(hkl_str) - if not status: - raise RuntimeError(ret_obj) - elif len(ret_obj) != 3: - raise RuntimeError('Unable to parse array "{0}" to 3 floating points.'.format(hkl_str)) - else: - m_h, m_k, m_l = ret_obj + # set value as default + # Append rows + for scan in scans: + # add a new row for the scan + if allow_duplicate_scans is False and scan in scan_list: + # skip is duplicate scan is not allowed + continue - return m_h, m_k, m_l + # add scans to new row + new_row = self._generate_empty_row(scan_number=scan) + status, err = self.append_row(new_row) + if status is False: + raise RuntimeError(err) - def get_scan_pt(self, row_number): + # set unit + # END-FOR + + return + + def get_integration_type(self, row_index): """ - Get Scan and Pt from a row - :param row_number: + get the peak integration type :return: """ - scan_number = self.get_cell_value(row_number, 0) - pt_number = self.get_cell_value(row_number, 1) + if self.rowCount() == 0: + raise RuntimeError('Empty table!') - return scan_number, pt_number + integrate_type = self.get_cell_value(row_index, self._colIndexIntType) - def get_selected_scans(self): + return integrate_type + + def get_row_by_scan(self, scan_number): """ - get the scan numbers that are selected + get the row number for a gien scan + :param scan_number: :return: """ - selected_rows = self.get_selected_rows(True) + assert isinstance(scan_number, int) and scan_number >= 0,\ + 'Scan number %s (type %s) is invalid. It must be a positive integer.' \ + '' % (str(scan_number), type(scan_number)) + num_rows = self.rowCount() + ret_row_number = None + for i_row in range(num_rows): + tmp_scan_no = self.get_cell_value(i_row, self._colIndexScan) + if scan_number == tmp_scan_no: + ret_row_number = i_row + break + # END-FOR - scan_list = list() - for i_row in selected_rows: - scan_number = self.get_cell_value(i_row, self._colIndexScan) - scan_list.append(scan_number) + if ret_row_number is None: + raise RuntimeError('Scan number %d does not exist in merge-scan-table.' % scan_number) - return scan_list + return ret_row_number - def is_selected(self, row_index): - """ Check whether a row is selected. - :param row_index: + def get_rows_by_state(self, target_state): + """ Get the rows' indexes by status' value (state) + Requirements: target_state is a string + Guarantees: a list of integers as row indexes are returned for all rows with state as target_state + :param target_state: :return: """ - if row_index < 0 or row_index >= self.rowCount(): - raise IndexError('Input row number %d is out of range [0, %d)' % (row_index, self.rowCount())) + # Check + assert isinstance(target_state, str), 'State {0} must be a string but not a {1}.' \ + ''.format(target_state, type(target_state)) - col_index = UB_Peak_Table_Setup.index(('Selected', 'checkbox')) + # Loop around to check + return_list = list() + num_rows = self.rowCount() + for i_row in range(num_rows): + status_i = self.get_cell_value(i_row, self._colIndexStatus) + if status_i == target_state: + return_list.append(i_row) + # END-FOR (i_row) - return self.get_cell_value(row_index, col_index) + return return_list - def setup(self): + def get_hkl(self, row_index): """ - Init setup - :return: + Get peak index, HKL or a row + :param row_index: row index (aka number) + :return: 3-float-tuple or None (not defined) """ - self.init_setup(UB_Peak_Table_Setup) - self.set_status_column_name('Selected') - - # define all the _colIndex - self._colIndexScan = self._myColumnNameList.index('Scan') - self._colIndexSpiceHKL = self._myColumnNameList.index('Spice HKL') - self._colIndexCalculatedHKL = self._myColumnNameList.index('Calculated HKL') - self._colIndexQSample = self._myColumnNameList.index('Q-Sample') - self._colIndexWavelength = self._myColumnNameList.index('Wavelength') - self._colIndexError = self._myColumnNameList.index('Error') + # check input's validity + assert isinstance(row_index, int) and row_index >= 0, 'Row index %s of type %s is not acceptable.' \ + '' % (str(row_index), type(row_index)) - # set up the width of some columns - self.setColumnWidth(self._colIndexSpiceHKL, 240) - self.setColumnWidth(self._colIndexCalculatedHKL, 240) - self.setColumnWidth(4, 240) + # retrieve value of HKL as string and then split them into floats + hkl_str = self.get_cell_value(row_index, self._colIndexHKL) + hkl_str = hkl_str.strip() + if len(hkl_str) == 0: + return None - return + hkl_str_list = hkl_str.split(',') + try: + peak_index_h = float(hkl_str_list[0]) + peak_index_k = float(hkl_str_list[1]) + peak_index_l = float(hkl_str_list[2]) + except IndexError: + raise RuntimeError('Row %d\' HKL value %s is not value.' % (row_index, hkl_str)) + except ValueError: + raise RuntimeError('Row %d\' HKL value %s is not value.' % (row_index, hkl_str)) - def select_nuclear_peak_rows(self, tolerance): + return peak_index_h, peak_index_k, peak_index_l + + def get_mask(self, row_index): + """ + get the mask/ROI name that this integration is based on + :param row_index: + :return: + """ + return self.get_cell_value(row_index, self._colIndexMask) + + def get_merged_status(self, row_number): + """ Get the status whether it is merged + :param row_number: + :return: boolean + """ + # check + assert isinstance(row_number, int) + assert 0 <= row_number < self.rowCount() + + # get value + merge_status_col_index = self._myColumnNameList.index('Status') + status_str = self.get_cell_value(row_number, merge_status_col_index) + + if status_str.lower() == 'done': + return True + + return False + + def get_merged_ws_name(self, i_row): + """ + Get merged workspace name + :param i_row: + :return: + """ + # return self.get_cell_value(i_row, self._colIndexWorkspace) + return self._workspaceCacheDict[i_row] + + def get_roi_name(self, row_index): + """ + get ROI name if it is a single-pt scan + :except RuntimeError: if it is not! + :param row_index: + :return: + """ + integral_type = self.get_integration_type(row_index) + if integral_type != 'single-pt': + raise RuntimeError('Non-single-pt is not applied to get roi name') + + roi_name = self.get_cell_value(row_index, self._colIndexMask) + + return roi_name + + def get_scan_list(self, output_row_number=True): + """ + Get all scans that are already listed in the table. + :param output_row_number: + :return: list of 2-tuple or integer according to value of output_row_number + """ + scan_list = list() + num_rows = self.rowCount() + + for i_row in range(num_rows): + scan_num = self.get_cell_value(i_row, self._colIndexScan) + if output_row_number: + scan_list.append((scan_num, i_row)) + else: + scan_list.append(scan_num) + # END-FOR (i_row) + + return scan_list + + def get_selected_scans(self): + """ Get list of selected scans to merge from table + :return: list of 2-tuples (scan number, row number) + """ + scan_list = list() + num_rows = self.rowCount() + col_select_index = self._myColumnNameList.index('Select') + + for i_row in range(num_rows): + if self.get_cell_value(i_row, col_select_index) is True: + scan_num = self.get_cell_value(i_row, self._colIndexScan) + scan_list.append((scan_num, i_row)) + + return scan_list + + def get_scan_number(self, row_number): + """ Get scan number of a row + Guarantees: get scan number of a row + :param row_number: + :return: + """ + return self.get_cell_value(row_number, self._colIndexScan) + + def select_all_nuclear_peaks(self): """ select all nuclear peaks, i.e., set the flag on on 'select' for all rows if their HKL indicates that they are nuclear peaks - :param tolerance: :return: string as error message """ num_rows = self.rowCount() @@ -775,856 +922,1068 @@ class UBMatrixPeakTable(tableBase.NTableWidget): for row_index in range(num_rows): # get the reading of HKL try: - hkl_tuple = self.get_hkl(row_index, is_spice_hkl=True) - if fourcircle_utility.is_peak_nuclear(hkl_tuple[0], hkl_tuple[1], hkl_tuple[2], tolerance): - self.select_row(row_index, status=True) + hkl_tuple = self.get_hkl(row_index) + if hkl_tuple is None: + error_message += 'Row %d has no HKL showed.' % row_index + continue + if fourcircle_utility.is_peak_nuclear(hkl_tuple[0], hkl_tuple[1], hkl_tuple[2]): + self.select_row(row_index) except RuntimeError as error: error_message += 'Unable to parse HKL of line %d due to %s.' % (row_index, str(error)) # END-FOR return error_message - def select_scans(self, select_all=False, nuclear_peaks=False, hkl_tolerance=None, - wave_length=None, wave_length_tolerance=None): - """ - select scans in the UB matrix table - :param select_all: - :param nuclear_peaks: - :param hkl_tolerance: - :param wave_length: - :param wave_length_tolerance: + def set_hkl(self, row_number, hkl, hkl_source=None): + """ Set Miller index HKL to a row + :param row_number: row number + :param hkl: + :param hkl_source: :return: """ - if select_all: - # select all - self.select_all_rows(True) + # check + assert isinstance(row_number, int) and 0 <= row_number < self.rowCount(),\ + 'Row number %s is out of range.' % str(row_number) + assert len(hkl) == 3, 'HKL must be a sequence with 3 items but not %s.' % len(hkl) - elif nuclear_peaks or wave_length_tolerance is not None: - # using filters - if nuclear_peaks: - self.select_nuclear_peak_rows(hkl_tolerance) - if wave_length_tolerance is not None: - self.select_rows_by_column_value(self._colIndexWavelength, wave_length, wave_length_tolerance, - keep_current_selection=True) - else: - raise RuntimeError('Must pick up one option to do filter.') + # update the cell + hkl_str = '%.3f, %.3f, %.3f' % (hkl[0], hkl[1], hkl[2]) + self.update_cell_value(row_number, self._colIndexHKL, hkl_str) return - def set_hkl(self, i_row, hkl, is_spice_hkl, error=None): - """ - Set HKL to a row in the table. Show H/K/L with 4 decimal points - :param i_row: - :param hkl: HKL is a list of tuple - :param is_spice_hkl: If true, then set input to cell for SPICE-imported HKL. Otherwise to calculated HKL. - :param error: error of HKL + def set_k_shift_index(self, row_number, k_index): + """ Set k-shift index to a row + :param row_number: + :param k_index: + :return: """ - # Check - assert isinstance(i_row, int), 'Row number (index) must be integer but not %s.'.format(type(i_row)) - - if isinstance(hkl, list) or isinstance(hkl, tuple): - assert len(hkl) == 3, 'In case HKL is list of tuple, its size must be equal to 3 but not %d.' \ - '' % len(hkl) - elif isinstance(hkl, numpy.ndarray): - assert hkl.shape == (3,), 'In case HKL is numpy array, its shape must be (3,) but not %s.' \ - '' % str(hkl.shape) - else: - raise AssertionError('HKL of type %s is not supported. Supported types include list, tuple ' - 'and numpy array.' % type(hkl)) - assert isinstance(is_spice_hkl, bool), 'Flag {0} for SPICE-HKL must be a boolean but not a {1}.' \ - ''.format(is_spice_hkl, type(is_spice_hkl)) - - # convert to a string with 4 decimal points - hkl_str = '%.4f, %.4f, %.4f' % (hkl[0], hkl[1], hkl[2]) - - if is_spice_hkl: - self.update_cell_value(i_row, self._colIndexSpiceHKL, hkl_str) - else: - self.update_cell_value(i_row, self._colIndexCalculatedHKL, hkl_str) + assert isinstance(k_index, int) - # set error - if error is not None: - i_col_error = UB_Peak_Table_Setup.index(('Error', 'float')) - self.update_cell_value(i_row, i_col_error, error) + self.update_cell_value(row_number, self._colIndexKIndex, k_index) return - def restore_cached_indexing(self, is_spice=True): + def set_motor_info(self, row_number, motor_move_tup): """ - Restore the previously saved value to HKL + Set the motor step information to the 'Motor' cell + :param row_number: + :param motor_move_tup: :return: """ - # check first such that all the stored value are to be - stored_line_index = sorted(self._cachedSpiceHKL.keys()) - assert len(stored_line_index) == self.rowCount(), 'The current rows and cached row counts do not match.' + # check + assert isinstance(row_number, int) and 0 <= row_number < self.rowCount(), 'Input row number is out of range.' + assert len(motor_move_tup) == 3 - # restore - for row_index in stored_line_index: - hkl = self._cachedSpiceHKL[row_index] - self.set_hkl(row_index, hkl, is_spice_hkl=is_spice) - # END-FOR + # get motor information and construct the string + motor_name = motor_move_tup[0] + motor_move = '%.3f (%.2E)' % (motor_move_tup[1], motor_move_tup[2]) - # clear - self._cachedSpiceHKL.clear() + # set motor step information string to the table cell. + self.update_cell_value(row_number, self._colIndexMotor, motor_name) + self.update_cell_value(row_number, self._colIndexMotorStep, motor_move) return - def store_current_indexing(self): + def set_peak_centre(self, row_number, peak_centre): """ - Store the current indexing for reverting + set peak centre value + :param row_number: + :param peak_centre: :return: """ - # clear the previous value - self._cachedSpiceHKL.clear() - - # store - num_rows = self.rowCount() - for row_index in range(num_rows): - peak_indexing = self.get_hkl(row_index, is_spice_hkl=True) - self._cachedSpiceHKL[row_index] = peak_indexing - # END-FOR - - return + # check input's validity + assert isinstance(row_number, int) and 0 <= row_number < self.rowCount(), \ + 'Row number %s is not supported or out of boundary.' % str(row_number) + assert isinstance(peak_centre, str) or len(peak_centre) == 3,\ + 'Peak centre %s must be a string or a container with size 3.' % str(peak_centre) - def update_hkl(self, i_row, h, k, l): - """ Update HKL value - :param i_row: index of the row to have HKL updated - :param h: - :param k: - :param l: - """ - assert isinstance(i_row, int), 'row number {0} must be an integer but not a {1}.' \ - ''.format(i_row, type(i_row)) + # set value of peak center + if isinstance(peak_centre, str): + # string no need to change + value_to_set = peak_centre + else: + # construct the value + value_to_set = '%.3f, %.3f, %.3f' % (peak_centre[0], peak_centre[1], peak_centre[2]) - self.update_cell_value(i_row, self._colIndexCalculatedHKL, self.format_array([h, k, l])) + self.update_cell_value(row_number, self._colIndexPeak, value_to_set) return + def set_peak_intensity(self, row_number, peak_intensity, corrected_intensity, standard_error, integrate_method): + """ + Set peak intensity to a row in the table + Guarantees: peak intensity is set + :param row_number: + :param peak_intensity: + :param corrected_intensity: + :param standard_error: + :param integrate_method: must be '', simple or gaussian for simple counts summation or Gaussian fit, respectively + :return: + """ + # check requirements + assert isinstance(peak_intensity, float), 'Peak intensity must be a float.' + assert isinstance(integrate_method, str), 'Integrated method {0} must be a string but not {1}.' \ + ''.format(integrate_method, type(integrate_method)) + if integrate_method not in ['', 'simple', 'mixed', 'gaussian', 'single-pt']: + raise RuntimeError('Peak integration {0} not in list. Method must be in ["" (Not defined), "simple"' + ', "gaussian"]'.format(integrate_method)) -class ProcessTableWidget(tableBase.NTableWidget): - """ - Extended table for peaks used to process scans including peak integration, scan merging and etc. - """ - TableSetup = [('Scan', 'int'), - ('Status', 'str'), - ('Intensity', 'float'), - ('F2', 'float'), # Lorenzian corrected - ('F2 Error', 'float'), - ('Integrate', 'str'), # integration type, Gaussian fit / simple summation - ('Mask', 'str'), # '' for no mask - ('HKL', 'str'), - ('Motor', 'str'), - ('Motor Step', 'str'), - ('Wavelength', 'float'), - ('K-Index', 'int'), - ('Select', 'checkbox')] - + self.update_cell_value(row_number, self._colIndexIntensity, peak_intensity) + self.update_cell_value(row_number, self._colIndexIntType, integrate_method) + self.update_cell_value(row_number, self._colIndexCorrInt, corrected_intensity) + self.update_cell_value(row_number, self._colIndexErrorBar, standard_error) + + return + + def set_status(self, row_number, status): + """ + Set the status for merging scan to QTable + :param row_number: scan number + :param status: + :return: + """ + # Check + assert isinstance(status, str), 'Status (%s) must be a string, but not %s.' % (str(status), type(status)) + + return self.update_cell_value(row_number, self._colIndexStatus, status) + + def set_wave_length(self, row_number, wave_length): + """ Set wave length to a row + :param row_number: + :param wave_length: + :return: + """ + # check + assert isinstance(row_number, int) and 0 <= row_number < self.rowCount(), 'Input row number is out of range.' + assert isinstance(wave_length, float) and wave_length >= 0. + + # set + col_index = self.TableSetup.index(('Wavelength', 'float')) + self.update_cell_value(row_number, col_index, wave_length) + + return + + def set_ws_name(self, row_number, merged_md_name): + """ + Set the output workspace and workspace group's names to QTable + :param row_number: + :param merged_md_name: + :return: + """ + # Check + assert isinstance(merged_md_name, str), 'Merged MDWorkspace name must be a string.' + + # self.update_cell_value(row_number, self._colIndexWorkspace, merged_md_name) + + self._workspaceCacheDict[row_number] = merged_md_name + + return + + def setup(self): + """ + Init setup + :return: + """ + self.init_setup(self.TableSetup) + self._statusColName = 'Select' + + # set up column index + self._colIndexScan = ProcessTableWidget.TableSetup.index(('Scan', 'int')) + self._colIndexIntensity = self.TableSetup.index(('Intensity', 'float')) + self._colIndexCorrInt = self.TableSetup.index(('F2', 'float')) + self._colIndexErrorBar = self.TableSetup.index(('F2 Error', 'float')) + self._colIndexMask = self.TableSetup.index(('Mask', 'str')) + self._colIndexIntType = self.TableSetup.index(('Integrate', 'str')) + self._colIndexStatus = self.TableSetup.index(('Status', 'str')) + self._colIndexHKL = ProcessTableWidget.TableSetup.index(('HKL', 'str')) + # self._colIndexPeak = self.TableSetup.index(('Peak', 'str')) + # self._colIndexIndexFrom = self.TableSetup.index(('Index From', 'str')) + self._colIndexMotor = ProcessTableWidget.TableSetup.index(('Motor', 'str')) + self._colIndexMotorStep = ProcessTableWidget.TableSetup.index(('Motor Step', 'str')) + self._colIndexWaveLength = self.TableSetup.index(('Wavelength', 'float')) + self._colIndexKIndex = self.TableSetup.index(('K-Index', 'int')) + # self._colIndexWorkspace = self.TableSetup.index(('Workspace', 'str')) + + return + + +class ScanSurveyTable(tableBase.NTableWidget): """ - in scans processing tab, the column name of corrected will be changed to 'F2;'Error' will be modified to 'F2 Error' -In survey, 'Max Counts' shall be normalized by counting time of that 'Pt'. + Extended table widget for peak integration """ + Table_Setup = [('Scan', 'int'), + ('Max Counts Pt', 'int'), + ('Max Counts', 'float'), + ('H', 'float'), + ('K', 'float'), + ('L', 'float'), + ('Q-range', 'float'), + ('Sample Temp', 'float'), + ('2theta', 'float'), + ('Selected', 'checkbox')] def __init__(self, parent): """ - Initialization :param parent: - :return: """ tableBase.NTableWidget.__init__(self, parent) - # some commonly used column index - self._colIndexScan = None - self._colIndexIntensity = None - self._colIndexCorrInt = None - self._colIndexErrorBar = None - self._colIndexMask = None - self._colIndexIntType = None - self._colIndexHKL = None - self._colIndexStatus = None - self._colIndexPeak = None - # self._colIndexIndexFrom = None - self._colIndexMotor = None - self._colIndexMotorStep = None - self._colIndexWaveLength = None - self._colIndexKIndex = None - self._colIndexWorkspace = None + self._myScanSummaryList = list() - # cache dictionaries - self._workspaceCacheDict = dict() + self._currStartScan = 0 + self._currEndScan = sys.maxsize + self._currMinCounts = 0. + self._currMaxCounts = sys.float_info.max + + self._colIndexH = None + self._colIndexK = None + self._colIndexL = None + + self._colIndex2Theta = None return - @staticmethod - def _generate_empty_row(scan_number, status='In-Queue', ws_name=''): - """ Generate a list for empty row with scan number - :param scan_number: - :param status: - :param ws_name + def filter_and_sort(self, start_scan, end_scan, min_counts, max_counts, + sort_by_column, sort_order): + """ + Filter the survey table and sort + Note: it might not be efficient here because the table will be refreshed twice + :param start_scan: + :param end_scan: + :param min_counts: + :param max_counts: + :param sort_by_column: + :param sort_order: 0 for ascending, 1 for descending :return: """ - # check inputs - assert isinstance(scan_number, int) - assert isinstance(status, str) + # check + assert isinstance(start_scan, int) and isinstance(end_scan, int) and end_scan >= start_scan + assert isinstance(min_counts, float) and isinstance(max_counts, float) and min_counts < max_counts + assert isinstance(sort_by_column, str), \ + 'sort_by_column requires a string but not %s.' % str(type(sort_by_column)) + assert isinstance(sort_order, int), \ + 'sort_order requires an integer but not %s.' % str(type(sort_order)) - intensity = None - corr_int = None - error = None - mask = '' - integrate_type = 'sum' - motor_name = None - motor_step = None - wave_length = 0 - hkl = '' + # get column index to sort + col_index = self.get_column_index(column_name=sort_by_column) - new_row = [scan_number, status, intensity, corr_int, error, integrate_type, mask, # peak_center, - hkl, motor_name, motor_step, wave_length, 0, False] + # filter on the back end row contents list first + self.filter_rows(start_scan, end_scan, min_counts, max_counts) - return new_row + # order + self.sort_by_column(col_index, sort_order) - def add_new_merged_data(self, exp_number, scan_number, ws_name): + return + + def filter_rows(self, start_scan, end_scan, min_counts, max_counts): """ - Append a new row with merged data - :param exp_number: - :param scan_number: - :param ws_name: + Filter by scan number, detector counts on self._myScanSummaryList + and reset the table via the latest result + :param start_scan: + :param end_scan: + :param min_counts: + :param max_counts: :return: """ - # check - assert isinstance(exp_number, int), 'Experiment number {0} must be an integer but not a {1}.' \ - ''.format(exp_number, type(exp_number)) - assert isinstance(scan_number, int), 'Scan number {0} must be an integer but not a {1}.' \ - ''.format(scan_number, type(scan_number)) - assert isinstance(ws_name, str), 'Workspace name {0} must be a string but not a {1}.' \ - ''.format(ws_name, type(ws_name)) + # check whether it can be skipped + if start_scan == self._currStartScan and end_scan == self._currEndScan \ + and min_counts == self._currMinCounts and max_counts == self._currMaxCounts: + # same filter set up, return + return - # construct a row - new_row = self._generate_empty_row(scan_number, ws_name=ws_name) - self.append_row(new_row) + # clear the table + self.remove_all_rows() + + # go through all rows in the original list and then reconstruct + for index in range(len(self._myScanSummaryList)): + sum_item = self._myScanSummaryList[index] + # check + assert isinstance(sum_item, list) + assert len(sum_item) == len(self._myColumnNameList) - 1 + # check with filters: original order is counts, scan, Pt., ... + scan_number = sum_item[1] + if scan_number < start_scan or scan_number > end_scan: + continue + counts = sum_item[0] + if counts < min_counts or counts > max_counts: + continue + + # modify for appending to table + row_items = sum_item[:] + counts = row_items.pop(0) + row_items.insert(2, counts) + row_items.append(False) + + # append to table + self.append_row(row_items) + # END-FOR (index) + + # Update + self._currStartScan = start_scan + self._currEndScan = end_scan + self._currMinCounts = min_counts + self._currMaxCounts = max_counts return - def append_scans(self, scans, allow_duplicate_scans): - """ Append rows for merge in future - :param scans: - :param allow_duplicate_scans: does not allow duplicate scan + def get_hkl(self, row_index): + """ + Get peak index (HKL) from survey table (i.e., SPICE file) + :param row_index: :return: """ - # Check - assert isinstance(scans, list) + index_h = self.get_cell_value(row_index, self._colIndexH) + index_k = self.get_cell_value(row_index, self._colIndexK) + index_l = self.get_cell_value(row_index, self._colIndexL) - if allow_duplicate_scans is False: - scan_list = self.get_scan_list(output_row_number=False) - else: - scan_list = list() + return index_h, index_k, index_l - # set value as default - # Append rows - for scan in scans: - # add a new row for the scan - if allow_duplicate_scans is False and scan in scan_list: - # skip is duplicate scan is not allowed - continue + def get_scan_numbers(self, row_index_list): + """ + Get scan numbers with specified rows + :param row_index_list: + :return: + """ + scan_list = list() + scan_col_index = self.Table_Setup.index(('Scan', 'int')) + for row_index in row_index_list: + scan_number_i = self.get_cell_value(row_index, scan_col_index) + scan_list.append(scan_number_i) + scan_list.sort() + + return scan_list + + def get_selected_scan_pt(self): + """ + get selected row's scan number and pt number + :return: + """ + selected_row_list = self.get_selected_rows() + + selected_scan_pt_list = list() + for row_number in selected_row_list: + scan_number = self.get_cell_value(row_number, 0) + pt_number = self.get_cell_value(row_number, 1) + selected_scan_pt_list.append((scan_number, pt_number)) + # END-FOR + + return selected_scan_pt_list + + def get_selected_run_surveyed(self, required_size=1): + """ + Purpose: Get selected pt number and run number that is set as selected + Requirements: there must be one and only one run that is selected + Guarantees: a 2-tuple for integer for return as scan number and Pt. number + :param required_size: if specified as an integer, then if the number of selected rows is different, + an exception will be thrown. + :return: a 2-tuple of integer if required size is 1 (as old implementation) or a list of 2-tuple of integer + """ + # check required size? + assert isinstance(required_size, int) or required_size is None, 'Required number of runs {0} must be None ' \ + 'or an integer but not a {1}.' \ + ''.format(required_size, type(required_size)) + + # get the selected row indexes and check + row_index_list = self.get_selected_rows(True) + + if required_size is not None and required_size != len(row_index_list): + raise RuntimeError('It is required to have {0} runs selected, but now there are {1} runs that are ' + 'selected.'.format(required_size, row_index_list)) + + # get all the scans and rows that are selected + scan_run_list = list() + for i_row in row_index_list: + # get scan and pt. + scan_number = self.get_cell_value(i_row, 0) + pt_number = self.get_cell_value(i_row, 1) + scan_run_list.append((scan_number, pt_number)) + + # special case for only 1 run that is selected + if len(row_index_list) == 1 and required_size is not None: + # get scan and pt + return scan_run_list[0] + # END-IF + + return scan_run_list - # add scans to new row - new_row = self._generate_empty_row(scan_number=scan) - status, err = self.append_row(new_row) - if status is False: - raise RuntimeError(err) + def show_reflections(self, num_rows): + """ + :param num_rows: + :return: + """ + assert isinstance(num_rows, int) + assert num_rows > 0 + assert len(self._myScanSummaryList) > 0 - # set unit + for i_ref in range(min(num_rows, len(self._myScanSummaryList))): + # get counts + scan_summary = self._myScanSummaryList[i_ref] + # check + assert isinstance(scan_summary, list) + assert len(scan_summary) == len(self._myColumnNameList) - 1 + # modify for appending to table + row_items = scan_summary[:] + max_count = row_items.pop(0) + row_items.insert(2, max_count) + row_items.append(False) + # append + self.append_row(row_items) # END-FOR return - def get_integration_type(self): + def set_survey_result(self, scan_summary_list): """ - get the peak integration type + + :param scan_summary_list: :return: """ - if self.rowCount() == 0: - raise RuntimeError('Empty table!') + # check + assert isinstance(scan_summary_list, list) - integrate_type = self.get_cell_value(0, self._colIndexIntType) + # Sort and set to class variable + scan_summary_list.sort(reverse=True) + self._myScanSummaryList = scan_summary_list - return integrate_type + return - def get_row_by_scan(self, scan_number): + def setup(self): """ - get the row number for a gien scan - :param scan_number: + Init setup :return: """ - assert isinstance(scan_number, int) and scan_number >= 0,\ - 'Scan number %s (type %s) is invalid. It must be a positive integer.' \ - '' % (str(scan_number), type(scan_number)) - num_rows = self.rowCount() - ret_row_number = None - for i_row in range(num_rows): - tmp_scan_no = self.get_cell_value(i_row, self._colIndexScan) - if scan_number == tmp_scan_no: - ret_row_number = i_row - break - # END-FOR + self.init_setup(ScanSurveyTable.Table_Setup) + self.set_status_column_name('Selected') - if ret_row_number is None: - raise RuntimeError('Scan number %d does not exist in merge-scan-table.' % scan_number) + self._colIndexH = ScanSurveyTable.Table_Setup.index(('H', 'float')) + self._colIndexK = ScanSurveyTable.Table_Setup.index(('K', 'float')) + self._colIndexL = ScanSurveyTable.Table_Setup.index(('L', 'float')) - return ret_row_number + self._colIndex2Theta = ScanSurveyTable.Table_Setup.index(('2theta', 'float')) - def get_rows_by_state(self, target_state): - """ Get the rows' indexes by status' value (state) - Requirements: target_state is a string - Guarantees: a list of integers as row indexes are returned for all rows with state as target_state - :param target_state: + return + + def reset(self): + """ Reset the inner survey summary table :return: """ - # Check - assert isinstance(target_state, str), 'State {0} must be a string but not a {1}.' \ - ''.format(target_state, type(target_state)) + self._myScanSummaryList = list() - # Loop around to check - return_list = list() - num_rows = self.rowCount() - for i_row in range(num_rows): - status_i = self.get_cell_value(i_row, self._colIndexStatus) - if status_i == target_state: - return_list.append(i_row) - # END-FOR (i_row) - return return_list +class SinglePtIntegrationTable(tableBase.NTableWidget): + """ + Extended QTable for integration on single Pt with previously calculated FWHM + """ + Table_Setup = [('Scan', 'int'), + ('Pt', 'int'), + ('HKL', 'str'), + ('PeakHeight', 'float'), + ('2theta', 'float'), + ('FWHM', 'float'), + ('Intensity', 'float'), + ('Pt-Sigma', 'float'), + ('Pt-I', 'float'), + ('Pt-B', 'float'), + ('ROI', 'str'), # name of ROI used to integrate counts on detector (single measurement) + ('Reference Scans', 'str'), + ('Selected', 'checkbox')] - def get_hkl(self, row_index): + def __init__(self, parent): """ - Get peak index, HKL or a row - :param row_index: row index (aka number) - :return: 3-float-tuple or None (not defined) + initialization + :param parent: """ - # check input's validity - assert isinstance(row_index, int) and row_index >= 0, 'Row index %s of type %s is not acceptable.' \ - '' % (str(row_index), type(row_index)) + super(SinglePtIntegrationTable, self).__init__(parent) - # retrieve value of HKL as string and then split them into floats - hkl_str = self.get_cell_value(row_index, self._colIndexHKL) - hkl_str = hkl_str.strip() - if len(hkl_str) == 0: - return None + # class variables + self._scan_index = None + self._pt_index = None + self._hkl_index = None + self._height_index = None + self._2theta_index = None + self._fwhm_index = None + self._intensity_index = None + self._2thta_scans_index = None + self._ref_scans_index = None + self._roi_index = None - hkl_str_list = hkl_str.split(',') - try: - peak_index_h = float(hkl_str_list[0]) - peak_index_k = float(hkl_str_list[1]) - peak_index_l = float(hkl_str_list[2]) - except IndexError: - raise RuntimeError('Row %d\' HKL value %s is not value.' % (row_index, hkl_str)) - except ValueError: - raise RuntimeError('Row %d\' HKL value %s is not value.' % (row_index, hkl_str)) + self._pt_row_dict = dict() - return peak_index_h, peak_index_k, peak_index_l + return - def get_merged_status(self, row_number): - """ Get the status whether it is merged - :param row_number: - :return: boolean + def add_scan_pt(self, scan_number, pt_number, hkl_str, two_theta): + """ add a new scan/pt to the table + :param scan_number: + :param pt_number: + :param hkl_str: + :param two_theta: + :return: """ # check - assert isinstance(row_number, int) - assert 0 <= row_number < self.rowCount() + if (scan_number, pt_number) in self._pt_row_dict: + raise RuntimeError('Pt number {0} exists in the table already.'.format(pt_number)) - # get value - merge_status_col_index = self._myColumnNameList.index('Status') - status_str = self.get_cell_value(row_number, merge_status_col_index) + # check inputs + assert isinstance(scan_number, int), 'Scan number {0} must be an integer but not a {1}' \ + ''.format(scan_number, type(scan_number)) + assert isinstance(pt_number, int), 'Pt number {0} must be an integer'.format(pt_number) + assert isinstance(hkl_str, str), 'HKL {0} must be given as a string.'.format(hkl_str) + assert isinstance(two_theta, float), '2theta {0} must be a float' - if status_str.lower() == 'done': - return True + # add a new row to the table + status, error_msg = self.append_row([scan_number, pt_number, hkl_str, 0., two_theta, 0., 0., 0., 0., 0., '', '', + False]) + if not status: + raise RuntimeError(error_msg) - return False + # register + self._pt_row_dict[scan_number, pt_number] = self.rowCount() - 1 - def get_merged_ws_name(self, i_row): - """ - Get merged workspace name - :param i_row: + # set scan editable + item_i = self.item(self.rowCount()-1, self._ref_scans_index) + item_i.setFlags(item_i.flags() | QtCore.Qt.ItemIsEditable) + + return + + def get_fwhm(self, row_index): + """ get reference scan's FWHM + :param row_index: :return: """ - # return self.get_cell_value(i_row, self._colIndexWorkspace) - return self._workspaceCacheDict[i_row] + return self.get_cell_value(row_index, self._fwhm_index) - def get_scan_list(self, output_row_number=True): - """ - Get all scans that are already listed in the table. - :param output_row_number: - :return: list of 2-tuple or integer according to value of output_row_number + def get_peak_intensities(self): + """ get the summary on all peaks' intensities + :return: dictionary as scan number and peak intensity """ - scan_list = list() - num_rows = self.rowCount() + peak_intensity_dict = dict() - for i_row in range(num_rows): - scan_num = self.get_cell_value(i_row, self._colIndexScan) - if output_row_number: - scan_list.append((scan_num, i_row)) - else: - scan_list.append(scan_num) - # END-FOR (i_row) + for row_index in range(self.rowCount()): + scan_number = self.get_cell_value(row_index, self._scan_index) + intensity_i = self.get_cell_value(row_index, self._intensity_index) + roi_i = self.get_cell_value(row_index, self._roi_index) + peak_intensity_dict[scan_number] = intensity_i, roi_i + # END-FOR - return scan_list + return peak_intensity_dict - def get_selected_scans(self): - """ Get list of selected scans to merge from table - :return: list of 2-tuples (scan number, row number) + def get_pt_number(self, row_index): """ - scan_list = list() - num_rows = self.rowCount() - col_select_index = self._myColumnNameList.index('Select') - - for i_row in range(num_rows): - if self.get_cell_value(i_row, col_select_index) is True: - scan_num = self.get_cell_value(i_row, self._colIndexScan) - scan_list.append((scan_num, i_row)) - - return scan_list - - def get_scan_number(self, row_number): - """ Get scan number of a row - Guarantees: get scan number of a row - :param row_number: + get PT number + :param row_index: :return: """ - return self.get_cell_value(row_number, self._colIndexScan) + return self.get_cell_value(row_index, self._pt_index) - def select_all_nuclear_peaks(self): + def get_reference_scans(self, row_index): """ - select all nuclear peaks, i.e., set the flag on on 'select' for all rows if their HKL indicates that - they are nuclear peaks - :return: string as error message + get the reference scans (i.e., the scans having same/close 2theta to the single Pt scan + :param row_index: + :return: """ - num_rows = self.rowCount() - error_message = '' + # get value + scans_str = self.get_cell_value(row_index, self._ref_scans_index) - for row_index in range(num_rows): - # get the reading of HKL - try: - hkl_tuple = self.get_hkl(row_index) - if hkl_tuple is None: - error_message += 'Row %d has no HKL showed.' % row_index - continue - if fourcircle_utility.is_peak_nuclear(hkl_tuple[0], hkl_tuple[1], hkl_tuple[2]): - self.select_row(row_index) - except RuntimeError as error: - error_message += 'Unable to parse HKL of line %d due to %s.' % (row_index, str(error)) - # END-FOR + # no matched scan can be found! + if scans_str is None: + return None + elif scans_str == 'No match': + return None + elif scans_str == '': + return None - return error_message + # split and parse to integers + terms = scans_str.split(',') + ref_scan_list = [int(term) for term in terms] - def set_hkl(self, row_number, hkl, hkl_source=None): - """ Set Miller index HKL to a row - :param row_number: row number - :param hkl: - :param hkl_source: + return ref_scan_list + + def get_region_of_interest_name(self, row_index): + """ + get ROI name + :param row_index: :return: """ - # check - assert isinstance(row_number, int) and 0 <= row_number < self.rowCount(),\ - 'Row number %s is out of range.' % str(row_number) - assert len(hkl) == 3, 'HKL must be a sequence with 3 items but not %s.' % len(hkl) - - # update the cell - hkl_str = '%.3f, %.3f, %.3f' % (hkl[0], hkl[1], hkl[2]) - self.update_cell_value(row_number, self._colIndexHKL, hkl_str) - - return + return self.get_cell_value(row_index, self._roi_index) - def set_k_shift_index(self, row_number, k_index): - """ Set k-shift index to a row - :param row_number: - :param k_index: + def get_scan_number(self, row_index): + """ + get scan number of the specified row + :param row_index: :return: """ - assert isinstance(k_index, int) + return self.get_cell_value(row_index, self._scan_index) - self.update_cell_value(row_number, self._colIndexKIndex, k_index) + def get_scan_pt_list(self): + """ + get a list of current scan and pt pair in the table + :return: + """ + return self._pt_row_dict.keys() - return + def get_two_theta(self, row_index): + """ + get two-theta value + :param row_index: + :return: + """ + return self.get_cell_value(row_index, self._2theta_index) - def set_motor_info(self, row_number, motor_move_tup): + def save_intensities_to_file(self, out_file_name): """ - Set the motor step information to the 'Motor' cell - :param row_number: - :param motor_move_tup: + save integrated peaks intensities to file + :param out_file_name: :return: """ - # check - assert isinstance(row_number, int) and 0 <= row_number < self.rowCount(), 'Input row number is out of range.' - assert len(motor_move_tup) == 3 + # check inputs .. + assert isinstance(out_file_name, str), 'Output file name {0} must be a string but not a {1}' \ + ''.format(out_file_name, type(out_file_name)) + if not os.access(out_file_name, os.W_OK): + raise RuntimeError('Use specified output file {0} is not writable.'.format(out_file_name)) - # get motor information and construct the string - motor_name = motor_move_tup[0] - motor_move = '%.3f (%.2E)' % (motor_move_tup[1], motor_move_tup[2]) + out_buffer = '' + for i_row in range(self.rowCount()): + scan_number = self.get_cell_value(i_row, self._scan_index) + intensity = self.get_cell_value(i_row, self._intensity_index) + out_buffer += '{0} \t{1}\n'.format(scan_number, intensity) - # set motor step information string to the table cell. - self.update_cell_value(row_number, self._colIndexMotor, motor_name) - self.update_cell_value(row_number, self._colIndexMotorStep, motor_move) + out_file = open(out_file_name, 'w') + out_file.write(out_buffer) + out_file.close() + + return + + def set_gaussian_sigma(self, row_index, sigma): + """ + set the (Gaussian) sigma value to a row + :param row_index: + :param sigma: sigma value of Gaussian + :return: None + """ + self.update_cell_value(row_index, self._fwhm_index, sigma) return - def set_peak_centre(self, row_number, peak_centre): - """ - set peak centre value - :param row_number: - :param peak_centre: + def set_reference_scan_numbers(self, row_index, ref_numbers): + """ set reference scan numbers or warning as 'No match' + :param row_index: + :param ref_numbers: :return: """ - # check input's validity - assert isinstance(row_number, int) and 0 <= row_number < self.rowCount(), \ - 'Row number %s is not supported or out of boundary.' % str(row_number) - assert isinstance(peak_centre, str) or len(peak_centre) == 3,\ - 'Peak centre %s must be a string or a container with size 3.' % str(peak_centre) + # process the reference number + if isinstance(ref_numbers, str): + scans_str = ref_numbers + elif isinstance(ref_numbers, list): + scans_str = '' + for index, ref_number in enumerate(ref_numbers): + if index > 0: + scans_str += ',' + scans_str += '{0}'.format(ref_number) - # set value of peak center - if isinstance(peak_centre, str): - # string no need to change - value_to_set = peak_centre else: - # construct the value - value_to_set = '%.3f, %.3f, %.3f' % (peak_centre[0], peak_centre[1], peak_centre[2]) + raise AssertionError('Reference scan numbers {0} of type {1} is not supported.' + ''.format(ref_numbers, type(ref_numbers))) - self.update_cell_value(row_number, self._colIndexPeak, value_to_set) - - return + # add to table + self.update_cell_value(row_index, self._ref_scans_index, scans_str) - def set_peak_intensity(self, row_number, peak_intensity, corrected_intensity, standard_error, integrate_method): + def set_two_theta(self, row_index, two_theta): """ - Set peak intensity to a row in the table - Guarantees: peak intensity is set - :param row_number: - :param peak_intensity: - :param corrected_intensity: - :param standard_error: - :param integrate_method: must be '', simple or gaussian for simple counts summation or Gaussian fit, respectively + set 2theta value of the scan + :param row_index: + :param two_theta: :return: """ - # check requirements - assert isinstance(peak_intensity, float), 'Peak intensity must be a float.' - assert isinstance(integrate_method, str), 'Integrated method {0} must be a string but not {1}.' \ - ''.format(integrate_method, type(integrate_method)) - if integrate_method not in ['', 'simple', 'mixed', 'gaussian']: - raise RuntimeError('Peak integration {0} not in list. Method must be in ["" (Not defined), "simple"' - ', "gaussian"]'.format(integrate_method)) - - self.update_cell_value(row_number, self._colIndexIntensity, peak_intensity) - self.update_cell_value(row_number, self._colIndexIntType, integrate_method) - self.update_cell_value(row_number, self._colIndexCorrInt, corrected_intensity) - self.update_cell_value(row_number, self._colIndexErrorBar, standard_error) + assert isinstance(two_theta, float), '2theta {0} must be a float.'.format(two_theta) + self.update_cell_value(row_index, self._2theta_index, two_theta) return - def set_status(self, row_number, status): + def setup(self): """ - Set the status for merging scan to QTable - :param row_number: scan number - :param status: + set up the table with default columns :return: """ - # Check - assert isinstance(status, str), 'Status (%s) must be a string, but not %s.' % (str(status), type(status)) + # set up columns + self.init_setup(SinglePtIntegrationTable.Table_Setup) - return self.update_cell_value(row_number, self._colIndexStatus, status) + # set up column index + self._scan_index = SinglePtIntegrationTable.Table_Setup.index(('Scan', 'int')) + self._pt_index = SinglePtIntegrationTable.Table_Setup.index(('Pt', 'int')) + self._hkl_index = SinglePtIntegrationTable.Table_Setup.index(('HKL', 'str')) + self._height_index = SinglePtIntegrationTable.Table_Setup.index(('PeakHeight', 'float')) + self._2theta_index = SinglePtIntegrationTable.Table_Setup.index(('2theta', 'float')) + self._fwhm_index = SinglePtIntegrationTable.Table_Setup.index(('FWHM', 'float')) + self._intensity_index = SinglePtIntegrationTable.Table_Setup.index(('Intensity', 'float')) + self._ref_scans_index = SinglePtIntegrationTable.Table_Setup.index(('Reference Scans', 'str')) + self._roi_index = SinglePtIntegrationTable.Table_Setup.index(('ROI', 'str')) - def set_wave_length(self, row_number, wave_length): - """ Set wave length to a row - :param row_number: - :param wave_length: + return + + def set_fwhm(self, scan_number, pt_number, fwhm): + """ + + :param pt_number: + :param fwhm: :return: """ - # check - assert isinstance(row_number, int) and 0 <= row_number < self.rowCount(), 'Input row number is out of range.' - assert isinstance(wave_length, float) and wave_length >= 0. + row_number = self._pt_row_dict[scan_number, pt_number] - # set - col_index = self.TableSetup.index(('Wavelength', 'float')) - self.update_cell_value(row_number, col_index, wave_length) + self.update_cell_value(row_number, self._fwhm_index, fwhm) return - def set_ws_name(self, row_number, merged_md_name): + def set_intensity(self, scan_number, pt_number, intensity): """ - Set the output workspace and workspace group's names to QTable - :param row_number: - :param merged_md_name: + + :param scan_number: + :param pt_number: + :param intensity: :return: """ - # Check - assert isinstance(merged_md_name, str), 'Merged MDWorkspace name must be a string.' + row_number = self._pt_row_dict[scan_number, pt_number] - # self.update_cell_value(row_number, self._colIndexWorkspace, merged_md_name) - - self._workspaceCacheDict[row_number] = merged_md_name + self.update_cell_value(row_number, self._intensity_index, intensity) return - def setup(self): - """ - Init setup + def set_peak_height(self, scan_number, pt_number, peak_height, roi_name): + """ set the intensity of single measurement from the counts on the detector. + In the view as 3D peak, it is the cut on the center plane as the peak shape can be modeled by 3D Gaussian. + Thus the integrated value is used as the Gaussian's height. + :param scan_number: + :param pt_number: + :param peak_height: + :param roi_name: ROI is closed related to how a peak/single measurement's intensity is calculated :return: """ - self.init_setup(self.TableSetup) - self._statusColName = 'Select' + row_number = self._pt_row_dict[scan_number, pt_number] - # set up column index - self._colIndexScan = ProcessTableWidget.TableSetup.index(('Scan', 'int')) - self._colIndexIntensity = self.TableSetup.index(('Intensity', 'float')) - self._colIndexCorrInt = self.TableSetup.index(('F2', 'float')) - self._colIndexErrorBar = self.TableSetup.index(('F2 Error', 'float')) - self._colIndexMask = self.TableSetup.index(('Mask', 'str')) - self._colIndexIntType = self.TableSetup.index(('Integrate', 'str')) - self._colIndexStatus = self.TableSetup.index(('Status', 'str')) - self._colIndexHKL = ProcessTableWidget.TableSetup.index(('HKL', 'str')) - # self._colIndexPeak = self.TableSetup.index(('Peak', 'str')) - # self._colIndexIndexFrom = self.TableSetup.index(('Index From', 'str')) - self._colIndexMotor = ProcessTableWidget.TableSetup.index(('Motor', 'str')) - self._colIndexMotorStep = ProcessTableWidget.TableSetup.index(('Motor Step', 'str')) - self._colIndexWaveLength = self.TableSetup.index(('Wavelength', 'float')) - self._colIndexKIndex = self.TableSetup.index(('K-Index', 'int')) - # self._colIndexWorkspace = self.TableSetup.index(('Workspace', 'str')) + self.update_cell_value(row_number, self._height_index, peak_height) + self.update_cell_value(row_number, self._roi_index, roi_name) return -class ScanSurveyTable(tableBase.NTableWidget): +class UBMatrixPeakTable(tableBase.NTableWidget): """ - Extended table widget for peak integration + Extended table for peaks used to calculate UB matrix """ - Table_Setup = [('Scan', 'int'), - ('Max Counts Pt', 'int'), - ('Max Counts', 'float'), - ('H', 'float'), - ('K', 'float'), - ('L', 'float'), - ('Q-range', 'float'), - ('Sample Temp', 'float'), - ('Selected', 'checkbox')] + # UB peak information table + UB_Peak_Table_Setup = [('Scan', 'int'), + ('Pt', 'int'), + ('Spice HKL', 'str'), + ('Calculated HKL', 'str'), + ('Q-Sample', 'str'), + ('Selected', 'checkbox'), + ('m1', 'float'), + ('Wavelength', 'float'), # wave length + ('Error', 'float')] def __init__(self, parent): """ + Initialization :param parent: + :return: """ tableBase.NTableWidget.__init__(self, parent) - self._myScanSummaryList = list() - - self._currStartScan = 0 - self._currEndScan = sys.maxsize - self._currMinCounts = 0. - self._currMaxCounts = sys.float_info.max + # define class variables + self._cachedSpiceHKL = dict() - self._colIndexH = None - self._colIndexK = None - self._colIndexL = None + # class variables for column indexes + self._colIndexScan = None + self._colIndexSpiceHKL = None + self._colIndexCalculatedHKL = None + self._colIndexQSample = None + self._colIndexWavelength = None + self._colIndexError = None return - def filter_and_sort(self, start_scan, end_scan, min_counts, max_counts, - sort_by_column, sort_order): + def add_peak(self, scan_number, spice_hkl, q_sample, m1, wave_length): """ - Filter the survey table and sort - Note: it might not be efficient here because the table will be refreshed twice - :param start_scan: - :param end_scan: - :param min_counts: - :param max_counts: - :param sort_by_column: - :param sort_order: 0 for ascending, 1 for descending + + :param scan_number: + :param spice_hkl: + :param q_sample: + :param m1: + :param wave_length: :return: """ - # check - assert isinstance(start_scan, int) and isinstance(end_scan, int) and end_scan >= start_scan - assert isinstance(min_counts, float) and isinstance(max_counts, float) and min_counts < max_counts - assert isinstance(sort_by_column, str), \ - 'sort_by_column requires a string but not %s.' % str(type(sort_by_column)) - assert isinstance(sort_order, int), \ - 'sort_order requires an integer but not %s.' % str(type(sort_order)) + # check inputs + assert isinstance(scan_number, int), 'Scan number integer' + assert len(spice_hkl) == 3, 'Spice HKL' + assert len(q_sample) == 3, 'Q-sample' + assert isinstance(m1, float) or m1 is None, 'm1' + assert isinstance(wave_length, float) or wave_length is None, 'wave length' - # get column index to sort - col_index = self.get_column_index(column_name=sort_by_column) + # spice_hkl_str = '{0:.4f}, {1:.4f}, {2:.4f}'.format(spice_hkl[0], spice_hkl[1], spice_hkl[2]) + # q_sample_str = '{0:.4f}, {1:.4f}, {2:.4f}'.format(q_sample[0], q_sample[1], q_sample[2]) + spice_hkl_str = self.format_array(spice_hkl) + q_sample_str = self.format_array(q_sample) + self.append_row([scan_number, -1, spice_hkl_str, '', q_sample_str, False, m1, wave_length, '']) - # filter on the back end row contents list first - self.filter_rows(start_scan, end_scan, min_counts, max_counts) + return True, '' - # order - self.sort_by_column(col_index, sort_order) + @staticmethod + def format_array(array): + """ + output a formatted array with limited precision of float + :param array: + :return: + """ + format_str = '' + for index, number in enumerate(array): + if index > 0: + format_str += ', ' + if isinstance(number, float): + format_str += '{0:.4f}'.format(number) + else: + format_str += '{0}'.format(number) + # END-FOR + + return format_str + + def get_exp_info(self, row_index): + """ + Get experiment information from a row + :param row_index: + :return: scan number, pt number + """ + assert isinstance(row_index, int) + + scan_number = self.get_cell_value(row_index, 0) + assert isinstance(scan_number, int) + pt_number = self.get_cell_value(row_index, 1) + assert isinstance(pt_number, int) + + return scan_number, pt_number + + def get_hkl(self, row_index, is_spice_hkl): + """ + Get reflection's miller index + :except RuntimeError: + :param row_index: + :param is_spice_hkl: + :return: 3-tuple as H, K, L + """ + # check input + assert isinstance(row_index, int), 'Row index {0} must be an integer but not a {1}.' \ + ''.format(row_index, type(row_index)) + + # get the HKL either parsed from SPICE file or from calculation + if is_spice_hkl: + hkl_str = self.get_cell_value(row_index, self._colIndexSpiceHKL) + else: + hkl_str = self.get_cell_value(row_index, self._colIndexCalculatedHKL) + + # convert the recorded string to HKL + status, ret_obj = guiutility.parse_float_array(hkl_str) + if not status: + raise RuntimeError('Unable to parse hkl (str) due to {0}'.format(ret_obj)) + elif len(ret_obj) != 3: + raise RuntimeError('Unable to convert array "{0}" to 3 floating points.'.format(hkl_str)) + else: + m_h, m_k, m_l = ret_obj + + return m_h, m_k, m_l + + def get_scan_pt(self, row_number): + """ + Get Scan and Pt from a row + :param row_number: + :return: + """ + scan_number = self.get_cell_value(row_number, 0) + pt_number = self.get_cell_value(row_number, 1) + + return scan_number, pt_number + + def get_selected_scans(self): + """ + get the scan numbers that are selected + :return: + """ + selected_rows = self.get_selected_rows(True) + + scan_list = list() + for i_row in selected_rows: + scan_number = self.get_cell_value(i_row, self._colIndexScan) + scan_list.append(scan_number) - return + return scan_list - def filter_rows(self, start_scan, end_scan, min_counts, max_counts): - """ - Filter by scan number, detector counts on self._myScanSummaryList - and reset the table via the latest result - :param start_scan: - :param end_scan: - :param min_counts: - :param max_counts: + def is_selected(self, row_index): + """ Check whether a row is selected. + :param row_index: :return: """ - # check whether it can be skipped - if start_scan == self._currStartScan and end_scan == self._currEndScan \ - and min_counts == self._currMinCounts and max_counts == self._currMaxCounts: - # same filter set up, return - return + if row_index < 0 or row_index >= self.rowCount(): + raise IndexError('Input row number %d is out of range [0, %d)' % (row_index, self.rowCount())) - # clear the table - self.remove_all_rows() + col_index = UBMatrixPeakTable.UB_Peak_Table_Setup.index(('Selected', 'checkbox')) - # go through all rows in the original list and then reconstruct - for index in range(len(self._myScanSummaryList)): - sum_item = self._myScanSummaryList[index] - # check - assert isinstance(sum_item, list) - assert len(sum_item) == len(self._myColumnNameList) - 1 - # check with filters: original order is counts, scan, Pt., ... - scan_number = sum_item[1] - if scan_number < start_scan or scan_number > end_scan: - continue - counts = sum_item[0] - if counts < min_counts or counts > max_counts: - continue + return self.get_cell_value(row_index, col_index) - # modify for appending to table - row_items = sum_item[:] - counts = row_items.pop(0) - row_items.insert(2, counts) - row_items.append(False) + def setup(self): + """ + Init setup + :return: + """ + self.init_setup(UBMatrixPeakTable.UB_Peak_Table_Setup) + self.set_status_column_name('Selected') - # append to table - self.append_row(row_items) - # END-FOR (index) + # define all the _colIndex + self._colIndexScan = self._myColumnNameList.index('Scan') + self._colIndexSpiceHKL = self._myColumnNameList.index('Spice HKL') + self._colIndexCalculatedHKL = self._myColumnNameList.index('Calculated HKL') + self._colIndexQSample = self._myColumnNameList.index('Q-Sample') + self._colIndexWavelength = self._myColumnNameList.index('Wavelength') + self._colIndexError = self._myColumnNameList.index('Error') - # Update - self._currStartScan = start_scan - self._currEndScan = end_scan - self._currMinCounts = min_counts - self._currMaxCounts = max_counts + # set up the width of some columns + self.setColumnWidth(self._colIndexSpiceHKL, 240) + self.setColumnWidth(self._colIndexCalculatedHKL, 240) + self.setColumnWidth(4, 240) return - def get_hkl(self, row_index): + def select_nuclear_peak_rows(self, tolerance): """ - Get peak index (HKL) from survey table (i.e., SPICE file) - :param row_index: - :return: + select all nuclear peaks, i.e., set the flag on on 'select' for all rows if their HKL indicates that + they are nuclear peaks + :param tolerance: + :return: string as error message """ - index_h = self.get_cell_value(row_index, self._colIndexH) - index_k = self.get_cell_value(row_index, self._colIndexK) - index_l = self.get_cell_value(row_index, self._colIndexL) + num_rows = self.rowCount() + error_message = '' - return index_h, index_k, index_l + for row_index in range(num_rows): + # get the reading of HKL + try: + hkl_tuple = self.get_hkl(row_index, is_spice_hkl=True) + if fourcircle_utility.is_peak_nuclear(hkl_tuple[0], hkl_tuple[1], hkl_tuple[2], tolerance): + self.select_row(row_index, status=True) + except RuntimeError as error: + error_message += 'Unable to parse HKL of line %d due to %s.' % (row_index, str(error)) + # END-FOR - def get_scan_numbers(self, row_index_list): + return error_message + + def select_scans(self, select_all=False, nuclear_peaks=False, hkl_tolerance=None, + wave_length=None, wave_length_tolerance=None): """ - Get scan numbers with specified rows - :param row_index_list: + select scans in the UB matrix table + :param select_all: + :param nuclear_peaks: + :param hkl_tolerance: + :param wave_length: + :param wave_length_tolerance: :return: """ - scan_list = list() - scan_col_index = self.Table_Setup.index(('Scan', 'int')) - for row_index in row_index_list: - scan_number_i = self.get_cell_value(row_index, scan_col_index) - scan_list.append(scan_number_i) - scan_list.sort() + if select_all: + # select all + self.select_all_rows(True) - return scan_list + elif nuclear_peaks or wave_length_tolerance is not None: + # using filters + if nuclear_peaks: + self.select_nuclear_peak_rows(hkl_tolerance) + if wave_length_tolerance is not None: + self.select_rows_by_column_value(self._colIndexWavelength, wave_length, wave_length_tolerance, + keep_current_selection=True) + else: + raise RuntimeError('Must pick up one option to do filter.') - def get_selected_run_surveyed(self, required_size=1): + return + + def set_hkl(self, i_row, hkl, is_spice_hkl, error=None): """ - Purpose: Get selected pt number and run number that is set as selected - Requirements: there must be one and only one run that is selected - Guarantees: a 2-tuple for integer for return as scan number and Pt. number - :param required_size: if specified as an integer, then if the number of selected rows is different, - an exception will be thrown. - :return: a 2-tuple of integer if required size is 1 (as old implementation) or a list of 2-tuple of integer + Set HKL to a row in the table. Show H/K/L with 4 decimal pionts + :param i_row: + :param hkl: HKL is a list of tuple + :param is_spice_hkl: If true, then set input to cell for SPICE-imported HKL. Otherwise to calculated HKL. + :param error: error of HKL """ - # check required size? - assert isinstance(required_size, int) or required_size is None, 'Required number of runs {0} must be None ' \ - 'or an integer but not a {1}.' \ - ''.format(required_size, type(required_size)) + # Check + assert isinstance(i_row, int), 'Row number (index) must be integer but not %s.'.format(type(i_row)) - # get the selected row indexes and check - row_index_list = self.get_selected_rows(True) + if isinstance(hkl, list) or isinstance(hkl, tuple): + assert len(hkl) == 3, 'In case HKL is list of tuple, its size must be equal to 3 but not %d.' \ + '' % len(hkl) + elif isinstance(hkl, numpy.ndarray): + assert hkl.shape == (3,), 'In case HKL is numpy array, its shape must be (3,) but not %s.' \ + '' % str(hkl.shape) + else: + raise AssertionError('HKL of type %s is not supported. Supported types include list, tuple ' + 'and numpy array.' % type(hkl)) + assert isinstance(is_spice_hkl, bool), 'Flag {0} for SPICE-HKL must be a boolean but not a {1}.' \ + ''.format(is_spice_hkl, type(is_spice_hkl)) - if required_size is not None and required_size != len(row_index_list): - raise RuntimeError('It is required to have {0} runs selected, but now there are {1} runs that are ' - 'selected.'.format(required_size, row_index_list)) + # convert to a string with 4 decimal points + hkl_str = '%.4f, %.4f, %.4f' % (hkl[0], hkl[1], hkl[2]) - # get all the scans and rows that are selected - scan_run_list = list() - for i_row in row_index_list: - # get scan and pt. - scan_number = self.get_cell_value(i_row, 0) - pt_number = self.get_cell_value(i_row, 1) - scan_run_list.append((scan_number, pt_number)) + if is_spice_hkl: + self.update_cell_value(i_row, self._colIndexSpiceHKL, hkl_str) + else: + self.update_cell_value(i_row, self._colIndexCalculatedHKL, hkl_str) - # special case for only 1 run that is selected - if len(row_index_list) == 1 and required_size is not None: - # get scan and pt - return scan_run_list[0] - # END-IF + # set error + if error is not None: + i_col_error = UBMatrixPeakTable.UB_Peak_Table_Setup.index(('Error', 'float')) + self.update_cell_value(i_row, i_col_error, error) - return scan_run_list + return - def show_reflections(self, num_rows): + def restore_cached_indexing(self, is_spice=True): """ - :param num_rows: + Restore the previously saved value to HKL :return: """ - assert isinstance(num_rows, int) - assert num_rows > 0 - assert len(self._myScanSummaryList) > 0 + # check first such that all the stored value are to be + stored_line_index = sorted(self._cachedSpiceHKL.keys()) + assert len(stored_line_index) == self.rowCount(), 'The current rows and cached row counts do not match.' - for i_ref in range(min(num_rows, len(self._myScanSummaryList))): - # get counts - scan_summary = self._myScanSummaryList[i_ref] - # check - assert isinstance(scan_summary, list) - assert len(scan_summary) == len(self._myColumnNameList) - 1 - # modify for appending to table - row_items = scan_summary[:] - max_count = row_items.pop(0) - row_items.insert(2, max_count) - row_items.append(False) - # append - self.append_row(row_items) + # restore + for row_index in stored_line_index: + hkl = self._cachedSpiceHKL[row_index] + self.set_hkl(row_index, hkl, is_spice_hkl=is_spice) # END-FOR + # clear + self._cachedSpiceHKL.clear() + return - def set_survey_result(self, scan_summary_list): + def store_current_indexing(self): """ - - :param scan_summary_list: + Store the current indexing for reverting :return: """ - # check - assert isinstance(scan_summary_list, list) + # clear the previous value + self._cachedSpiceHKL.clear() - # Sort and set to class variable - scan_summary_list.sort(reverse=True) - self._myScanSummaryList = scan_summary_list + # store + num_rows = self.rowCount() + for row_index in range(num_rows): + peak_indexing = self.get_hkl(row_index, is_spice_hkl=True) + self._cachedSpiceHKL[row_index] = peak_indexing + # END-FOR return - def setup(self): - """ - Init setup - :return: + def update_hkl(self, i_row, h, k, l): + """ Update HKL value + :param i_row: index of the row to have HKL updated + :param h: + :param k: + :param l: """ - self.init_setup(ScanSurveyTable.Table_Setup) - self.set_status_column_name('Selected') + assert isinstance(i_row, int), 'row number {0} must be an integer but not a {1}.' \ + ''.format(i_row, type(i_row)) - self._colIndexH = ScanSurveyTable.Table_Setup.index(('H', 'float')) - self._colIndexK = ScanSurveyTable.Table_Setup.index(('K', 'float')) - self._colIndexL = ScanSurveyTable.Table_Setup.index(('L', 'float')) + self.update_cell_value(i_row, self._colIndexCalculatedHKL, self.format_array([h, k, l])) return - - def reset(self): - """ Reset the inner survey summary table - :return: - """ - self._myScanSummaryList = list() diff --git a/scripts/HFIR_4Circle_Reduction/integratedpeakview.py b/scripts/HFIR_4Circle_Reduction/integratedpeakview.py index 9f8ae3c26208b4c3e58371fb8d02a3892018e6b2..11fc8bfe919caec82dee0879b7a286875d9ee1bd 100644 --- a/scripts/HFIR_4Circle_Reduction/integratedpeakview.py +++ b/scripts/HFIR_4Circle_Reduction/integratedpeakview.py @@ -93,7 +93,6 @@ class IntegratedPeakView(mplgraphicsview.MplGraphicsView): def on_mouse_release_event(self, event): """ - :param event: :return: """ @@ -196,3 +195,230 @@ class IntegratedPeakView(mplgraphicsview.MplGraphicsView): self.setXYLimit(ymin=y_lower_limit, ymax=y_upper_limit) return + + +class GeneralPurposedPlotView(mplgraphicsview.MplGraphicsView): + """ + Extended matplotlib based 1D plot viewer for general-purposed useage + """ + def __init__(self, parent): + """ + initialization + """ + super(GeneralPurposedPlotView, self).__init__(parent) + + # define interaction with the canvas + # self._myCanvas.mpl_connect('button_press_event', self.on_mouse_press_event) + + # class variables + self._currentDataID = None + + self._parent_window = None + + return + + # def on_mouse_press_event(self, event): + # """ Handling the event as the mouse button is pressed + # :param event: + # :return: + # """ + # return + + def plot_data(self, vec_x, vec_y, vec_e, title, label_x, label_y, annotation_list=None): + """ + plot current data + :param vec_x: + :param vec_y: + :param vec_e: + :param title: + :param label_x: + :param label_y: + :param annotation_list: + :return: + """ + self._currentDataID = self.add_plot_1d(vec_x=vec_x, vec_y=vec_y, y_err=vec_e, annotation_list=annotation_list, + color='red', label=title, x_label=label_x, y_label=label_y, + marker='.', line_style='--') + + return + + def reset_plots(self): + """ + reset current plots + :return: + """ + # clear all lines + self.clear_all_lines() + + # reset handlers + self._currentDataID = None + + return + + def save_current_plot(self, line_id, file_name): + """ + save the current plot + :param line_id: + :param file_name: + :return: + """ + if line_id is None and self._currentDataID is None: + raise RuntimeError('No line on canvas to save!') + if line_id is None: + line_id = self._currentDataID + + vec_x, vec_y, vec_e = self._myCanvas.get_data_error(line_id) + + # convert vectors to a matrix to save + row_size = len(vec_x) + + matrix = numpy.ndarray(shape=(row_size, 3), dtype='float') + matrix[:, 0] = vec_x + matrix[:, 1] = vec_y + matrix[:, 2] = vec_e + + numpy.savetxt(file_name, matrix) + + return + + +class SinglePtIntegrationView(mplgraphicsview.MplGraphicsView): + """ + Single Pt peak integration viewer. Extended from regular 1D view + """ + def __init__(self, parent): + """ + initialization + """ + super(SinglePtIntegrationView, self).__init__(parent) + + # define interaction with the canvas + self._myCanvas.mpl_connect('button_press_event', self.on_mouse_press_event) + + # class variables + self._rawDataID = None + self._fitDataID = None + self._2thetaModelID = None + self._2thetaFwhmID = None # line ID for 2theta-fwhm plot + + self._parent_window = None + + return + + def add_observed_data(self, vec_x, vec_y, label, update_plot=True): + """ + add observed data + :param vec_x: + :param vec_y: + :param label: + :param update_plot: flag to update the plot (call draw) + :return: + """ + self._rawDataID = self.add_plot_1d(vec_x, vec_y, x_label='pixel', label=label, + color='blue', update_plot=update_plot) + self.set_smart_y_limit(vec_y) + + return + + def add_fit_data(self, vec_x, vec_y, label, update_plot=True): + """ + add fitted data + :param vec_x: + :param vec_y: + :param label: + :param update_plot: flag to update the plot (call draw) + :return: + """ + self._fitDataID = self.add_plot_1d(vec_x, vec_y, label=label, color='red', update_plot=update_plot) + self.set_smart_y_limit(vec_y) + + return + + def on_mouse_press_event(self, event): + """ + plot the previous or next scan in the list + :return: + """ + if self._parent_window is not None: + # if parent window is defined + if event.button == 1: + scan_index_increment = -1 + elif event.button == 3: + scan_index_increment = 1 + else: + scan_index_increment = 0 + + if scan_index_increment != 0: + self._parent_window.change_scan_number(increment=scan_index_increment) + self._parent_window.do_plot_integrated_pt(show_plot=True, save_plot_to=None) + # END-IF + # END-IF + + return + + def plot_2theta_model(self, vec_2theta, vec_fwhm, vec_model): + """ plot 2theta model + :param vec_2theta: + :param vec_fwhm: + :param vec_model: + :return: + """ + # remove previous plot + self.clear_all_lines() + self._fitDataID = None + self._rawDataID = None + + # add the line + if vec_fwhm is not None: + self._2thetaFwhmID = self.add_plot_1d(vec_2theta, vec_fwhm, x_label='$2\theta$', y_label='FWHM', + color='black', update_plot=True, label='Observed') + + if vec_model is not None: + self._2thetaModelID = self.add_plot_1d(vec_2theta, vec_model, x_label='$2\theta$', y_label='FWHM', + color='blue', update_plot=True, label='Model') + + return + + def set_parent_window(self, parent_window): + """ + set the parent window but not the UI parent + :param parent_window: + :return: + """ + assert parent_window is not None, 'Parent window cannot be None' + + self._parent_window = parent_window + + return + + def set_smart_y_limit(self, vec_y): + """ + Set limit on Y axis automatically (in a 'smart' way), i.e., + - to the smaller of zero and 10 percent delta Y under minimum Y + - to 10 percent delta Y above maximum Y + :return: + """ + # check + assert isinstance(vec_y, numpy.ndarray) and len(vec_y) > 0, 'Vector Y must be a numpy array and not empty.' + + # find y's minimum and maximum + try: + min_y = numpy.min(vec_y) + max_y = numpy.max(vec_y) + except ValueError as value_err: + raise RuntimeError(str(value_err)) + + d_y = max_y - min_y + + # set Y to the smaller of zero and 10 percent delta Y under minimum Y + if min_y > 0: + y_lower_limit = 0 + else: + y_lower_limit = min_y - 0.1 * d_y + + # set Y to 10 percent delta Y above maximum Y + y_upper_limit = max_y + 0.1 * d_y + + self.setXYLimit(ymin=y_lower_limit, ymax=y_upper_limit) + + return diff --git a/scripts/HFIR_4Circle_Reduction/message_dialog.py b/scripts/HFIR_4Circle_Reduction/message_dialog.py index 97fccda8af40a37f3db7f9bd02b081f2adfebe45..ea85a3e376de64267d064c9f9b203cc6d1a7c995 100644 --- a/scripts/HFIR_4Circle_Reduction/message_dialog.py +++ b/scripts/HFIR_4Circle_Reduction/message_dialog.py @@ -7,12 +7,16 @@ # Dialog for message from __future__ import (absolute_import, division, print_function) from six.moves import range -from PyQt4 import QtGui, QtCore +from qtpy.QtWidgets import (QDialog) # noqa +from mantid.kernel import Logger +try: + from mantidqt.utils.qt import load_ui +except ImportError: + Logger("HFIR_4Circle_Reduction").information('Using legacy ui importer') + from mantidplot import load_ui -from . import ui_messagebox - -class MessageDialog(QtGui.QDialog): +class MessageDialog(QDialog): """ extension of QDialog """ @@ -25,12 +29,11 @@ class MessageDialog(QtGui.QDialog): super(MessageDialog, self).__init__(parent) # set up UI - self.ui = ui_messagebox.Ui_Dialog() - self.ui.setupUi(self) + ui_path = "messagebox.ui" + self.ui = load_ui(__file__, ui_path, baseinstance=self) # define operation - self.connect(self.ui.pushButton_close, QtCore.SIGNAL('clicked()'), - self.do_quit) + self.ui.pushButton_close.clicked.connect(self.do_quit) return diff --git a/scripts/HFIR_4Circle_Reduction/mpl2dgraphicsview.py b/scripts/HFIR_4Circle_Reduction/mpl2dgraphicsview.py index 52e47cd90462bb3db80d2db67f97450a59922a6e..a515456faef90b3ba9fd776f62ca1e00bba79c24 100644 --- a/scripts/HFIR_4Circle_Reduction/mpl2dgraphicsview.py +++ b/scripts/HFIR_4Circle_Reduction/mpl2dgraphicsview.py @@ -8,17 +8,15 @@ from __future__ import (absolute_import, division, print_function) import os import numpy as np - -from PyQt4 import QtGui - -from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar2 +from qtpy.QtWidgets import (QWidget, QVBoxLayout, QSizePolicy) +from MPLwidgets import FigureCanvasQTAgg as FigureCanvas +from MPLwidgets import NavigationToolbar2QT as NavigationToolbar2 from matplotlib.figure import Figure import matplotlib.image from matplotlib import pyplot as plt -class Mpl2dGraphicsView(QtGui.QWidget): +class Mpl2dGraphicsView(QWidget): """ A combined graphics view including matplotlib canvas and a navigation tool bar for 2D image specifically """ @@ -27,14 +25,14 @@ class Mpl2dGraphicsView(QtGui.QWidget): """ Initialization """ # Initialize parent - QtGui.QWidget.__init__(self, parent) + QWidget.__init__(self, parent) # set up canvas self._myCanvas = Qt4Mpl2dCanvas(self) self._myToolBar = MyNavigationToolbar(self, self._myCanvas) # set up layout - self._vBox = QtGui.QVBoxLayout(self) + self._vBox = QVBoxLayout(self) self._vBox.addWidget(self._myCanvas) self._vBox.addWidget(self._myToolBar) @@ -118,6 +116,24 @@ class Mpl2dGraphicsView(QtGui.QWidget): return + def save_figure(self, file_name): + """ + save the current on-canvas figure to a file + :param file_name: + :return: + """ + self._myCanvas.fig.savefig(file_name) + + return + + def set_title(self, title): + """ + set title to image + :param title: + :return: + """ + self._myCanvas.axes.set_title(title) + @property def x_min(self): """ @@ -151,7 +167,7 @@ class Mpl2dGraphicsView(QtGui.QWidget): class Qt4Mpl2dCanvas(FigureCanvas): """ A customized Qt widget for matplotlib 2D image. - It can be used to replace GraphicsView of QtGui + It can be used to replace GraphicsView """ def __init__(self, parent): @@ -168,7 +184,7 @@ class Qt4Mpl2dCanvas(FigureCanvas): self.setParent(parent) # Set size policy to be able to expanding and resizable with frame - FigureCanvas.setSizePolicy(self, QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding) + FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding) FigureCanvas.updateGeometry(self) # legend and color bar diff --git a/scripts/HFIR_4Circle_Reduction/mplgraphicsview.py b/scripts/HFIR_4Circle_Reduction/mplgraphicsview.py index 17bc2f8478074dd28c68029fe714d6bbdafb3673..0936492816ed9dd14299a00d0e42bd71a889346b 100644 --- a/scripts/HFIR_4Circle_Reduction/mplgraphicsview.py +++ b/scripts/HFIR_4Circle_Reduction/mplgraphicsview.py @@ -9,14 +9,14 @@ from __future__ import (absolute_import, division, print_function) from six.moves import range import os import numpy as np - -from PyQt4 import QtGui -from PyQt4.QtCore import pyqtSignal - -from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar2 +from MPLwidgets import FigureCanvasQTAgg as FigureCanvas +from MPLwidgets import NavigationToolbar2QT as NavigationToolbar2 from matplotlib.figure import Figure import matplotlib.image +import matplotlib.collections +from qtpy.QtWidgets import (QWidget, QVBoxLayout, QSizePolicy) # noqa +from qtpy.QtCore import Signal as pyqtSignal + MplLineStyles = ['-', '--', '-.', ':', 'None', ' ', ''] MplLineMarkers = [ @@ -358,7 +358,7 @@ class IndicatorManager(object): return -class MplGraphicsView(QtGui.QWidget): +class MplGraphicsView(QWidget): """ A combined graphics view including matplotlib canvas and a navigation tool bar @@ -368,7 +368,7 @@ class MplGraphicsView(QtGui.QWidget): """ Initialization """ # Initialize parent - QtGui.QWidget.__init__(self, parent) + QWidget.__init__(self, parent) # set up canvas self._myCanvas = Qt4MplCanvas(self) @@ -380,7 +380,7 @@ class MplGraphicsView(QtGui.QWidget): self._homeXYLimit = None # set up layout - self._vBox = QtGui.QVBoxLayout(self) + self._vBox = QVBoxLayout(self) self._vBox.addWidget(self._myCanvas) self._vBox.addWidget(self._myToolBar) @@ -436,13 +436,14 @@ class MplGraphicsView(QtGui.QWidget): return key_list - def add_plot_1d(self, vec_x, vec_y, y_err=None, color=None, label='', x_label=None, y_label=None, - marker=None, line_style=None, line_width=1, show_legend=True): + def add_plot_1d(self, vec_x, vec_y, y_err=None, annotation_list=None, color=None, label='', x_label=None, y_label=None, + marker=None, line_style=None, line_width=1, show_legend=True, update_plot=True): """ Add a 1-D plot to canvas :param vec_x: :param vec_y: :param y_err: + :param annotation_list: list of string for annotation of each data point OR None :param color: :param label: :param x_label: @@ -451,10 +452,11 @@ class MplGraphicsView(QtGui.QWidget): :param line_style: :param line_width: :param show_legend: + :param update_plot: :return: line ID (key to the line) """ line_id = self._myCanvas.add_plot_1d(vec_x, vec_y, y_err, color, label, x_label, y_label, marker, line_style, - line_width, show_legend) + line_width, show_legend, annotation_list) return line_id @@ -617,8 +619,10 @@ class MplGraphicsView(QtGui.QWidget): return + @property def canvas(self): - """ Get the canvas + """ + provide reference to Canvas :return: """ return self._myCanvas @@ -1030,7 +1034,7 @@ class MplGraphicsView(QtGui.QWidget): class Qt4MplCanvas(FigureCanvas): """ A customized Qt widget for matplotlib figure. - It can be used to replace GraphicsView of QtGui + It can be used to replace GraphicsView """ def __init__(self, parent): """ Initialization @@ -1055,11 +1059,12 @@ class Qt4MplCanvas(FigureCanvas): self.setParent(parent) # Set size policy to be able to expanding and resizable with frame - FigureCanvas.setSizePolicy(self, QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding) + FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding) FigureCanvas.updateGeometry(self) # Variables to manage all lines/subplot - self._lineDict = {} + self._lineDict = dict() + self._errorBarDict = dict() # containing two tuple: r[0] as line, r[2][0] as error bar offsets self._lineIndex = 0 # legend and color bar @@ -1094,7 +1099,7 @@ class Qt4MplCanvas(FigureCanvas): return def add_plot_1d(self, vec_x, vec_y, y_err=None, color=None, label="", x_label=None, y_label=None, - marker=None, line_style=None, line_width=1, show_legend=True): + marker=None, line_style=None, line_width=1, show_legend=True, annotation_list=None): """ :param vec_x: numpy array X @@ -1108,6 +1113,7 @@ class Qt4MplCanvas(FigureCanvas): :param line_style: :param line_width: :param show_legend: + :param annotation_list: :return: new key """ # Check input @@ -1165,10 +1171,8 @@ class Qt4MplCanvas(FigureCanvas): # Register line_key = self._lineIndex if plot_error: - msg = 'Return from plot is a {0}-tuple: {1} with plot error is {2}\n'.format(len(r), r, plot_error) - for i_r in range(len(r)): - msg += 'r[%d] = %s\n' % (i_r, str(r[i_r])) - raise NotImplementedError(msg) + # plot with error bar: data_line = r[0], error_bar_line = r[2][0] + self._errorBarDict[line_key] = r[0], r[2][0] else: assert len(r) > 0, 'There must be at least 1 figure returned' self._lineDict[line_key] = r[0] @@ -1179,6 +1183,11 @@ class Qt4MplCanvas(FigureCanvas): self.axes.lines.remove(r[i_r]) # END-IF-ELSE + if annotation_list is not None and len(annotation_list) == len(vec_y): + for ipt in range(len(annotation_list)): + self.axes.annotate(annotation_list[ipt], (vec_x[ipt], vec_y[ipt])) + # END-IF + # Flush/commit self.draw() @@ -1362,16 +1371,24 @@ class Qt4MplCanvas(FigureCanvas): str(plot), len(self.axes.lines), str(e))) del self._lineDict[ikey] else: - # error bar - plot[0].remove() - for line in plot[1]: - line.remove() - for line in plot[2]: - line.remove() - del self._lineDict[ikey] + # error bar: but not likely to be set to _lineDict + raise RuntimeError('It is not correct to set a line with error bar to _lineDict') # ENDIF(plot) # ENDFOR + # clear all 1D plot with error bar + for line_key in self._errorBarDict: + # check whether this line has been removed + if self._errorBarDict[line_key] is None: + del self._errorBarDict[line_key] + continue + + # remove data line and error bar + line_obj, error_bar_obj = self._errorBarDict[line_key] + line_obj.remove() + error_bar_obj.remove() + # END-FOR + self._setup_legend() self.draw() @@ -1496,6 +1513,19 @@ class Qt4MplCanvas(FigureCanvas): return + def save_figure(self, file_name): + """ save the current figure to an image file + save to image + :param file_name: + :return: + """ + assert isinstance(file_name, str), 'File name {} must be a string.'.format(file_name) + + # TODO - NEXT - Provide many more options for figures to save + self.fig.savefig(file_name) + + return + def set_title(self, title, color, location='center'): """ set title to the figure (canvas) with default location at center @@ -1508,8 +1538,8 @@ class Qt4MplCanvas(FigureCanvas): assert isinstance(title, str), 'Title {0} must be a string but not a {1}.'.format(title, type(title)) assert isinstance(color, str) and len(color) > 0, 'Color {0} must be a non-empty string but not a {1}.' \ ''.format(color, type(color)) - assert isinstance(location, str) and len(location) > 0, 'Location {0} must be a non-empty string but not a {1}.' \ - ''.format(location, type(location)) + assert isinstance(location, str) and len(location) > 0, 'Location {0} must be a non-empty string but not a' \ + ' {1}.'.format(location, type(location)) # set title and re-draw to apply self.axes.set_title(title, loc=location, color=color) @@ -1614,17 +1644,75 @@ class Qt4MplCanvas(FigureCanvas): :param line_id: :return: 2-tuple as vector X and vector Y """ - # check - if line_id not in self._lineDict: - raise KeyError('Line ID %s does not exist.' % str(line_id)) + if line_id in self._lineDict: + # single line + # get line and check + line = self._lineDict[line_id] + if line is None: + raise RuntimeError('Line ID %s has been removed.' % line_id) + + elif line_id in self._errorBarDict: + # single line with error + # get line and check + content = self._errorBarDict[line_id] + if content is None: + raise RuntimeError('Line ID {0} has been removed from error-bar dict.'.format(line_id)) + line = content[0] - # get line - line = self._lineDict[line_id] - if line is None: - raise RuntimeError('Line ID %s has been removed.' % line_id) + else: + # not anywhere + raise KeyError('Line ID %s does not exist.' % str(line_id)) return line.get_xdata(), line.get_ydata() + def get_data_error(self, line_id): + """ + get data with error bar if there is an error bar set with data; otherwise, set error bar to None + :param line_id: + :return: + """ + def retrieve_error_bar(error_bar_lineset): + """ + retrieve error bar from a + :param error_bar_lineset: + :return: + """ + # check input + assert isinstance(error_bar_lineset, matplotlib.collections.LineCollection), 'Error bar line set type' + + segments = error_bar_lineset.get_segments() + vec_error = np.ndarray(shape=(len(segments),), dtype='float') + for iseg, segment in enumerate(segments): + error_i = segment[1, 1] - segment[0, 1] + vec_error[iseg] = error_i * 0.5 + + return vec_error + + if line_id in self._lineDict: + # single line + # get line and check + line = self._lineDict[line_id] + if line is None: + raise RuntimeError('Line ID %s has been removed.' % line_id) + vec_e = None + + elif line_id in self._errorBarDict: + # single line with error + # get line and check + content = self._errorBarDict[line_id] + if content is None: + raise RuntimeError('Line ID {0} has been removed from error-bar dict.'.format(line_id)) + line = content[0] + + # get vector for error + vec_e = retrieve_error_bar(content[1]) + + else: + # not anywhere + raise KeyError('Line ID %s does not exist.' % str(line_id)) + + return line.get_xdata(), line.get_ydata(), vec_e + def getLineStyleList(self): """ """ diff --git a/scripts/HFIR_4Circle_Reduction/mplgraphicsview3d.py b/scripts/HFIR_4Circle_Reduction/mplgraphicsview3d.py index b2ef2d83d2b05cea6df49d82f157469035ecb793..714fcc328055f5e2d9211f06caa30a0f158baec8 100644 --- a/scripts/HFIR_4Circle_Reduction/mplgraphicsview3d.py +++ b/scripts/HFIR_4Circle_Reduction/mplgraphicsview3d.py @@ -10,9 +10,8 @@ from six.moves import range import numpy as np import os -from PyQt4.QtGui import QSizePolicy - -from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas +from qtpy.QtWidgets import QSizePolicy +from MPLwidgets import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure from mpl_toolkits.mplot3d import Axes3D diff --git a/scripts/HFIR_4Circle_Reduction/multi_threads_helpers.py b/scripts/HFIR_4Circle_Reduction/multi_threads_helpers.py index ef2a28f6a71ecf119bd69b9daebdf196c30065ce..34d3672b4ed130f48d781eedc1b80434fa052db8 100644 --- a/scripts/HFIR_4Circle_Reduction/multi_threads_helpers.py +++ b/scripts/HFIR_4Circle_Reduction/multi_threads_helpers.py @@ -7,11 +7,10 @@ #pylint: disable=W0403,R0913,R0902 from __future__ import (absolute_import, division, print_function) import os -from PyQt4 import QtCore -from PyQt4.QtCore import QThread - -import HFIR_4Circle_Reduction.reduce4circleControl as r4c -from HFIR_4Circle_Reduction import peak_integration_utility +from qtpy.QtCore import Signal as pyqtSignal +from qtpy.QtCore import QThread # noqa +import HFIR_4Circle_Reduction.reduce4circleControl as r4c # noqa +from HFIR_4Circle_Reduction import peak_integration_utility # noqa class AddPeaksThread(QThread): @@ -19,11 +18,11 @@ class AddPeaksThread(QThread): A QThread class to add peaks to Mantid to calculate UB matrix """ # signal for a peak is added: int_0 = experiment number, int_1 = scan number - peakAddedSignal = QtCore.pyqtSignal(int, int) + peakAddedSignal = pyqtSignal(int, int) # signal for status: int_0 = experiment number, int_1 = scan number, int_2 = progress (0...) - peakStatusSignal = QtCore.pyqtSignal(int, int, int) + peakStatusSignal = pyqtSignal(int, int, int) # signal for final error report: int_0 = experiment number, str_1 = error message - peakAddedErrorSignal = QtCore.pyqtSignal(int, str) + peakAddedErrorSignal = pyqtSignal(int, str) def __init__(self, main_window, exp_number, scan_number_list): """ @@ -119,9 +118,9 @@ class IntegratePeaksThread(QThread): A thread to integrate peaks """ # signal to emit before a merge/integration status: exp number, scan number, progress, mode - peakMergeSignal = QtCore.pyqtSignal(int, int, float, list, int) + peakMergeSignal = pyqtSignal(int, int, float, list, int) # signal to report state: (1) experiment, (2) scan, (3) mode, (4) message - mergeMsgSignal = QtCore.pyqtSignal(int, int, int, str) + mergeMsgSignal = pyqtSignal(int, int, int, str) def __init__(self, main_window, exp_number, scan_tuple_list, mask_det, mask_name, norm_type, num_pt_bg_left, num_pt_bg_right, scale_factor=1.000): @@ -340,6 +339,7 @@ class IntegratePeaksThread(QThread): return str(ass_err) # calculate lorentz correction + # TODO/FIXME/NOW2 : peak center Q shall be from calculation! lorentz_factor = peak_integration_utility.calculate_lorentz_correction_factor(peak_center_q, wavelength, motor_step) @@ -357,8 +357,8 @@ class MergePeaksThread(QThread): """ # signal to report state: (1) scan, (2) message - mergeMsgSignal = QtCore.pyqtSignal(int, str) - saveMsgSignal = QtCore.pyqtSignal(int, str) + mergeMsgSignal = pyqtSignal(int, str) + saveMsgSignal = pyqtSignal(int, str) def __init__(self, main_window, exp_number, scan_number_list, md_file_list): """Initialization diff --git a/scripts/HFIR_4Circle_Reduction/optimizelatticewindow.py b/scripts/HFIR_4Circle_Reduction/optimizelatticewindow.py index bc1ed493288cf8269dcdbf898bf029787ae5cd26..6e71da3c7e5e0a5c8b1edd5f0664a32ca9239b92 100644 --- a/scripts/HFIR_4Circle_Reduction/optimizelatticewindow.py +++ b/scripts/HFIR_4Circle_Reduction/optimizelatticewindow.py @@ -6,18 +6,23 @@ # SPDX - License - Identifier: GPL - 3.0 + #pylint: disable=C0103 from __future__ import (absolute_import, division, print_function) -from PyQt4 import QtGui, QtCore +from qtpy.QtWidgets import (QMainWindow) +from qtpy.QtCore import Signal as pyqtSignal +from mantid.kernel import Logger +try: + from mantidqt.utils.qt import load_ui +except ImportError: + Logger("HFIR_4Circle_Reduction").information('Using legacy ui importer') + from mantidplot import load_ui -from . import ui_OptimizeLattice - -class OptimizeLatticeWindow(QtGui.QMainWindow): +class OptimizeLatticeWindow(QMainWindow): """ Main window widget to set up parameters to optimize """ # establish signal for communicating from App2 to App1 - must be defined before the constructor - mySignal = QtCore.pyqtSignal(int) + mySignal = pyqtSignal(int) def __init__(self, parent=None): """ @@ -26,10 +31,10 @@ class OptimizeLatticeWindow(QtGui.QMainWindow): :return: """ # init - QtGui.QMainWindow.__init__(self, parent) + QMainWindow.__init__(self, parent) - self.ui = ui_OptimizeLattice.Ui_MainWindow() - self.ui.setupUi(self) + ui_path = "OptimizeLattice.ui" + self.ui = load_ui(__file__, ui_path, baseinstance=self) # initialize widgets self.ui.comboBox_unitCellTypes.addItems(['Cubic', @@ -45,11 +50,8 @@ class OptimizeLatticeWindow(QtGui.QMainWindow): self.ui.lineEdit_tolerance.setText('0.12') # define event handling - self.connect(self.ui.pushButton_Ok, QtCore.SIGNAL('clicked()'), - self.do_ok) - - self.connect(self.ui.pushButton_cancel, QtCore.SIGNAL('clicked()'), - self.do_quit) + self.ui.pushButton_Ok.clicked.connect(self.do_ok) + self.ui.pushButton_cancel.clicked.connect(self.do_quit) if parent is not None: # connect to the method to refine UB matrix by constraining lattice parameters diff --git a/scripts/HFIR_4Circle_Reduction/peak_integration_utility.py b/scripts/HFIR_4Circle_Reduction/peak_integration_utility.py index 9f30e89574d8abbe05e7ad8f5908d06101accf31..4aee1f7f7792c5c554e650789752553aa6e39e83 100644 --- a/scripts/HFIR_4Circle_Reduction/peak_integration_utility.py +++ b/scripts/HFIR_4Circle_Reduction/peak_integration_utility.py @@ -9,9 +9,12 @@ from __future__ import (absolute_import, division, print_function) from six.moves import range import numpy import math +import os +import csv from scipy.optimize import curve_fit import mantid.simpleapi as mantidsimple from mantid.api import AnalysisDataService +from HFIR_4Circle_Reduction.fourcircle_utility import * def apply_lorentz_correction(peak_intensity, q, wavelength, step_omega): @@ -26,15 +29,18 @@ def apply_lorentz_correction(peak_intensity, q, wavelength, step_omega): def calculate_lorentz_correction_factor(q_sample, wavelength, motor_step): """ - + Lorenz correction = sin(2theta) :param q_sample: :param wavelength: :param motor_step: :return: """ + # TODO/FIXME/NOW2 - Q sample shall be calculated from HKL and UB matrix but not from observation sin_theta = q_sample * wavelength / (4 * numpy.pi) theta = math.asin(sin_theta) - factor = numpy.sin(2 * theta) * motor_step + # MOTOR step does not make sense! + # factor = numpy.sin(2 * theta) * motor_step + factor = numpy.sin(2 * theta) return factor @@ -60,26 +66,36 @@ def calculate_motor_step(motor_pos_array, motor_step_tolerance=0.5): return motor_step -def convert_motor_pos_intensity(integrated_pt_dict, motor_pos_dict): +def calculate_peak_intensity_gauss(gauss_a, gauss_sigma, error_a_sq=None, error_sigma_sq=None, + error_a_sigma=None): """ - :except: raise RuntimeError if - :param integrated_pt_dict: - :param motor_pos_dict: - :return: motor_pos_vec, pt_intensity_vec + calculate the peak intensity, which is the area under the peak + if sigma == 1, then the integral is sqrt(pi); + then the value is sqrt(pi) * e^{-1/(2.*sigma**2)} + :param gauss_a: + :param gauss_sigma: + :param error_a_sq: error(a)**2 + :param error_sigma_sq: error(sigma)**2 + :param error_a_sigma: correlated error for a and sigma + :return: """ - pt_list = sorted(integrated_pt_dict.keys()) - - if len(motor_pos_dict) != len(pt_list): - raise RuntimeError('Integrated Pt intensities does not match motor positions') - - pt_intensity_vec = numpy.ndarray(shape=(len(pt_list), ), dtype='float') - motor_pos_vec = numpy.ndarray(shape=(len(pt_list), ), dtype='float') + integral = numpy.sqrt(2. * numpy.pi) * gauss_a * gauss_sigma - for i_pt, pt in enumerate(pt_list): - pt_intensity_vec[i_pt] = integrated_pt_dict[pt] - motor_pos_vec[i_pt] = motor_pos_dict[pt] + if error_a_sq is not None: + # calculate integral intensity error by propagation + # check + assert isinstance(error_a_sq, float), 'Error(a)**2 must be a float but not a {0}.'.format(type(error_a_sq)) + assert isinstance(error_sigma_sq, float), 'Error(sigma)**2 must be a float but not a {0}.' \ + ''.format(type(error_sigma_sq)) + assert isinstance(error_a_sigma, float), 'Error(a,sigma) must be a float but not a {0}.' \ + ''.format(type(error_a_sigma)) + # calculate + error2 = gauss_a**2 * error_sigma_sq + error_a_sq * gauss_sigma**2 + 2. * gauss_a * gauss_sigma * error_a_sigma + error = numpy.sqrt(error2) + else: + error = numpy.sqrt(integral) - return motor_pos_vec, pt_intensity_vec + return integral, error def calculate_penalty(model_vec_y, exp_vec_y): @@ -109,6 +125,50 @@ def calculate_penalty(model_vec_y, exp_vec_y): return cost +def calculate_single_pt_scan_peak_intensity(peak_height, fwhm, is_fwhm): + """ + calculate peak intensity for single-measurement peak (1-measurement-1-scan) + :param peak_height: + :param fwhm: + :param is_fwhm: + :return: + """ + # check input + assert isinstance(fwhm, float), 'blabla' + + # convert fwhm + if is_fwhm: + sigma = fwhm / 2.355 + else: + sigma = fwhm + + peak_intensity, no_error = calculate_peak_intensity_gauss(peak_height, sigma) + + return peak_intensity + + +def convert_motor_pos_intensity(integrated_pt_dict, motor_pos_dict): + """ + :except: raise RuntimeError if + :param integrated_pt_dict: + :param motor_pos_dict: + :return: motor_pos_vec, pt_intensity_vec + """ + pt_list = sorted(integrated_pt_dict.keys()) + + if len(motor_pos_dict) != len(pt_list): + raise RuntimeError('Integrated Pt intensities does not match motor positions') + + pt_intensity_vec = numpy.ndarray(shape=(len(pt_list), ), dtype='float') + motor_pos_vec = numpy.ndarray(shape=(len(pt_list), ), dtype='float') + + for i_pt, pt in enumerate(pt_list): + pt_intensity_vec[i_pt] = integrated_pt_dict[pt] + motor_pos_vec[i_pt] = motor_pos_dict[pt] + + return motor_pos_vec, pt_intensity_vec + + def estimate_background(pt_intensity_dict, bg_pt_list): """ Estimate background value by average the integrated counts of some Pt. @@ -160,6 +220,81 @@ def find_gaussian_start_values_by_observation(vec_x, vec_y): return [x0, est_sigma, est_a, est_background] +def fit_gaussian_linear_background_mtd(matrix_ws_name): + """ + fit Gaussian with linear background by calling Mantid's FitPeaks + :param matrix_ws_name: + :return: 2-tuple: dictionary for fit result (key = ws index, value = dictionary), + model workspace name + """ + # check input workspace + check_string('MatrixWorkspace name', matrix_ws_name) + + if not AnalysisDataService.doesExist(matrix_ws_name): + raise RuntimeError('Workspace {} does not exist in Mantid ADS.'.format(matrix_ws_name)) + else: + peak_ws = AnalysisDataService.retrieve(matrix_ws_name) + + # fit peaks + out_ws_name = '{}_fit_positions'.format(matrix_ws_name) + model_ws_name = '{}_model'.format(matrix_ws_name) + fit_param_table_name = '{}_params_table'.format(matrix_ws_name) + + # estimated peak center + vec_x = peak_ws.readX(0) + estimated_peak_center = numpy.mean(vec_x) + + mantidsimple.FitPeaks(InputWorkspace=matrix_ws_name, + OutputWorkspace=out_ws_name, + PeakCenters=estimated_peak_center, + FitWindowBoundaryList='{}, {}'.format(vec_x[0], vec_x[-1]), + FitFromRight=False, + Minimizer='Levenberg-MarquardtMD', + HighBackground=False, + ConstrainPeakPositions=False, + FittedPeaksWorkspace=model_ws_name, + OutputPeakParametersWorkspace=fit_param_table_name) + + # Convert the table workspace to a standard dictionary + fit_param_dict = convert_fit_parameter_table_to_dict(fit_param_table_name) + + return fit_param_dict, model_ws_name + + +def convert_fit_parameter_table_to_dict(table_name): + """ + + :param table_name: + :return: + """ + # table workspace + assert isinstance(table_name, str), 'blabla' + table_ws = AnalysisDataService.retrieve(table_name) + + # create dictionary + fit_param_dict = dict() + params_list = table_ws.getColumnNames() + # ['wsindex', 'peakindex', 'Height', 'PeakCentre', 'Sigma', 'A0', 'A1', 'chi2'] + + # go through all lines + num_rows = table_ws.rowCount() + for irow in range(num_rows): + value_dict = dict() + ws_index = None + for iparam, par_name in enumerate(params_list): + value_i = table_ws.cell(irow, iparam) + if par_name == 'wsindex': + ws_index = int(value_i) + else: + value_dict[par_name] = float(value_i) + # END-FOR (column) + assert ws_index is not None + fit_param_dict[ws_index] = value_dict + # END-FOR (row) + + return fit_param_dict + + def fit_gaussian_linear_background(vec_x, vec_y, vec_e, start_value_list=None, find_start_value_by_fit=False): """ Fit a curve with Gaussian + linear background @@ -197,8 +332,9 @@ def fit_gaussian_linear_background(vec_x, vec_y, vec_e, start_value_list=None, f # do second round fit assert isinstance(start_value_list, list) and len(start_value_list) == 4, 'Starting value list must have 4 elements' + fit2_coeff, fit2_cov_matrix = curve_fit(gaussian_linear_background, vec_x, vec_y, sigma=vec_e, p0=start_value_list) - # take sigma=vec_e, out as it increases unstable + # take sigma=vec_e out as it increases unstable # calculate the model x0, sigma, a, b = fit2_coeff @@ -266,13 +402,23 @@ def fit_motor_intensity_model(motor_pos_dict, integrated_pt_dict): cov_matrix = None else: # good - assert isinstance(cov_matrix, numpy.ndarray), 'Covarance matrix must be a numpy array' + assert isinstance(cov_matrix, numpy.ndarray), 'Covariance matrix must be a numpy array' + # calculate fitting error/standard deviation + g_error_array = numpy.sqrt(numpy.diag(cov_matrix)) + # print('[DB...BAT] Gaussian fit error (type {0}): {1}'.format(type(g_error_array), g_error_array)) + + gauss_error_dict['x0'] = g_error_array[0] + gauss_error_dict['s'] = g_error_array[1] + gauss_error_dict['A'] = g_error_array[2] + gauss_error_dict['B'] = g_error_array[3] + gauss_error_dict['x02'] = cov_matrix[0, 0] gauss_error_dict['s2'] = cov_matrix[1, 1] gauss_error_dict['A2'] = cov_matrix[2, 2] gauss_error_dict['B2'] = cov_matrix[3, 3] gauss_error_dict['s_A'] = cov_matrix[1, 2] gauss_error_dict['A_s'] = cov_matrix[2, 1] + # END-FOR return gauss_parameter_dict, gauss_error_dict, cov_matrix @@ -408,38 +554,6 @@ def gaussian_peak_intensity(parameter_dict, error_dict): return peak_intensity, intensity_error -def calculate_peak_intensity_gauss(gauss_a, gauss_sigma, error_a_sq=None, error_sigma_sq=None, - error_a_sigma=None): - """ - calculate the peak intensity, which is the area under the peak - if sigma == 1, then the integral is sqrt(pi); - then the value is sqrt(pi) * e^{-1/(2.*sigma**2)} - :param gauss_a: - :param gauss_sigma: - :param error_a_sq: error(a)**2 - :param error_sigma_sq: error(sigma)**2 - :param error_a_sigma: correlated error for a and sigma - :return: - """ - integral = numpy.sqrt(2. * numpy.pi) * gauss_a * gauss_sigma - - if error_a_sq is not None: - # calculate integral intensity error by propagation - # check - assert isinstance(error_a_sq, float), 'Error(a)**2 must be a float but not a {0}.'.format(type(error_a_sq)) - assert isinstance(error_sigma_sq, float), 'Error(sigma)**2 must be a float but not a {0}.' \ - ''.format(type(error_sigma_sq)) - assert isinstance(error_a_sigma, float), 'Error(a,sigma) must be a float but not a {0}.' \ - ''.format(type(error_a_sigma)) - # calculate - error2 = gauss_a**2 * error_sigma_sq + error_a_sq * gauss_sigma**2 + 2. * gauss_a * gauss_sigma * error_a_sigma - error = numpy.sqrt(error2) - else: - error = numpy.sqrt(integral) - - return integral, error - - def get_finer_grid(vec_x, factor): """ insert values to a vector (grid) to make it finer @@ -542,6 +656,7 @@ def integrate_single_scan_peak(merged_scan_workspace_name, integrated_peak_ws_na return True, pt_dict +# TEST NOW3 - API changed! def integrate_peak_full_version(scan_md_ws_name, spice_table_name, output_peak_ws_name, peak_center, mask_workspace_name, norm_type, intensity_scale_factor, background_pt_tuple): @@ -550,7 +665,7 @@ def integrate_peak_full_version(scan_md_ws_name, spice_table_name, output_peak_w 1. simple summation 2. simple summation with gaussian fit 3. integrate with fitted gaussian - :return: peak integration result in dictionary + :return: dictionary: peak integration result """ def create_peak_integration_dict(): """ @@ -579,7 +694,7 @@ def integrate_peak_full_version(scan_md_ws_name, spice_table_name, output_peak_w 'gauss error': 0., 'gauss background': 0., 'gauss parameters': None, - 'gauss errors': None, + 'gauss errors': None, # details can be found in (this module) fit_motor_intensity_model() 'motor positions': None, 'pt intensities': None, 'covariance matrix': None @@ -641,9 +756,9 @@ def integrate_peak_full_version(scan_md_ws_name, spice_table_name, output_peak_w peak_int_dict['simple background'] = averaged_background # fit gaussian + flat background - parameters, errors, covariance_matrix = fit_motor_intensity_model(motor_pos_dict, integrated_pt_dict) + parameters, gauss_error_dict, covariance_matrix = fit_motor_intensity_model(motor_pos_dict, integrated_pt_dict) peak_int_dict['gauss parameters'] = parameters - peak_int_dict['gauss errors'] = errors + peak_int_dict['gauss errors'] = gauss_error_dict peak_int_dict['covariance matrix'] = covariance_matrix if covariance_matrix is None or parameters['B'] < 0.: @@ -670,7 +785,7 @@ def integrate_peak_full_version(scan_md_ws_name, spice_table_name, output_peak_w peak_int_dict['pt_range'] = pt_range # calculate gaussian (method 3) - intensity_gauss, intensity_gauss_error = gaussian_peak_intensity(parameters, errors) + intensity_gauss, intensity_gauss_error = gaussian_peak_intensity(parameters, gauss_error_dict) peak_int_dict['gauss intensity'] = intensity_gauss peak_int_dict['gauss error'] = intensity_gauss_error # END-IF-ELSE @@ -678,6 +793,42 @@ def integrate_peak_full_version(scan_md_ws_name, spice_table_name, output_peak_w return peak_int_dict +def read_peak_integration_table_csv(peak_file_name): + """ + read a csv file saved from the peak integration information table + :param peak_file_name: + :return: a dictionary of peak integration result information in STRING form + """ + # check input + assert isinstance(peak_file_name, str), 'Peak integration table file {0} must be a string but not a {1}.' \ + ''.format(peak_file_name, type(peak_file_name)) + + if os.path.exists(peak_file_name) is False: + raise RuntimeError('Peak integration information file {0} does not exist.'.format(peak_file_name)) + + # read + scan_peak_dict = dict() + with open(peak_file_name, 'r') as csv_file: + reader = csv.reader(csv_file, delimiter='\t', quotechar='#') + title_list = None + for index, row in enumerate(reader): + if index == 0: + # title + title_list = row + else: + # value + peak_dict = dict() + for term_index, value in enumerate(row): + peak_dict[title_list[term_index]] = value + scan_number = int(peak_dict['Scan']) + scan_peak_dict[scan_number] = peak_dict + # END-IF-ELSE + # END-FOR + # END-WITH + + return scan_peak_dict + + def simple_integrate_peak(pt_intensity_dict, bg_value, motor_step_dict, peak_center=None, peak_sigma=None, motor_pos_dict=None, sigma_range=2.): """ diff --git a/scripts/HFIR_4Circle_Reduction/peakprocesshelper.py b/scripts/HFIR_4Circle_Reduction/peakprocesshelper.py index ea4a72960b3345377e3b01f51f2eb90ebe500cf0..4abaeaa1a4c03199d70410d53232834ee6f75fb1 100644 --- a/scripts/HFIR_4Circle_Reduction/peakprocesshelper.py +++ b/scripts/HFIR_4Circle_Reduction/peakprocesshelper.py @@ -20,10 +20,10 @@ __author__ = 'wzz' class PeakProcessRecord(object): """ Class containing a peak's information for GUI In order to manage some operations for a peak - It does not contain peak workspace but will hold + It does not contain peak workspace but will hold all the parameters about peak integration """ - def __init__(self, exp_number, scan_number, peak_ws_name): + def __init__(self, exp_number, scan_number, peak_ws_name, two_theta): """ Initialization Purpose: set up unchanged parameters including experiment number, scan number and peak workspace's name """ @@ -40,6 +40,9 @@ class PeakProcessRecord(object): self._myScanNumber = scan_number self._myPeakWorkspaceName = peak_ws_name + # related detector information + self._2theta = two_theta + # self._myDataMDWorkspaceName = None @@ -65,9 +68,16 @@ class PeakProcessRecord(object): self._gaussStdDev = 0. self._lorenzFactor = None - # peak integration result + # Gaussian fitting related + self._gaussFWHM = None + self._peakMotorCenter = None + self._gaussBackground = None + + # peak integration result: all the fitted parameters such as Sigma are in this dictionary self._integrationDict = None - self._ptIntensityDict = None + # pt-based Gaussian integration result dictionary. + # details can be found in peak_integration_utility.integrate_peak_full_version + self._gaussIntegrationInfoDict = None # some motor/goniometer information for further correction self._movingMotorTuple = None @@ -139,11 +149,21 @@ class PeakProcessRecord(object): return err_msg + @property + def gaussian_fwhm(self): + """ + return the gaussian FWHM + :return: + """ + return self._gaussFWHM + def generate_integration_report(self): """ generate a dictionary for this PeakInfo :return: """ + print ('[DB..BAT] generate_integration_report is called!') + report = dict() if self._spiceHKL is not None: @@ -195,6 +215,10 @@ class PeakProcessRecord(object): report['K-vector'] = self._kShiftVector report['Absorption Correction'] = self._absorptionCorrection + if self._gaussIntegrationInfoDict: + print ('[FLAG-SigmaError] {0} {1}'.format(self._myScanNumber, + self._gaussIntegrationInfoDict['gauss errors']['s'])) + return report def get_intensity(self, algorithm_type, lorentz_corrected): @@ -242,6 +266,24 @@ class PeakProcessRecord(object): return intensity, std_dev + def get_parameter(self, par_name): + """ + get some parameters for peak fitting or etc + :param par_name: + :return: + """ + # TODO (future): Allow for more parameters + if par_name == '2theta': + par_value = self._2theta + par_error = 0 + elif par_name == 'sigma': + par_value = self._integrationDict['gauss parameters']['s'] + par_error = self._gaussIntegrationInfoDict['gauss errors']['s'] + else: + raise RuntimeError('Parameter {0} is not set up for get_parameter()'.format(par_name)) + + return par_value, par_error + def get_peak_centre(self): """ get weighted peak centre :return: Qx, Qy, Qz (3-double-tuple) @@ -267,6 +309,13 @@ class PeakProcessRecord(object): assert peak_ws return peak_ws + def get_integration_gauss_fit_params(self): + """ + get the parameters of the Gaussian fit on 3D scan peak integration + :return: + """ + return self._gaussIntegrationInfoDict + def get_hkl(self, user_hkl): """ Get HKL from the peak process record :param user_hkl: if selected, then return the HKL set from client (GUI). Otherwise, HKL is retrieved @@ -439,6 +488,34 @@ class PeakProcessRecord(object): return + def set_gaussian_fit_params(self, intensity, fwhm, position, background, intensity_error=None): + """ + set Gaussian fit parameters + :param intensity: + :param fwhm: + :param position: + :param background: list as [A0, A1, ...] + :return: + """ + # check inputs + assert isinstance(intensity, float), 'Intensity {0} must be a float but not a {1}' \ + ''.format(intensity, type(intensity)) + assert isinstance(fwhm, float), 'Peak FWHM {0} must be a float but not a {1}' \ + ''.format(fwhm, type(fwhm)) + assert isinstance(position, float), 'Peak center {0} must be a float but not a {1}' \ + ''.format(position, type(position)) + assert isinstance(background, list), 'Background {0} must be given as a list, i.e., [A0, A1, ...]' \ + ''.format(background) + + # set value + self._gaussIntensity = intensity + self._gaussStdDev = intensity_error + self._gaussFWHM = fwhm + self._peakMotorCenter = position + self._gaussBackground = background + + return + def set_hkl_np_array(self, hkl): """ Set current HKL which may come from any source, such as user, spice or calculation :param hkl: 3-item-list or 3-tuple for HKL @@ -536,11 +613,361 @@ class PeakProcessRecord(object): """ assert isinstance(pt_intensity_dict, dict) - self._ptIntensityDict = pt_intensity_dict + self._gaussIntegrationInfoDict = pt_intensity_dict return +class SinglePointPeakIntegration(object): + """ + simple class to store the result of ONE and ONLY ONE single point measurement peak integration + """ + def __init__(self, exp_number, scan_number, roi_name, pt_number, two_theta): + """ + initialization + :param exp_number: + :param scan_number: + :param roi_name: + :param pt_number: + """ + # check inputs + check_integer('Experiment number', exp_number) + check_integer('Scan number', scan_number) + check_integer('Pt number', pt_number) + check_string('ROI name', roi_name) + check_float('Two theta', two_theta) + + self._exp_number = exp_number + self._scan_number = scan_number + self._roi_name = roi_name + self._pt_number = pt_number + self._two_theta = two_theta + + self._integral_direction = None + + self._pt_intensity = None + self._peak_intensity = None + + self._vec_x = None + self._vec_y = None + + self._gauss_x0 = None + self._gauss_sigma = None + self._flat_b = None + self._a1 = None # background first order + + # reference peak width + self._ref_peak_sigma = None + + # fitting cost (goodness) + self._fit_cost = None + + self._spiceHKL = None + + return + + # TODO FIXME NOW3 - Need to set up all the Gaussian/Background parameters + def get_gaussian_parameters(self): + """ + get the Gaussian + :return: + """ + return self._gauss_x0, self._gauss_sigma, self._pt_intensity, self._flat_b + + def get_hkl(self, user_hkl): + """ Get HKL (originally from SPICE) + :param user_hkl: if selected, then return the HKL set from client (GUI). Otherwise, HKL is retrieved + from original SPICE file. + :return: + """ + # get HKL from SPICE file + # if self._spiceHKL is None: + self.retrieve_hkl_from_spice_table() + ret_hkl = self._spiceHKL + # END-IF-ELSE + + return ret_hkl + + def get_pt_intensity(self): + """ + get single-pt-can intensity + :return: + """ + return self._pt_intensity + + def get_intensity(self, algorithm_type, lorentz_corrected): + """ + get the integrated intensity with specified integration algorithm and whether + the result should be corrected by Lorentz correction factor + :param algorithm_type: + :param lorentz_corrected: + :return: + """ + # check + if self._pt_intensity is None: + raise RuntimeError('SinglePtPeakInfo of Exp {0} Scan {1} ({2} | {3}) has not integrated setup.' + ''.format(self._exp_number, self._scan_number, self._roi_name, hex(id(self)))) + + # get intensity + intensity = self._pt_intensity + std_dev = numpy.sqrt(intensity) + + if lorentz_corrected: + # use the instrument 2theta: L = sin(2theta) + lorentz_factor = numpy.sin(self._two_theta * numpy.pi / 180.) + + intensity *= lorentz_factor + std_dev *= lorentz_factor + + return intensity, std_dev + + def get_vec_x_y(self): + """ + get single Pt processed intensity + :return: + """ + return self._vec_x, self._vec_y + + def has_fit_result(self): + """ + check whether this single pt scan has Gassian function fit for the summed counts + :return: + """ + + # TODO NOW3 Code Quality: this has a duplicate in the same file! + def retrieve_hkl_from_spice_table(self): + """ Get averaged HKL from SPICE table + HKL will be averaged from SPICE table by assuming the value in SPICE might be right + :return: + """ + # get SPICE table + spice_table_name = get_spice_table_name(self._exp_number, self._scan_number) + assert AnalysisDataService.doesExist(spice_table_name), 'Spice table for Exp %d Scan %d cannot be found.' \ + '' % (self._exp_number, self._scan_number) + + spice_table_ws = AnalysisDataService.retrieve(spice_table_name) + + # get HKL column indexes + h_col_index = spice_table_ws.getColumnNames().index('h') + k_col_index = spice_table_ws.getColumnNames().index('k') + l_col_index = spice_table_ws.getColumnNames().index('l') + + # scan each Pt. + hkl = numpy.array([0., 0., 0.]) + + num_rows = spice_table_ws.rowCount() + for row_index in range(num_rows): + mi_h = spice_table_ws.cell(row_index, h_col_index) + mi_k = spice_table_ws.cell(row_index, k_col_index) + mi_l = spice_table_ws.cell(row_index, l_col_index) + hkl += numpy.array([mi_h, mi_k, mi_l]) + # END-FOR + + self._spiceHKL = hkl/num_rows + + return + + def set_xy_vector(self, vec_x, vec_y, integral_direction): + """ + set the X and Y vector + :param vec_x: + :param vec_y: + :param integral_direction: integration direction + :return: + """ + # check input + assert isinstance(vec_x, numpy.ndarray), 'X vector must be a numpy array' + assert isinstance(vec_y, numpy.ndarray), 'Y vector must be a numpy array' + assert integral_direction in ['vertical', 'horizontal'], 'Peak integration direction {} must be a string ' \ + '(now a {}) being either vertical or horizontal' \ + ''.format(integral_direction, type(integral_direction)) + + # set + self._vec_x = vec_x + self._vec_y = vec_y + self._integral_direction = integral_direction + + return + + def set_fit_cost(self, cost): + """ + set the cost (goodness) of fit + :param cost: + :return: + """ + self._fit_cost = cost + + return + + def set_peak_intensity(self, peak_intensity): + """ + set peak intensity + :param peak_intensity: + :return: + """ + assert isinstance(peak_intensity, float), 'Peak intensity to set must be a float.' + if peak_intensity < 0: + raise RuntimeError('Peak intensity {0} cannot be negative!'.format(peak_intensity)) + + self._peak_intensity = peak_intensity + + def set_fit_params(self, x0, sigma, height, a0, a1): + """ + set the parameters for fitting + :param x0: + :param sigma: + :param a: + :param b: + :return: + """ + # TODO - 2018 - clean! + self._pt_intensity = height + self._gauss_x0 = x0 + self._gauss_sigma = sigma + self._flat_b = a0 + self._a1 = a1 + + return + + def set_ref_fwhm(self, ref_fwhm, is_fwhm): + """ + set reference scan's FWHM from same/similar 2theta value + :param ref_fwhm: + :param is_fwhm: flag whether the input is FWHM or Sigma + :return: + """ + check_float('Reference scan FWHM', ref_fwhm) + + self._ref_peak_sigma = ref_fwhm + if is_fwhm: + self._ref_peak_sigma /= 2.355 + + return +# END-CLASS + + +class SinglePtScansIntegrationOperation(object): + """ + a class to handle and manage Mantid Workspace2D instance created from integrated single pt-scan peaks + along either vertical direction or horizontal direction + """ + def __init__(self, exp_number, scan_number_list, matrix_ws_name, scan_spectrum_map, spectrum_scan_map): + """ + initialization + :param exp_number: + :param scan_number_list: + :param matrix_ws_name: + :param scan_spectrum_map: + :param spectrum_scan_map: + """ + # check input + check_integer('Experiment number', exp_number) + check_list('Scan numbers', scan_number_list) + check_string('Workspace2D name', matrix_ws_name) + check_dictionary('Scan number spectrum number mapping', scan_spectrum_map) + check_dictionary('Spectrum number scan number mapping', spectrum_scan_map) + + if AnalysisDataService.doesExist(matrix_ws_name) is False: + raise RuntimeError('Workspace {} does not exist.'.format(matrix_ws_name)) + + # store + self._exp_number = exp_number + self._scan_number_list = scan_number_list[:] + self._matrix_ws_name = matrix_ws_name + self._scan_spectrum_map = scan_spectrum_map + self._spectrum_scan_map = spectrum_scan_map + + # TODO - 20180814 - Add pt number, rio name and integration direction for future check! + + # others + self._model_ws_name = None + + return + + def check_scan_numbers_same(self, scans_to_check): + """ + check whether the scan numbers are same or not + :param scans_to_check: + :return: + """ + check_list('Scan numbers to check with', scans_to_check) + + if len(scans_to_check) != len(self._scan_number_list): + return False + + scans_to_check_set = set(scans_to_check) + my_scans_set = set(self._scan_number_list) + + return scans_to_check_set == my_scans_set + + @property + def exp_number(self): + """ + experiment number + :return: + """ + return self._exp_number + + def get_model_workspace(self): + """ + get the workspace name for calculated data (model) + :return: + """ + return self._model_ws_name + + def get_workspace(self): + """ get workspace name + :return: + """ + return self._matrix_ws_name + + def get_scan_number(self, spectrum_number, from_zero=True): + """ + get the scan number of a spectrum + :param spectrum_number: + :param from_zero: + :return: + """ + if not from_zero: + spectrum_number -= 1 + + if spectrum_number not in self._spectrum_scan_map: + raise RuntimeError('Spectrum number {} of type {} cannot be found in spectrum-scan map' + ''.format(spectrum_number, type(spectrum_number))) + + return self._spectrum_scan_map[spectrum_number] + + def get_spectrum_number(self, scan_number, from_zero=True): + """ + get the spectrum number of a scan + :param scan_number: + :param from_zero: + :return: + """ + if scan_number not in self._scan_spectrum_map: + raise RuntimeError('Scan number {} of type {} cannot be found in spectrum-scan map' + ''.format(scan_number, type(scan_number))) + + spectrum_number = self._scan_spectrum_map[scan_number] + + if not from_zero: + spectrum_number += 1 + + return spectrum_number + + def set_model_workspace(self, model_ws_name): + """ + set the workspace name for calculated peak (model) workspace from FitPeaks + :param model_ws_name: + :return: + """ + self._model_ws_name = model_ws_name + + return + +# END-CLASS + + def build_pt_spice_table_row_map(spice_table_ws): """ Build a lookup dictionary for Pt number and row number diff --git a/scripts/HFIR_4Circle_Reduction/plot3dwindow.py b/scripts/HFIR_4Circle_Reduction/plot3dwindow.py index a695a066bfbbc7da45494e3f79c52572474553ad..88d8dee14ad1b498ed9b09c792f2cb70524bd25f 100644 --- a/scripts/HFIR_4Circle_Reduction/plot3dwindow.py +++ b/scripts/HFIR_4Circle_Reduction/plot3dwindow.py @@ -9,15 +9,23 @@ from __future__ import (absolute_import, division, print_function) from six.moves import range import sys import numpy as np -from PyQt4 import QtGui, QtCore +from qtpy.QtWidgets import (QMainWindow) +from mantid.kernel import Logger +try: + from mantidqt.utils.qt import load_ui +except ImportError: + Logger("HFIR_4Circle_Reduction").information('Using legacy ui importer') + from mantidplot import load_ui +from qtpy.QtWidgets import (QVBoxLayout) -from . import ui_View3DWidget + +from HFIR_4Circle_Reduction.mplgraphicsview3d import MplPlot3dCanvas from HFIR_4Circle_Reduction import guiutility __author__ = 'wzz' -class Plot3DWindow(QtGui.QMainWindow): +class Plot3DWindow(QMainWindow): """ Main window to view merged data in 3D """ @@ -29,10 +37,11 @@ class Plot3DWindow(QtGui.QMainWindow): :return: """ # Init - QtGui.QMainWindow.__init__(self, parent) + QMainWindow.__init__(self, parent) - self.ui = ui_View3DWidget.Ui_MainWindow() - self.ui.setupUi(self) + ui_path ="View3DWidget.ui" + self.ui = load_ui(__file__, ui_path, baseinstance=self) + self._promote_widgets() # Initialize widgets self.ui.lineEdit_baseColorRed.setText('0.5') @@ -42,17 +51,11 @@ class Plot3DWindow(QtGui.QMainWindow): self.ui.comboBox_scans.addItem('unclassified') # Event handling - self.connect(self.ui.pushButton_plot3D, QtCore.SIGNAL('clicked()'), - self.do_plot_3d) - self.connect(self.ui.pushButton_checkCounts, QtCore.SIGNAL('clicked()'), - self.do_check_counts) - self.connect(self.ui.pushButton_clearPlots, QtCore.SIGNAL('clicked()'), - self.do_clear_plots) - self.connect(self.ui.pushButton_quit, QtCore.SIGNAL('clicked()'), - self.do_quit) - - self.connect(self.ui.comboBox_scans, QtCore.SIGNAL('currentIndexChanged(int)'), - self.evt_change_scan) + self.ui.pushButton_plot3D.clicked.connect(self.do_plot_3d) + self.ui.pushButton_checkCounts.clicked.connect(self.do_check_counts) + self.ui.pushButton_clearPlots.clicked.connect(self.do_clear_plots) + self.ui.pushButton_quit.clicked.connect(self.do_quit) + self.ui.comboBox_scans.currentIndexChanged.connect(self.evt_change_scan) # Set up # list of data keys for management @@ -68,6 +71,14 @@ class Plot3DWindow(QtGui.QMainWindow): return + def _promote_widgets(self): + graphicsView_layout = QVBoxLayout() + self.ui.frame_graphicsView.setLayout(graphicsView_layout) + self.ui.graphicsView = MplPlot3dCanvas(self) + graphicsView_layout.addWidget(self.ui.graphicsView) + + return + def close_session(self): """ Close session :return: diff --git a/scripts/HFIR_4Circle_Reduction/preprocess_window.ui b/scripts/HFIR_4Circle_Reduction/preprocess_window.ui index b472cf4f51eb5deda5ddb45a6ec2a2463de65ffb..95d64baf0b757990ad9c9c1ce9b8785cde26cca2 100644 --- a/scripts/HFIR_4Circle_Reduction/preprocess_window.ui +++ b/scripts/HFIR_4Circle_Reduction/preprocess_window.ui @@ -243,7 +243,7 @@ </layout> </item> <item row="1" column="1" rowspan="3"> - <widget class="ScanPreProcessStatusTable" name="tableView_scanProcessState"/> + <widget class="QFrame" name="frame_tableView_scanProcessState"/> </item> </layout> </widget> diff --git a/scripts/HFIR_4Circle_Reduction/reduce4circleControl.py b/scripts/HFIR_4Circle_Reduction/reduce4circleControl.py index 66288b0fe632f0b8feb4ffb7b3ec53b215acbbdf..0868618eba442340de9be58954684dafdcfb2a7e 100644 --- a/scripts/HFIR_4Circle_Reduction/reduce4circleControl.py +++ b/scripts/HFIR_4Circle_Reduction/reduce4circleControl.py @@ -15,6 +15,7 @@ # ################################################################################ from __future__ import (absolute_import, division, print_function) +import bisect try: # python3 from urllib.request import urlopen @@ -25,13 +26,17 @@ except ImportError: from urllib2 import HTTPError from urllib2 import URLError from six.moves import range +import math import csv import random import os +import numpy from HFIR_4Circle_Reduction.fourcircle_utility import * import HFIR_4Circle_Reduction.fourcircle_utility as fourcircle_utility from HFIR_4Circle_Reduction.peakprocesshelper import PeakProcessRecord +from HFIR_4Circle_Reduction.peakprocesshelper import SinglePointPeakIntegration +from HFIR_4Circle_Reduction.peakprocesshelper import SinglePtScansIntegrationOperation from HFIR_4Circle_Reduction import fputility from HFIR_4Circle_Reduction import project_manager from HFIR_4Circle_Reduction import peak_integration_utility @@ -42,6 +47,7 @@ import mantid import mantid.simpleapi as mantidsimple from mantid.api import AnalysisDataService from mantid.kernel import V3D +from numpy import * DebugMode = True @@ -67,6 +73,22 @@ def check_str_type(variable, var_name): return +def check_float_type(variable, var_name): + """ + check whether a variable is an integer + :except AssertionError: + :param variable: + :param var_name: + :return: + """ + assert isinstance(var_name, str), 'Variable name {0} must be an integer but not a {1}' \ + ''.format(var_name, type(var_name)) + assert isinstance(variable, int) or isinstance(variable, float), '{0} {1} must be an integer but not a {2}' \ + ''.format(var_name, variable, type(variable)) + + return + + def check_int_type(variable, var_name): """ check whether a variable is an integer @@ -138,10 +160,20 @@ class CWSCDReductionControl(object): # Peak Info self._myPeakInfoDict = dict() + # Loaded peak information dictionary + self._myLoadedPeakInfoDict = dict() + # Sample log value look up table + self._2thetaLookupTable = dict() # Last UB matrix calculated self._myLastPeakUB = None # Flag for data storage self._cacheDataOnly = False + # Single PT scan integration: + # example: key = exp_number, scan_number, pt_number, roi_name][integration direction] + # value = vec_x, vec_y, cost, params + self._single_pt_integration_dict = dict() + # workspace for a exp, scan_numbers, roi_name + self._single_pt_matrix_dict = dict() # Dictionary to store survey information self._scanSummaryList = list() @@ -168,9 +200,16 @@ class CWSCDReductionControl(object): # reference workspace for LoadMask self._refWorkspaceForMask = None - # Region of interest: key = (experiment, scan), value = RegionOfInterest instance + # Region of interest: key = roi name, value = RegionOfInterest instance self._roiDict = dict() + # single point peak integration related + self._two_theta_scan_dict = dict() + self._scan_2theta_set = set() + self._two_theta_sigma = None # a 2-tuple vector for (2theta, gaussian-sigma) + self._current_single_pt_integration_key = None + self._curr_2theta_fwhm_func = None + # register startup mantid.UsageService.registerFeatureUsage("Interface", "4-Circle Reduction", False) @@ -233,6 +272,26 @@ class CWSCDReductionControl(object): return + @staticmethod + def generate_single_pt_scans_key(exp_number, scan_number_list, roi_name, integration_direction): + """ + generate a unique but repeatable key for multiple single-pt scans + :param exp_number: + :param scan_number_list: + :param roi_name: + :param integration_direction: + :return: + """ + # do some math to scan numbers + check_list('Scan numbers', scan_number_list) + scan_number_vec = numpy.array(scan_number_list) + unique = numpy.sum(scan_number_vec, axis=0) + + ws_key = 'e{}_s{}-{}:{}_{}_{}'.format(exp_number, scan_number_list[0], scan_number_list[-1], unique, + roi_name, integration_direction) + + return ws_key + def add_k_shift_vector(self, k_x, k_y, k_z): """ Add a k-shift vector @@ -295,6 +354,26 @@ class CWSCDReductionControl(object): return + def check_2theta_fwhm_formula(self, formula): + """ + check whether a formula can be used to calculate FWHM from 2theta. + If it is a valid formula, set as a class variable + :param formula: + :return: 2-tuple + """ + assert isinstance(formula, str), '2theta-FWHM formula {} must be a string but not a {}' \ + ''.format(formula, type(formula)) + + try: + equation = 'lambda x: {}'.format(formula) + fwhm_func = eval(equation) + except SyntaxError as syn_err: + return False, 'Unable to accept 2theta-FWHM formula {} due to {}'.format(formula, syn_err) + + self._curr_2theta_fwhm_func = fwhm_func + + return True, None + def find_peak(self, exp_number, scan_number, pt_number_list=None): """ Find 1 peak in sample Q space for UB matrix :param exp_number: @@ -305,8 +384,9 @@ class CWSCDReductionControl(object): This part will be redo as 11847_Load_HB3A_Experiment """ # Check & set pt. numbers - assert isinstance(exp_number, int) - assert isinstance(scan_number, int) + check_int_type(exp_number, 'Experiment number') + check_int_type(scan_number, 'Scan Number') + if pt_number_list is None: status, pt_number_list = self.get_pt_numbers(exp_number, scan_number) assert status, 'Unable to get Pt numbers from scan %d.' % scan_number @@ -362,6 +442,137 @@ class CWSCDReductionControl(object): return False, 'Unable to find first Pt file {0}'.format(first_xm_file) + def calculate_intensity_single_pt(self, exp_number, scan_number, pt_number, roi_name, ref_fwhm, is_fwhm): + """ + calculate single-point-measurement peak/scan's intensity + :param exp_number: + :param scan_number: + :param pt_number: + :param roi_name: + :param ref_fwhm: + :param is_fwhm: + :return: + """ + # check inputs + assert isinstance(exp_number, int), 'Experiment number {0} must be an integer but not a {1}' \ + ''.format(exp_number, type(exp_number)) + assert isinstance(scan_number, int), 'Scan number {0} must be an integer.'.format(scan_number) + assert isinstance(pt_number, int), 'Pt number {0} must be an integer'.format(pt_number) + assert isinstance(roi_name, str), 'ROI name {0} must be a string'.format(roi_name) + check_float_type(ref_fwhm, 'Reference FWHM') + + # check whether the detector counts has been calculated and get the value + if (exp_number, scan_number, pt_number, roi_name) not in self._single_pt_integration_dict: + raise RuntimeError('Exp {0} Scan {1} Pt {2} ROI {3} does not exist in single-point integration ' + 'dictionary, whose keys are {4}'.format(exp_number, scan_number, pt_number, roi_name, + self._single_pt_integration_dict.keys())) + + integration_record = self._single_pt_integration_dict[exp_number, scan_number, pt_number, roi_name] + # integration_record.set_ref_peak_width(ref_fwhm, is_fwhm) + + # params = integration_record + # + # # get 2theta value from + # two_theta = self.get_sample_log_value(exp_number, scan_number, pt_number, '2theta') + # ref_exp_number, ref_scan_number, integrated_peak_params = self.get_integrated_scan_params(exp_number, + # two_theta, + # resolution=0.01) + peak_intensity = peak_integration_utility.calculate_single_pt_scan_peak_intensity( + integration_record.get_pt_intensity(), ref_fwhm, is_fwhm) + integration_record.set_peak_intensity(peak_intensity) + + return peak_intensity + + def get_single_scan_pt_model(self, exp_number, scan_number, pt_number, roi_name, integration_direction): + """ get a single-pt scan summed 1D data either vertically or horizontally with model data + :param exp_number: + :param scan_number: + :param pt_number: + :param roi_name: + :param integration_direction: + :return: 2-tuple.. vector model + """ + # get record key + ws_record_key = self._current_single_pt_integration_key + print('[DB...BAT] Retrieve ws record key: {}'.format(ws_record_key)) + + # TODO - 20180814 - Check pt number, rio name and integration direction + + if ws_record_key in self._single_pt_matrix_dict: + # check integration manager + integration_manager = self._single_pt_matrix_dict[ws_record_key] + assert integration_manager.exp_number == exp_number, 'blabla' + else: + raise RuntimeError('Last single-pt integration manager (key) {} does not exist.' + .format(ws_record_key)) + + matrix_ws = AnalysisDataService.retrieve(integration_manager.get_model_workspace()) + ws_index = integration_manager.get_spectrum_number(scan_number, from_zero=True) + + vec_x = matrix_ws.readX(ws_index) + vec_model = matrix_ws.readY(ws_index) + + return vec_x, vec_model + + def get_single_scan_pt_summed(self, exp_number, scan_number, pt_number, roi_name, integration_direction): + """ get a single scan Pt. 's on-detector in-roi integration result + :param exp_number: + :param scan_number: + :param pt_number: + :param roi_name: + :param integration_direction: vector X, vector Y (raw), vector Y (model) + :return: + """ + integration_record = self.get_single_pt_info(exp_number, scan_number, pt_number, roi_name, + integration_direction) + + vec_x, vec_y = integration_record.get_vec_x_y() + + return vec_x, vec_y + + def get_single_pt_info(self, exp_number, scan_number, pt_number, roi_name, integration_direction): + """ get the integrated single-pt scan data + :param exp_number: + :param scan_number: + :param pt_number: + :param roi_name: + :return: + """ + try: + peak_info = self._single_pt_integration_dict[exp_number, scan_number, pt_number, roi_name] + except KeyError: + err_message = 'Exp {0} Scan {1} Pt {2} ROI {3} does not exit in Single-Pt-Integration dictionary ' \ + 'which has keys: {4}'.format(exp_number, scan_number, pt_number, roi_name, + self._single_pt_integration_dict.keys()) + raise RuntimeError(err_message) + try: + peak_info = peak_info[integration_direction] + except KeyError: + err_message = 'Exp {0} Scan {1} Pt {2} ROI {3} does not have integration direction {4}' \ + 'in Single-Pt-Integration dictionary which has keys: {5}' \ + ''.format(exp_number, scan_number, pt_number, roi_name, integration_direction, + sorted(peak_info.keys())) + raise RuntimeError(err_message) + + return peak_info + + def calculate_peak_integration_sigma(self, two_theta): + """ + calculate Gaussian-Sigma for single-measurement peak integration by linear interpolation + :param two_theta: + :return: float + """ + if self._two_theta_sigma is None: + raise RuntimeError('2-theta Gaussian-sigma curve has not been set') + + # do a linear interpolation + interp_sigma = numpy.interp(two_theta, self._two_theta_sigma[0], self._two_theta_sigma[1]) + + print ('[DB...BAT] 2theta = {0}: output sigma = {1}'.format(two_theta, interp_sigma)) + print ('[DB...BAT] X = {0}, Y = {1}'.format(self._two_theta_sigma[0], self._two_theta_sigma[1])) + + return interp_sigma + def calculate_ub_matrix(self, peak_info_list, a, b, c, alpha, beta, gamma): """ Calculate UB matrix @@ -388,8 +599,8 @@ class CWSCDReductionControl(object): # Construct a new peak workspace by combining all single peak ub_peak_ws_name = 'Temp_UB_Peak' - self._build_peaks_workspace(peak_info_list, ub_peak_ws_name, - index_from_spice=True, hkl_to_int=True) + self._build_peaks_workspace(peak_info_list, ub_peak_ws_name) + # index_from_spice=True, hkl_to_int=True) # Calculate UB matrix try: @@ -596,13 +807,13 @@ class CWSCDReductionControl(object): return mask_ws_name - def does_file_exist(self, exp_number, scan_number, pt_number=None): + def does_spice_files_exist(self, exp_number, scan_number, pt_number=None): """ Check whether data file for a scan or pt number exists on the :param exp_number: experiment number or None (default to current experiment number) :param scan_number: :param pt_number: if None, check SPICE file; otherwise, detector xml file - :return: + :return: boolean (exist?) and string (file name) """ # check inputs assert isinstance(exp_number, int) or pt_number is None @@ -622,14 +833,15 @@ class CWSCDReductionControl(object): except AttributeError: raise AttributeError('Unable to create SPICE file name from directory %s and file name %s.' '' % (self._dataDir, spice_file_name)) + else: - # pt number given, then check + # pt number given, then check whether the XML file for Pt exists xml_file_name = get_det_xml_file_name(self._instrumentName, exp_number, scan_number, pt_number) file_name = os.path.join(self._dataDir, xml_file_name) # END-IF - return os.path.exists(file_name) + return os.path.exists(file_name), file_name @staticmethod def estimate_background(pt_intensity_dict, bg_pt_list): @@ -653,6 +865,15 @@ class CWSCDReductionControl(object): return avg_bg + def get_surveyed_scans(self): + """ + get list of scans that are surveyed + :return: + """ + scan_number_list = [info[1] for info in self._scanSummaryList] + + return scan_number_list + def get_ub_matrix(self, exp_number): """ Get UB matrix assigned to an experiment :param exp_number: @@ -749,33 +970,31 @@ class CWSCDReductionControl(object): return move_tup[1] - def export_to_fullprof(self, exp_number, scan_number_list, user_header, - export_absorption, fullprof_file_name, high_precision): + # TEST Me - This need a lot of work because of single-pt scans + def export_to_fullprof(self, exp_number, scan_roi_list, user_header, + export_absorption, fullprof_file_name, high_precision, + integration_direction='vertical'): """ Export peak intensities to Fullprof data file :param exp_number: - :param scan_number_list: + :param scan_roi_list: list of 2-tuple: (1) scan number (2) roi/mask name :param user_header: - :param export_absorption: + :param export_absorption: requiring UB matrix :param fullprof_file_name: :param high_precision: flag to write peak intensity as f18.5 if true; otherwise, output as f8.2 :return: 2-tuples. status and return object ((mixed) file content or error message) """ # check assert isinstance(exp_number, int), 'Experiment number must be an integer.' - assert isinstance(scan_number_list, list), 'Scan number list must be a list but not %s.' \ - '' % str(type(scan_number_list)) - assert len(scan_number_list) > 0, 'Scan number list must larger than 0, but ' \ - 'now %d. ' % len(scan_number_list) + assert isinstance(scan_roi_list, list), 'Scan number list must be a list but not %s.' \ + '' % str(type(scan_roi_list)) + assert len(scan_roi_list) > 0, 'Scan number list must larger than 0' # get wave-length - try: - exp_wave_length = self.get_wave_length(exp_number, scan_number_list) - except RuntimeError as error: - return False, 'RuntimeError: %s.' % str(error) + scan_number_list = [t[0] for t in scan_roi_list] + exp_wave_length = self.get_wave_length(exp_number, scan_number_list) # get the information whether there is any k-shift vector specified by user - # form k-shift and peak intensity information scan_kindex_dict = dict() k_shift_dict = dict() @@ -789,27 +1008,46 @@ class CWSCDReductionControl(object): error_message = 'Number of scans with k-shift must either be 0 (no shift at all) or ' \ 'equal to or larger than the number scans to export.' - assert len(scan_kindex_dict) == 0 or len(scan_kindex_dict) >= len(scan_number_list), error_message + assert len(scan_kindex_dict) == 0 or len(scan_kindex_dict) >= len(scan_roi_list), error_message # form peaks no_shift = len(scan_kindex_dict) == 0 - # get ub matrix - ub_matrix = self.get_ub_matrix(exp_number) + # get ub matrix in the case of export absorption + if export_absorption: + try: + ub_matrix = self.get_ub_matrix(exp_number) + except RuntimeError as err: + raise RuntimeError('It is required to have UB matrix set up for exporting absorption\n(error ' + 'message: {0}'.format(err)) + else: + ub_matrix = None - mixed_content = None + mixed_content = 'Nothing is written' for algorithm_type in ['simple', 'mixed', 'gauss']: # set list of peaks for exporting peaks = list() - for scan_number in scan_number_list: + for scan_number, roi_name in scan_roi_list: + # create a single peak information dictionary for peak_dict = dict() + + # get peak-info object + if (exp_number, scan_number) in self._myPeakInfoDict: + peak_info = self._myPeakInfoDict[exp_number, scan_number] + else: + pt_number = 1 + peak_info = self._single_pt_integration_dict[exp_number, scan_number, pt_number, roi_name] + peak_info = peak_info[integration_direction] + + # get HKL try: - peak_dict['hkl'] = self._myPeakInfoDict[(exp_number, scan_number)].get_hkl(user_hkl=True) + peak_dict['hkl'] = peak_info.get_hkl(user_hkl=True) + # self._myPeakInfoDict[(exp_number, scan_number)].get_hkl(user_hkl=True) except RuntimeError as run_err: - return False, str('Peak index error: %s.' % run_err) + raise RuntimeError('Peak index error: {0}.'.format(run_err)) - intensity, std_dev = self._myPeakInfoDict[(exp_number, scan_number)].get_intensity( - algorithm_type, lorentz_corrected=True) + intensity, std_dev = peak_info.get_intensity(algorithm_type, lorentz_corrected=True) + # self._myPeakInfoDict[(exp_number, scan_number)] if intensity < std_dev: # error is huge, very likely bad gaussian fit @@ -841,23 +1079,18 @@ class CWSCDReductionControl(object): # get file name for this type this_file_name = fullprof_file_name.split('.')[0] + '_' + algorithm_type + '.dat' - try: - file_content = fputility.write_scd_fullprof_kvector( - user_header=user_header, wave_length=exp_wave_length, - k_vector_dict=k_shift_dict, peak_dict_list=peaks, - fp_file_name=this_file_name, with_absorption=export_absorption, - high_precision=high_precision) - if algorithm_type == 'mixed': - mixed_content = file_content - except AssertionError as error: - return False, 'AssertionError: %s.' % str(error) - except RuntimeError as error: - return False, 'RuntimeError: %s.' % str(error) + file_content = fputility.write_scd_fullprof_kvector( + user_header=user_header, wave_length=exp_wave_length, + k_vector_dict=k_shift_dict, peak_dict_list=peaks, + fp_file_name=this_file_name, with_absorption=export_absorption, + high_precision=high_precision) + if algorithm_type == 'mixed': + mixed_content = file_content continue # END-FOR - return True, mixed_content + return mixed_content def export_md_data(self, exp_number, scan_number, base_file_name): """ @@ -883,6 +1116,76 @@ class CWSCDReductionControl(object): return out_file_name + def find_scans_by_2theta(self, exp_number, two_theta, resolution, excluded_scans): + """ + find scans by 2theta (same or similar) + :param exp_number: + :param two_theta: + :param resolution: + :param excluded_scans: + :return: + """ + # check inputs + assert isinstance(exp_number, int), 'Exp number {0} must be integer'.format(exp_number) + assert isinstance(two_theta, float), '2-theta {0} must be a float.'.format(two_theta) + assert isinstance(resolution, float), 'Resolution {0} must be a float.'.format(resolution) + assert isinstance(excluded_scans, list), 'Excluded scans {0} must be a list.'.format(excluded_scans) + + # get the list of scans in the memory + have_change = False + for scan_sum in self._scanSummaryList: + # get scan number + scan_number = scan_sum[1] + pt_number = scan_sum[2] + if scan_number in self._scan_2theta_set: + # already parsed + continue + + have_change = True + # get 2theta + two_theta_i = float(self.get_sample_log_value(exp_number, scan_number, pt_number, '2theta')) + self._two_theta_scan_dict[two_theta_i] = scan_number + self._scan_2theta_set.add(scan_number) + # END-FOR + + # check as an exception whether there are multiple scans with exactly same two theta + if len(self._two_theta_scan_dict) != len(self._scan_2theta_set): + raise RuntimeError('Exception case: scans with exactly same 2theta! FindScanBy2Theta fails!') + + # sort 2thetas and index two thetas within a certain range + two_theta_list = numpy.array(sorted(self._two_theta_scan_dict.keys())) + + min_2theta = two_theta - resolution + max_2theta = two_theta + resolution + + min_index = bisect.bisect_left(two_theta_list, min_2theta) + max_index = bisect.bisect_left(two_theta_list, max_2theta) + + # debug output + if have_change: + pass + # print('[DB...BAT] Dict size = {0}; Scan set size = {1}'.format(len(self._two_theta_scan_dict), + # len(self._scan_2theta_set))) + # print('[DB...BAT] 2theta list: {0}'.format(two_theta_list)) + + # print ('[DB..BAT] Input 2theta = {0}; 2-thetas in range: {1}' + # ''.format(two_theta, two_theta_list[min_index:max_index])) + # print ('[DB...BAT] index range: {0}, {1}'.format(min_index, max_index)) + + scans_set = set([self._two_theta_scan_dict[two_theta_j] for two_theta_j in two_theta_list[min_index:max_index]]) + # print ('[DB...BAT] Find scans: {0} Excluded scans {1}'.format(scans_set, set(excluded_scans))) + scans_set = scans_set - set(excluded_scans) + # print ('[DB...BAT] Find scans: {0}'.format(scans_set)) + + # matched scans by removing single-pt scans + matched_scans = list(scans_set) + for scan_number in matched_scans: + spice_table = self._get_spice_workspace(exp_number, scan_number) + if spice_table.rowCount() == 1: + matched_scans.remove(scan_number) + + return matched_scans + def get_experiment(self): """ Get experiment number @@ -899,8 +1202,8 @@ class CWSCDReductionControl(object): # Check if exp_no is None: exp_no = self._expNumber - assert isinstance(exp_no, int) - assert isinstance(scan_no, int) + assert isinstance(exp_no, int), 'Experiment number {0} must be an integer'.format(exp_no) + assert isinstance(scan_no, int), 'Scan number {0} must be an integer'.format(scan_no) # Get workspace status, ret_obj = self.load_spice_scan_file(exp_no, scan_no) @@ -971,7 +1274,8 @@ class CWSCDReductionControl(object): if roi_name not in self._roiDict: # ROI: not saved - raise RuntimeError('ROI not here blabla') + raise RuntimeError('ROI {0} is not in ROI dictionary which has keys {1}' + ''.format(roi_name, self._roiDict.keys())) # check... lower_left_corner = self._roiDict[roi_name].lower_left_corner @@ -982,9 +1286,16 @@ class CWSCDReductionControl(object): return lower_left_corner, upper_right_corner + def get_region_of_interest_list(self): + """ + Get the list of all the ROIs defined + :return: + """ + return sorted(self._roiDict.keys()) + def get_sample_log_value(self, exp_number, scan_number, pt_number, log_name): """ - Get sample log's value + Get sample log's value from merged data! :param exp_number: :param scan_number:167 :param pt_number: @@ -995,16 +1306,29 @@ class CWSCDReductionControl(object): assert isinstance(scan_number, int) assert isinstance(pt_number, int) assert isinstance(log_name, str) - try: - status, pt_number_list = self.get_pt_numbers(exp_number, scan_number) - assert status - md_ws_name = get_merged_md_name(self._instrumentName, exp_number, - scan_number, pt_number_list) - md_ws = AnalysisDataService.retrieve(md_ws_name) - except KeyError as ke: - return 'Unable to find log value %s due to %s.' % (log_name, str(ke)) - return md_ws.getExperimentInfo(0).run().getProperty(log_name).value + # access data from SPICE table + # TODO FIXME THIS IS A HACK! + if log_name == '2theta': + spice_table_name = get_spice_table_name(exp_number, scan_number) + print ('[DB...BAT] Scan {0} Spice Table {1}'.format(scan_number, spice_table_name)) + spice_table_ws = AnalysisDataService.retrieve(spice_table_name) + log_value = spice_table_ws.toDict()[log_name][0] + + else: + + try: + status, pt_number_list = self.get_pt_numbers(exp_number, scan_number) + assert status + md_ws_name = get_merged_md_name(self._instrumentName, exp_number, + scan_number, pt_number_list) + md_ws = AnalysisDataService.retrieve(md_ws_name) + except KeyError as ke: + return 'Unable to find log value %s due to %s.' % (log_name, str(ke)) + + log_value = md_ws.getExperimentInfo(0).run().getProperty(log_name).value + + return log_value def get_merged_data(self, exp_number, scan_number, pt_number_list): """ @@ -1040,11 +1364,11 @@ class CWSCDReductionControl(object): def get_peak_info(self, exp_number, scan_number, pt_number=None): """ - get PeakInfo instance + get PeakInfo instance, which including :param exp_number: experiment number :param scan_number: :param pt_number: - :return: PeakInfo instance or None + :return: peakprocesshelper.PeakProcessRecord or None """ # Check for type assert isinstance(exp_number, int), 'Experiment %s must be an integer but not of type %s.' \ @@ -1111,6 +1435,59 @@ class CWSCDReductionControl(object): return vec_x, vec_y + def get_peak_integration_parameters(self, xlabel='2theta', ylabel=None, with_error=True): + """ + get the parameters from peak integration + :param xlabel: parameter name for x value + :param ylabel: parameter name for y value + :param with_error: If true, then output error + :return: + """ + # convert all kinds of y-label to a list of strings for y-label + if ylabel is None: + ylabel = ['sigma'] + elif isinstance(ylabel, str): + ylabel = [ylabel] + + # create list of output + param_list = list() + for (exp_number, scan_number) in self._myPeakInfoDict.keys(): + peak_int_info = self._myPeakInfoDict[exp_number, scan_number] + + # x value + try: + x_value = peak_int_info.get_parameter(xlabel)[0] + except RuntimeError as run_err: + print ('[ERROR] Exp {} Scan {}: {}'.format(exp_number, scan_number, run_err)) + continue + + # set up + scan_i = [x_value] + + for param_name in ylabel: + if param_name.lower() == 'scan': + # scan number + y_value = scan_number + scan_i.append(y_value) + else: + # parameter name + y_value, e_value = peak_int_info.get_parameter(param_name.lower()) + scan_i.append(y_value) + if with_error: + scan_i.append(e_value) + # END-FOR + param_list.append(scan_i) + # END-FOR + + if len(param_list) == 0: + raise RuntimeError('No integrated peak is found') + + # convert to a matrix + param_list.sort() + xye_matrix = numpy.array(param_list) + + return xye_matrix + def generate_mask_workspace(self, exp_number, scan_number, roi_start, roi_end, mask_tag=None): """ Generate a mask workspace :param exp_number: @@ -1286,6 +1663,23 @@ class CWSCDReductionControl(object): return has + def import_2theta_gauss_sigma_file(self, twotheta_sigma_file_name): + """ import a 2theta-sigma column file + :param twotheta_sigma_file_name: + :return: (numpy.array, numpy.array) : vector X and vector y + """ + assert isinstance(twotheta_sigma_file_name, str), 'Input file name {0} must be a string but not a {1}.' \ + ''.format(twotheta_sigma_file_name, + type(twotheta_sigma_file_name)) + if os.path.exists(twotheta_sigma_file_name) is False: + raise RuntimeError('2theta-sigma file {0} does not exist.'.format(twotheta_sigma_file_name)) + + vec_2theta, vec_sigma = numpy.loadtxt(twotheta_sigma_file_name, delimiter=' ', usecols=(0, 1), unpack=True) + # TODO - 20180814 - shall be noted as single-pt scan... + self._two_theta_sigma = vec_2theta, vec_sigma + + return vec_2theta, vec_sigma + def index_peak(self, ub_matrix, scan_number, allow_magnetic=False): """ Index peaks in a Pt. by create a temporary PeaksWorkspace which contains only 1 peak :param ub_matrix: numpy.ndarray (3, 3) @@ -1490,6 +1884,220 @@ class CWSCDReductionControl(object): return peak_intensity, gauss_bkgd, info_str + def integrate_single_pt_scans_detectors_counts(self, exp_number, scan_number_list, roi_name, integration_direction, + fit_gaussian): + """ + integrate a list of single-pt scans detector counts with fit with Gaussian function as an option + :param exp_number: + :param scan_number_list: + :param roi_name: + :param integration_direction: + :param fit_gaussian: + :return: a dictionary of peak height + """ + # check inputs + check_list('Scan numbers', scan_number_list) + + # get the workspace key. if it does exist, it means there is no need to sum the data but just get from dict + ws_record_key = self.generate_single_pt_scans_key(exp_number, scan_number_list, roi_name, + integration_direction) + print ('[DB...BAT] Retrieve ws record key: {}'.format(ws_record_key)) + + if ws_record_key in self._single_pt_matrix_dict: + # it does exist. get the workspace name + integration_manager = self._single_pt_matrix_dict[ws_record_key] + out_ws_name = integration_manager.get_workspace() + print ('[DB...TRACE] workspace key {} does exist: workspace name = {}' + ''.format(ws_record_key, out_ws_name)) + else: + # it does not exist. sum over all the scans and create the workspace + out_ws_name = 'Exp{}_Scan{}-{}_{}_{}'.format(exp_number, scan_number_list[0], scan_number_list[-1], + roi_name, integration_direction) + + print('[DB...TRACE] workspace key {} does not exist. Integrate and generate workspace {}.' + ''.format(ws_record_key, out_ws_name)) + + # initialize the vectors to form a Mantid Workspace2D + appended_vec_x = None + appended_vec_y = None + appended_vec_e = None + + scan_spectrum_map = dict() + spectrum_scan_map = dict() + + for ws_index, scan_number in enumerate(scan_number_list): + + scan_spectrum_map[scan_number] = ws_index + spectrum_scan_map[ws_index] = scan_number + + pt_number = 1 + self.integrate_detector_image(exp_number, scan_number, pt_number, roi_name, + integration_direction=integration_direction, + fit_gaussian=fit_gaussian) + + # create a workspace + det_integration_info = \ + self._single_pt_integration_dict[(exp_number, scan_number, pt_number, roi_name)][ + integration_direction] + vec_x, vec_y = det_integration_info.get_vec_x_y() + if appended_vec_x is None: + appended_vec_x = vec_x + appended_vec_y = vec_y + appended_vec_e = numpy.sqrt(vec_y) + else: + appended_vec_x = numpy.concatenate((appended_vec_x, vec_x), axis=0) + appended_vec_y = numpy.concatenate((appended_vec_y, vec_y), axis=0) + appended_vec_e = numpy.concatenate((appended_vec_e, numpy.sqrt(vec_y)), axis=0) + # END-IF + # END-FOR + + # create workspace + mantidsimple.CreateWorkspace(DataX=appended_vec_x, DataY=appended_vec_y, + DataE=appended_vec_e, NSpec=len(scan_number_list), + OutputWorkspace=out_ws_name) + + # record the workspace + integration_manager = SinglePtScansIntegrationOperation(exp_number, scan_number_list, out_ws_name, + scan_spectrum_map, spectrum_scan_map) + + self._single_pt_matrix_dict[ws_record_key] = integration_manager + self._current_single_pt_integration_key = ws_record_key + # END-IF-ELSE + + # about result: peak height + peak_height_dict = dict() + for scan_number in scan_number_list: + peak_height_dict[scan_number] = 0. + + # fit gaussian + if fit_gaussian: + # for mantid Gaussian, 'peakindex', 'Height', 'PeakCentre', 'Sigma', 'A0', 'A1', 'chi2' + fit_result_dict, model_ws_name = peak_integration_utility.fit_gaussian_linear_background_mtd(out_ws_name) + # digest fit parameters + for ws_index in sorted(fit_result_dict.keys()): + scan_number = integration_manager.get_scan_number(ws_index, from_zero=True) + integrate_record_i = \ + self._single_pt_integration_dict[(exp_number, scan_number, 1, roi_name)][integration_direction] + integrate_record_i.set_fit_cost(fit_result_dict[ws_index]['chi2']) + integrate_record_i.set_fit_params(x0=fit_result_dict[ws_index]['PeakCentre'], + sigma=fit_result_dict[ws_index]['Sigma'], + a0=fit_result_dict[ws_index]['A0'], + a1=fit_result_dict[ws_index]['A1'], + height=fit_result_dict[ws_index]['Height']) + peak_height_dict[scan_number] = fit_result_dict[ws_index]['Height'] + # END-FOR + + # workspace + integration_manager.set_model_workspace(model_ws_name) + + # print ('[DB..BAT] SinglePt-Scan: cost = {0}, params = {1}, integrated = {2} +/- {3}' + # ''.format(cost, params, integrated_intensity, intensity_error)) + # END-IF + + return peak_height_dict + + def integrate_detector_image(self, exp_number, scan_number, pt_number, roi_name, fit_gaussian, + integration_direction): + """ Integrate detector counts on detector image inside a given ROI. + Integration is either along X-direction (summing along rows) or Y-direction (summing along columns) + Peak fitting is removed from this method + :param exp_number: + :param scan_number: + :param pt_number: + :param roi_name: + :param fit_gaussian: + :param integration_direction: horizontal (integrate along X direction) or vertical (integrate along Y direction) + :return: + """ + # check data loaded with mask information + does_loaded = self.does_raw_loaded(exp_number, scan_number, pt_number, roi_name) + if not does_loaded: + # load SPICE table + self.load_spice_scan_file(exp_number, scan_number) + # load Pt xml + self.load_spice_xml_file(exp_number, scan_number, pt_number) + # END-IF + + # check integration direction + assert isinstance(integration_direction, str) and integration_direction in ['vertical', 'horizontal'],\ + 'Integration direction {} (now of type {}) must be a string equal to eiether vertical or horizontal' \ + ''.format(integration_direction, type(integration_direction)) + + # check whether the first step integration been done + roi_key = exp_number, scan_number, pt_number, roi_name + if roi_key in self._single_pt_integration_dict\ + and integration_direction in self._single_pt_integration_dict[roi_key]: + sum_counts = False + else: + sum_counts = True + + # Get data and plot + if sum_counts: + raw_det_data = self.get_raw_detector_counts(exp_number, scan_number, pt_number) + assert isinstance(raw_det_data, numpy.ndarray), 'A matrix must be an ndarray but not {0}.' \ + ''.format(type(raw_det_data)) + roi_lower_left, roi_upper_right = self.get_region_of_interest(roi_name) + + data_in_roi = raw_det_data[roi_lower_left[0]:roi_upper_right[0], roi_lower_left[1]:roi_upper_right[1]] + print('IN ROI: Data set shape: {}'.format(data_in_roi.shape)) + + if integration_direction == 'horizontal': + # FIXME - This works! + # integrate peak along row + print(roi_lower_left[1], roi_upper_right[1]) + vec_x = numpy.array(range(roi_lower_left[1], roi_upper_right[1])) + vec_y = raw_det_data[roi_lower_left[0]:roi_upper_right[0], roi_lower_left[1]:roi_upper_right[1]].sum( + axis=0) + elif integration_direction == 'vertical': + # integrate peak along column + # FIXME - This doesn't work! + print(roi_lower_left[0], roi_upper_right[0]) + vec_x = numpy.array(range(roi_lower_left[0], roi_upper_right[0])) + vec_y = raw_det_data[roi_lower_left[0]:roi_upper_right[0], roi_lower_left[1]:roi_upper_right[1]].sum( + axis=1) + print('[DB...BAT] Vec X shape: {}; Vec Y shape: {}'.format(vec_x.shape, vec_y.shape)) + else: + # wrong + raise NotImplementedError('It is supposed to be unreachable.') + # END-IF-ELSE + + # initialize integration record + # get 2theta + two_theta = self.get_sample_log_value(self._expNumber, scan_number, pt_number, '2theta') + # create SinglePointPeakIntegration + integrate_record = SinglePointPeakIntegration(exp_number, scan_number, roi_name, pt_number, two_theta) + integrate_record.set_xy_vector(vec_x, vec_y, integration_direction) + # add the _single_pt_integration_dict() + if (exp_number, scan_number, pt_number, roi_name) not in self._single_pt_integration_dict: + self._single_pt_integration_dict[exp_number, scan_number, pt_number, roi_name] = dict() + self._single_pt_integration_dict[exp_number, scan_number, pt_number, roi_name][integration_direction] = \ + integrate_record + # else: + # # retrieve the integration record from previously saved + # integrate_record = self._single_pt_integration_dict[roi_key][integration_direction] + # # vec_x, vec_y = integrate_record.get_vec_x_y() + # END-IF + + # if fit_gaussian: + # cost, params, cov_matrix = peak_integration_utility.fit_gaussian_linear_background(vec_x, vec_y, + # numpy.sqrt(vec_y)) + # gaussian_a = params[2] + # gaussian_sigma = params[1] + # integrated_intensity, intensity_error = \ + # peak_integration_utility.calculate_peak_intensity_gauss(gaussian_a, gaussian_sigma) + # print ('[DB..BAT] SinglePt-Scan: cost = {0}, params = {1}, integrated = {2} +/- {3}' + # ''.format(cost, params, integrated_intensity, intensity_error)) + # + # else: + # cost = -1 + # params = dict() + # integrated_intensity = 0. + # + # integrate_record.set_fit_cost(cost) + # integrate_record.set_fit_params(x0=params[0], sigma=params[1], a=params[2], b=params[3]) + + return + @staticmethod def load_scan_survey_file(csv_file_name): """ Load scan survey from a csv file @@ -1580,6 +2188,21 @@ class CWSCDReductionControl(object): return True, out_ws_name + def remove_pt_xml_workspace(self, exp_no, scan_no, pt_no): + """ + remove the Workspace2D loaded from SPICE XML detector file + :param exp_no: + :param scan_no: + :param pt_no: + :return: + """ + pt_ws_name = get_raw_data_workspace_name(exp_no, scan_no, pt_no) + + if AnalysisDataService.doesExist(pt_ws_name): + AnalysisDataService.remove(pt_ws_name) + + return + def load_spice_xml_file(self, exp_no, scan_no, pt_no, xml_file_name=None): """ Load SPICE's detector counts XML file from local data directory @@ -1832,6 +2455,21 @@ class CWSCDReductionControl(object): return roi_range + def load_peak_integration_table(self, table_file_name): + """ + load peak integration table + :param table_file_name: + :return: + """ + # load to a dictionary + try: + scan_peak_dict = peak_integration_utility.read_peak_integration_table_csv(table_file_name) + self._myLoadedPeakInfoDict.update(scan_peak_dict) + except RuntimeError as run_error: + return False, 'Failed to read: {0}'.format(run_error) + + return True, None + def load_preprocessed_scan(self, exp_number, scan_number, md_dir, output_ws_name): """ load preprocessed scan from hard disk :return: (bool, str): loaded, message @@ -2174,6 +2812,30 @@ class CWSCDReductionControl(object): return + def get_2theta_fwhm_data(self, min_2theta, res_2theta, max_2theta): + """ + + :param min_2theta: + :param max_2theta: + :return: + """ + if self._curr_2theta_fwhm_func is None: + # user inputs smoothed data for interpolation + raise RuntimeError(blabla) + + else: + # user inputs math equation as model + print ('[DB...BAT] Prepare to evaluate 2theta-FWHM model {}'.format(self._curr_2theta_fwhm_func)) + + vec_2theta = numpy.arange(min_2theta, max_2theta, res_2theta) + vec_model = self._curr_2theta_fwhm_func(vec_2theta) + + # END-IF-ELSE + + vec_fwhm = None + + return vec_2theta, vec_fwhm, vec_model + def get_calibrated_det_center(self, exp_number): """ get calibrated/user-specified detector center or the default center @@ -2403,6 +3065,33 @@ class CWSCDReductionControl(object): return + def set_single_measure_peak_width(self, exp_number, scan_number, pt_number, roi_name, gauss_sigma, is_fhwm=False): + """ + set peak width (Gaussian sigma value) to single-measurement peak + :param scan_number: + :param gauss_sigma: + :param is_fhwm: + :return: + """ + # check input + fourcircle_utility.check_integer('Experiment number', exp_number) + fourcircle_utility.check_integer('Scan number', scan_number) + fourcircle_utility.check_float('Gaussian-sigma', gauss_sigma) + fourcircle_utility.check_string('ROI name', roi_name) + + # get the single-measurement integration instance + dict_key = exp_number, scan_number, pt_number, roi_name + if dict_key in self._single_pt_integration_dict: + int_record = self._single_pt_integration_dict[dict_key] + else: + raise RuntimeError('Scan number {0} shall be in the single-pt integration dictionary. Keys are {1}.' + ''.format(scan_number, self._single_pt_integration_dict.keys())) + + # set sigma: SinglePointPeakIntegration + int_record.set_ref_fwhm(gauss_sigma, is_fhwm) + + return + def set_user_wave_length(self, exp_number, wave_length): """ set the user wave length for future operation @@ -2667,7 +3356,7 @@ class CWSCDReductionControl(object): file_name = '%s.csv' % file_name # Write file - titles = ['Max Counts', 'Scan', 'Max Counts Pt', 'H', 'K', 'L', 'Q', 'Sample T'] + titles = ['Max Counts', 'Scan', 'Max Counts Pt', 'H', 'K', 'L', 'Q', 'Sample T', '2-theta'] with open(file_name, 'w') as csv_file: csv_writer = csv.writer(csv_file, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL) csv_writer.writerow(titles) @@ -2684,28 +3373,6 @@ class CWSCDReductionControl(object): return - # def save_roi(self, roi_name, region_of_interest): - # """ - # Save region of interest to controller for future use - # :param roi_name: - # :param region_of_interest: a 2-tuple for 2-tuple as lower-left and upper-right corners of the region - # :return: - # """ - # # check - # assert isinstance(roi_name, str), 'Tag {0} must be a string {1}'.format(roi_name, type(roi_name)) - # - # - # # save ROI to ROI dictionary - # self._roiDict[roi_name] = region_of_interest - # - # # if ROI already been used to mask detectors, then need to change mask workspace dictionary - # if self.RESERVED_ROI_NAME in self._maskWorkspaceDict: - # self._maskWorkspaceDict[roi_name] = self._maskWorkspaceDict[self.RESERVED_ROI_NAME] - # del self._maskWorkspaceDict[self.RESERVED_ROI_NAME] - # # END-IF - # - # return - def save_roi_to_file(self, exp_number, scan_number, tag, file_name): """ save ROI to file @@ -2717,10 +3384,6 @@ class CWSCDReductionControl(object): :return: """ # check input - # assert isinstance(exp_number, int), 'Experiment number {0} shall be an integer but not a {1}' \ - # ''.format(exp_number, type(exp_number)) - # assert isinstance(scan_number, int), 'Scan number {0} shall be an integer but not a {1}' \ - # ''.format(scan_number, type(scan_number)) assert isinstance(tag, str), 'Tag {0} shall be a string but not a {1}'.format(tag, type(tag)) assert isinstance(file_name, str), 'File name {0} shall be a string but not a {1}' \ ''.format(file_name, type(file_name)) @@ -2839,16 +3502,18 @@ class CWSCDReductionControl(object): # END-IF # create a PeakInfo instance if it does not exist - peak_info = PeakProcessRecord(exp_number, scan_number, peak_ws_name) - self._myPeakInfoDict[(exp_number, scan_number)] = peak_info + two_theta = self.get_sample_log_value(exp_number, scan_number, 1, '2theta') + self._2thetaLookupTable[two_theta] = exp_number, scan_number + self._myPeakInfoDict[(exp_number, scan_number)] = PeakProcessRecord(exp_number, scan_number, peak_ws_name, + two_theta) # set the other information - peak_info.set_data_ws_name(md_ws_name) - err_msg = peak_info.calculate_peak_center() + self._myPeakInfoDict[(exp_number, scan_number)].set_data_ws_name(md_ws_name) + err_msg = self._myPeakInfoDict[(exp_number, scan_number)].calculate_peak_center() if self._debugPrintMode and len(err_msg) > 0: print ('[Error] during calculating peak center:{0}'.format(err_msg)) - return True, peak_info + return True, self._myPeakInfoDict[(exp_number, scan_number)] def _add_spice_workspace(self, exp_no, scan_no, spice_table_ws): """ @@ -2968,107 +3633,143 @@ class CWSCDReductionControl(object): error_message = '' # Download and + spice_table_name_list = list() + for scan_number in range(start_scan, end_scan+1): # check whether file exists - if self.does_file_exist(exp_number, scan_number) is False: - # SPICE file does not exist in data directory. Download! - # set up URL and target file name - spice_file_url = get_spice_file_url(self._myServerURL, self._instrumentName, exp_number, scan_number) - spice_file_name = get_spice_file_name(self._instrumentName, exp_number, scan_number) - spice_file_name = os.path.join(self._dataDir, spice_file_name) - - # download file and load - try: - mantidsimple.DownloadFile(Address=spice_file_url, Filename=spice_file_name) - except RuntimeError as download_error: - error_message += 'Unable to access/download scan {0} from {1} due to {2}.\n' \ - ''.format(scan_number, spice_file_url, download_error) - continue - else: - spice_file_name = get_spice_file_name(self._instrumentName, exp_number, scan_number) - spice_file_name = os.path.join(self._dataDir, spice_file_name) + file_exist, spice_file_name = self.does_spice_files_exist(exp_number, scan_number) + if not file_exist: + # SPICE file does not exist in data directory. Download + # NOTE Download SPICE file is disabled. An error message will be sent + # out for user to download the data explicitly + error_message += 'SPICE file for Exp {0} Scan {1} does not exist.' \ + '\n'.format(exp_number, scan_number) # Load SPICE file and retrieve information + spice_table_ws_name = fourcircle_utility.get_spice_table_name(exp_number, scan_number) try: - spice_table_ws_name = 'TempTable' - mantidsimple.LoadSpiceAscii(Filename=spice_file_name, - OutputWorkspace=spice_table_ws_name, - RunInfoWorkspace='TempInfo') - spice_table_ws = AnalysisDataService.retrieve(spice_table_ws_name) - num_rows = spice_table_ws.rowCount() - - if num_rows == 0: - # it is an empty table - error_message += 'Scan %d: empty spice table.\n' % scan_number - continue - - col_name_list = spice_table_ws.getColumnNames() - h_col_index = col_name_list.index('h') - k_col_index = col_name_list.index('k') - l_col_index = col_name_list.index('l') - col_2theta_index = col_name_list.index('2theta') - m1_col_index = col_name_list.index('m1') - time_col_index = col_name_list.index('time') - det_count_col_index = col_name_list.index('detector') - # optional as T-Sample - if 'tsample' in col_name_list: - tsample_col_index = col_name_list.index('tsample') - else: - tsample_col_index = None - - max_count = 0 - max_row = 0 - max_h = max_k = max_l = 0 - max_tsample = 0. - - two_theta = m1 = -1 - - for i_row in range(num_rows): - det_count = spice_table_ws.cell(i_row, det_count_col_index) - count_time = spice_table_ws.cell(i_row, time_col_index) - # normalize max count to count time - det_count = float(det_count)/count_time - if det_count > max_count: - max_count = det_count - max_row = i_row - max_h = spice_table_ws.cell(i_row, h_col_index) - max_k = spice_table_ws.cell(i_row, k_col_index) - max_l = spice_table_ws.cell(i_row, l_col_index) - two_theta = spice_table_ws.cell(i_row, col_2theta_index) - m1 = spice_table_ws.cell(i_row, m1_col_index) - # t-sample is not a mandatory sample log in SPICE - if tsample_col_index is None: - max_tsample = 0. - else: - max_tsample = spice_table_ws.cell(i_row, tsample_col_index) - # END-FOR - - # calculate wavelength - wavelength = get_hb3a_wavelength(m1) - if wavelength is None: - q_range = 0. - error_message += 'Scan number {0} has invalid m1 for wavelength.\n'.format(scan_number) - else: - q_range = 4.*math.pi*math.sin(two_theta/180.*math.pi*0.5)/wavelength - - # appending to list - scan_sum_list.append([max_count, scan_number, max_row, max_h, max_k, max_l, - q_range, max_tsample]) + info_list, error_i = self.retrieve_scan_info_spice(scan_number, spice_file_name, spice_table_ws_name) + scan_sum_list.append(info_list) + error_message += error_i + spice_table_name_list.append(spice_table_ws_name) + # TODO FIXME NOW NOW2 - Need a dict [exp] = list() to record all the surveyed scans except RuntimeError as e: return False, None, str(e) + except ValueError as e: # Unable to import a SPICE file without necessary information error_message += 'Scan %d: unable to locate column h, k, or l. See %s.' % (scan_number, str(e)) + # END-FOR (scan_number) - if error_message != '': - print('[Error]\n%s' % error_message) + # group all the SPICE tables + # TEST - If workspace group exists, then add new group but not calling GroupWorkspaces + group_name = fourcircle_utility.get_spice_group_name(exp_number) + if AnalysisDataService.doesExist(group_name): + ws_group = AnalysisDataService.retrieve(group_name) + for table_name in spice_table_name_list: + ws_group.add(table_name) + else: + mantidsimple.GroupWorkspaces(InputWorkspaces=spice_table_name_list, + OutputWorkspace=group_name) + # END-IF-ELSE + + # if error_message != '': + # print('[Error]\n%s' % error_message) - self._scanSummaryList = scan_sum_list + self._scanSummaryList.extend(scan_sum_list) return True, scan_sum_list, error_message + @staticmethod + def retrieve_scan_info_spice(scan_number, spice_file_name, spice_table_ws_name): + """ + process SPICE table + :param scan_number: + :param spice_file_name: + :param spice_table_ws_name: + :return: list/None (information for scan) and string (error message) + """ + # check inputs + if os.path.exists(spice_file_name) is False: + raise RuntimeError('Parsing error') + + error_message = '' + + # Load SPICE file if the workspace does not exist + if AnalysisDataService.doesExist(spice_table_ws_name) is False: + mantidsimple.LoadSpiceAscii(Filename=spice_file_name, + OutputWorkspace=spice_table_ws_name, + RunInfoWorkspace='TempInfo') + + # Get workspace + spice_table_ws = AnalysisDataService.retrieve(spice_table_ws_name) + num_rows = spice_table_ws.rowCount() + + if num_rows == 0: + # it is an empty table + error_message += 'Scan %d: empty spice table.\n' % scan_number + return None, error_message + + # process the columns + col_name_list = spice_table_ws.getColumnNames() + h_col_index = col_name_list.index('h') + k_col_index = col_name_list.index('k') + l_col_index = col_name_list.index('l') + col_2theta_index = col_name_list.index('2theta') + m1_col_index = col_name_list.index('m1') + time_col_index = col_name_list.index('time') + det_count_col_index = col_name_list.index('detector') + # optional as T-Sample + if 'tsample' in col_name_list: + tsample_col_index = col_name_list.index('tsample') + else: + tsample_col_index = None + # 2theta + + # do some simple statistics + max_count = 0 + max_pt = 0 + max_h = max_k = max_l = 0 + max_tsample = 0. + + two_theta = m1 = -1 + + for i_row in range(num_rows): + det_count = spice_table_ws.cell(i_row, det_count_col_index) + count_time = spice_table_ws.cell(i_row, time_col_index) + # normalize max count to count time + det_count = float(det_count) / count_time + if det_count > max_count: + max_count = det_count + max_pt = spice_table_ws.cell(i_row, 0) # pt_col_index is always 0 + max_h = spice_table_ws.cell(i_row, h_col_index) + max_k = spice_table_ws.cell(i_row, k_col_index) + max_l = spice_table_ws.cell(i_row, l_col_index) + two_theta = spice_table_ws.cell(i_row, col_2theta_index) + m1 = spice_table_ws.cell(i_row, m1_col_index) + # t-sample is not a mandatory sample log in SPICE + if tsample_col_index is None: + max_tsample = 0. + else: + max_tsample = spice_table_ws.cell(i_row, tsample_col_index) + # END-FOR + + # calculate wavelength + wavelength = get_hb3a_wavelength(m1) + if wavelength is None: + q_range = 0. + error_message += 'Scan number {0} has invalid m1 for wavelength.\n'.format(scan_number) + else: + q_range = 4. * math.pi * math.sin(two_theta / 180. * math.pi * 0.5) / wavelength + + # appending to list + info_list = [max_count, scan_number, max_pt, max_h, max_k, max_l, + q_range, max_tsample, two_theta] + + return info_list, error_message + def save_project(self, project_file_name, ui_dict): """ Export project - the data structure and information will be written to a ProjectManager file diff --git a/scripts/HFIR_4Circle_Reduction/reduce4circleGUI.py b/scripts/HFIR_4Circle_Reduction/reduce4circleGUI.py index a7a516a04b548867cd816719db480f969c369364..670bd500348abef90ac2f7580a79121510d6ac1d 100644 --- a/scripts/HFIR_4Circle_Reduction/reduce4circleGUI.py +++ b/scripts/HFIR_4Circle_Reduction/reduce4circleGUI.py @@ -35,28 +35,35 @@ from HFIR_4Circle_Reduction import PreprocessWindow from HFIR_4Circle_Reduction.downloaddialog import DataDownloadDialog import HFIR_4Circle_Reduction.refineubfftsetup as refineubfftsetup import HFIR_4Circle_Reduction.PeaksIntegrationReport as PeaksIntegrationReport - - +import HFIR_4Circle_Reduction.IntegrateSingePtSubWindow as IntegrateSingePtSubWindow +import HFIR_4Circle_Reduction.generalplotview as generalplotview # import line for the UI python class -from HFIR_4Circle_Reduction.ui_MainWindow import Ui_MainWindow - -from PyQt4 import QtCore, QtGui - -if six.PY3: - unicode = str - +from HFIR_4Circle_Reduction.hfctables import UBMatrixPeakTable +from HFIR_4Circle_Reduction.hfctables import UBMatrixTable +from HFIR_4Circle_Reduction.hfctables import ProcessTableWidget +from HFIR_4Circle_Reduction.hfctables import ScanSurveyTable +from HFIR_4Circle_Reduction.integratedpeakview import IntegratedPeakView +from HFIR_4Circle_Reduction.detector2dview import Detector2DView +from HFIR_4Circle_Reduction.hfctables import KShiftTableWidget +from HFIR_4Circle_Reduction.hfctables import MatrixTable +from mantid.kernel import Logger +from qtpy.QtWidgets import (QButtonGroup, QFileDialog, QMessageBox, QMainWindow, QInputDialog) # noqa +from qtpy.QtCore import (QSettings) # noqa +from qtpy import QtCore # noqa try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - def _fromUtf8(s): - return s - + from mantidqt.utils.qt import load_ui +except ImportError: + Logger("HFIR_4Circle_Reduction").information('Using legacy ui importer') + from mantidplot import load_ui +from qtpy.QtWidgets import (QVBoxLayout) try: from mantidqtpython import MantidQt except ImportError as e: NO_SCROLL = True else: NO_SCROLL = False +if six.PY3: + unicode = str # define constants IndexFromSpice = 'From Spice (pre-defined)' @@ -64,7 +71,7 @@ IndexFromUB = 'From Calculation By UB' MAGNETIC_TOL = 0.2 -class MainWindow(QtGui.QMainWindow): +class MainWindow(QMainWindow): """ Class of Main Window (top) """ TabPage = {'View Raw Data': 2, @@ -77,11 +84,12 @@ class MainWindow(QtGui.QMainWindow): """ Initialization and set up """ # Base class - QtGui.QMainWindow.__init__(self,parent) + QMainWindow.__init__(self,parent) # UI Window (from Qt Designer) - self.ui = Ui_MainWindow() - self.ui.setupUi(self) + ui_path = "MainWindow.ui" + self.ui = load_ui(__file__, ui_path, baseinstance=self) + self._promote_widgets() # children windows self._my3DWindow = None @@ -93,6 +101,8 @@ class MainWindow(QtGui.QMainWindow): self._preProcessWindow = None self._singlePeakIntegrationDialogBuffer = '' self._dataDownloadDialog = None + self._single_pt_peak_integration_window = None + self._general_1d_plot_window = None # Make UI scrollable if NO_SCROLL is False: @@ -112,231 +122,146 @@ class MainWindow(QtGui.QMainWindow): # Event handling definitions # Top - self.connect(self.ui.pushButton_setExp, QtCore.SIGNAL('clicked()'), - self.do_set_experiment) + self.ui.pushButton_setExp.clicked.connect(self.do_set_experiment) # Tab 'Data Access' - self.connect(self.ui.pushButton_applySetup, QtCore.SIGNAL('clicked()'), - self.do_apply_setup) - self.connect(self.ui.pushButton_browseLocalDataDir, QtCore.SIGNAL('clicked()'), - self.do_browse_local_spice_data) - self.connect(self.ui.pushButton_applyCalibratedSampleDistance, QtCore.SIGNAL('clicked()'), - self.do_set_user_detector_distance) - self.connect(self.ui.pushButton_applyUserDetCenter, QtCore.SIGNAL('clicked()'), - self.do_set_user_detector_center) - self.connect(self.ui.pushButton_applyUserWavelength, QtCore.SIGNAL('clicked()'), - self.do_set_user_wave_length) - self.connect(self.ui.pushButton_applyDetectorSize, QtCore.SIGNAL('clicked()'), - self.do_set_detector_size) + self.ui.pushButton_applySetup.clicked.connect(self.do_apply_setup) + self.ui.pushButton_browseLocalDataDir.clicked.connect(self.do_browse_local_spice_data) + self.ui.pushButton_applyCalibratedSampleDistance.clicked.connect(self.do_set_user_detector_distance) + self.ui.pushButton_applyUserDetCenter.clicked.connect(self.do_set_user_detector_center) + self.ui.pushButton_applyUserWavelength.clicked.connect(self.do_set_user_wave_length) + self.ui.pushButton_applyDetectorSize.clicked.connect(self.do_set_detector_size) # Tab survey - self.connect(self.ui.pushButton_survey, QtCore.SIGNAL('clicked()'), - self.do_survey) - self.connect(self.ui.pushButton_saveSurvey, QtCore.SIGNAL('clicked()'), - self.do_save_survey) - self.connect(self.ui.pushButton_loadSurvey, QtCore.SIGNAL('clicked()'), - self.do_load_survey) - self.connect(self.ui.pushButton_viewSurveyPeak, QtCore.SIGNAL('clicked()'), - self.do_view_survey_peak) - self.connect(self.ui.pushButton_addPeaksToRefine, QtCore.SIGNAL('clicked()'), - self.do_add_peaks_for_ub) - self.connect(self.ui.pushButton_mergeScansSurvey, QtCore.SIGNAL('clicked()'), - self.do_merge_scans_survey) - self.connect(self.ui.pushButton_selectAllSurveyPeaks, QtCore.SIGNAL('clicked()'), - self.do_select_all_survey) - self.connect(self.ui.pushButton_sortInfoTable, QtCore.SIGNAL('clicked()'), - self.do_filter_sort_survey_table) - self.connect(self.ui.pushButton_clearSurvey, QtCore.SIGNAL('clicked()'), - self.do_clear_survey) - self.connect(self.ui.pushButton_viewRawSpice, QtCore.SIGNAL('clicked()'), - self.do_show_spice_file) - - self.connect(self.ui.lineEdit_numSurveyOutput, QtCore.SIGNAL('editingFinished()'), - self.evt_show_survey) - self.connect(self.ui.lineEdit_numSurveyOutput, QtCore.SIGNAL('returnPressed()'), - self.evt_show_survey) - self.connect(self.ui.lineEdit_numSurveyOutput, QtCore.SIGNAL('textEdited(const QString&)'), - self.evt_show_survey) + self.ui.pushButton_survey.clicked.connect(self.do_survey) + self.ui.pushButton_saveSurvey.clicked.connect(self.do_save_survey) + self.ui.pushButton_loadSurvey.clicked.connect(self.do_load_survey) + self.ui.pushButton_viewSurveyPeak.clicked.connect(self.do_view_survey_peak) + self.ui.pushButton_addPeaksToRefine.clicked.connect(self.do_add_peaks_for_ub) + self.ui.pushButton_mergeScansSurvey.clicked.connect(self.do_merge_scans_survey) + self.ui.pushButton_selectAllSurveyPeaks.clicked.connect(self.do_select_all_survey) + self.ui.pushButton_sortInfoTable.clicked.connect(self.do_filter_sort_survey_table) + self.ui.pushButton_clearSurvey.clicked.connect(self.do_clear_survey) + self.ui.pushButton_viewRawSpice.clicked.connect(self.do_show_spice_file) # Tab 'View Raw Data' - self.connect(self.ui.pushButton_setScanInfo, QtCore.SIGNAL('clicked()'), - self.do_load_scan_info) - self.connect(self.ui.pushButton_plotRawPt, QtCore.SIGNAL('clicked()'), - self.do_plot_pt_raw) - self.connect(self.ui.pushButton_prevPtNumber, QtCore.SIGNAL('clicked()'), - self.do_plot_prev_pt_raw) - self.connect(self.ui.pushButton_nextPtNumber, QtCore.SIGNAL('clicked()'), - self.do_plot_next_pt_raw) - self.connect(self.ui.pushButton_showPtList, QtCore.SIGNAL('clicked()'), - self.show_scan_pt_list) - self.connect(self.ui.pushButton_showSPICEinRaw, QtCore.SIGNAL('clicked()'), - self.do_show_spice_file_raw) - self.connect(self.ui.pushButton_switchROIMode, QtCore.SIGNAL('clicked()'), - self.do_switch_roi_mode) - self.connect(self.ui.pushButton_removeROICanvas, QtCore.SIGNAL('clicked()'), - self.do_del_roi) - self.connect(self.ui.pushButton_nextScanNumber, QtCore.SIGNAL('clicked()'), - self.do_plot_next_scan) - self.connect(self.ui.pushButton_prevScanNumber, QtCore.SIGNAL('clicked()'), - self.do_plot_prev_scan) - self.connect(self.ui.pushButton_maskScanPt, QtCore.SIGNAL('clicked()'), - self.do_mask_pt_2d) - self.connect(self.ui.pushButton_integrateROI, QtCore.SIGNAL('clicked()'), - self.do_integrate_roi) - self.connect(self.ui.pushButton_exportMaskToFile, QtCore.SIGNAL('clicked()'), - self.do_export_mask) + self.ui.pushButton_setScanInfo.clicked.connect(self.do_load_scan_info) + self.ui.pushButton_plotRawPt.clicked.connect(self.do_plot_pt_raw) + self.ui.pushButton_prevPtNumber.clicked.connect(self.do_plot_prev_pt_raw) + self.ui.pushButton_nextPtNumber.clicked.connect(self.do_plot_next_pt_raw) + self.ui.pushButton_showPtList.clicked.connect(self.show_scan_pt_list) + self.ui.pushButton_showSPICEinRaw.clicked.connect(self.do_show_spice_file_raw) + self.ui.pushButton_switchROIMode.clicked.connect(self.do_switch_roi_mode) + self.ui.pushButton_removeROICanvas.clicked.connect(self.do_del_roi) + self.ui.pushButton_nextScanNumber.clicked.connect(self.do_plot_next_scan) + self.ui.pushButton_prevScanNumber.clicked.connect(self.do_plot_prev_scan) + self.ui.pushButton_maskScanPt.clicked.connect(self.do_mask_pt_2d) + self.ui.pushButton_integrateROI.clicked.connect(self.do_integrate_roi) + self.ui.pushButton_exportMaskToFile.clicked.connect(self.do_export_mask) # Tab 'calculate ub matrix' - self.connect(self.ui.pushButton_addUBScans, QtCore.SIGNAL('clicked()'), - self.do_add_ub_peaks) - # self.connect(self.ui.pushButton_addPeakToCalUB, QtCore.SIGNAL('clicked()'), - # self.do_add_ub_peak) - self.connect(self.ui.pushButton_calUB, QtCore.SIGNAL('clicked()'), - self.do_cal_ub_matrix) - self.connect(self.ui.pushButton_acceptUB, QtCore.SIGNAL('clicked()'), - self.do_accept_ub) - self.connect(self.ui.pushButton_indexUBPeaks, QtCore.SIGNAL('clicked()'), - self.do_index_ub_peaks) - self.connect(self.ui.pushButton_deleteUBPeak, QtCore.SIGNAL('clicked()'), - self.do_del_ub_peaks) - self.connect(self.ui.pushButton_clearUBPeakTable, QtCore.SIGNAL('clicked()'), - self.do_clear_ub_peaks) - self.connect(self.ui.pushButton_resetPeakHKLs, QtCore.SIGNAL('clicked()'), - self.do_reset_ub_peaks_hkl) - # self.connect(self.ui.pushButton_selectAllPeaks, QtCore.SIGNAL('clicked()'), - # self.do_select_all_peaks) - self.connect(self.ui.pushButton_viewScan3D, QtCore.SIGNAL('clicked()'), - self.do_view_data_3d) - self.connect(self.ui.pushButton_plotSelectedData, QtCore.SIGNAL('clicked()'), - self.do_view_data_set_3d) - self.connect(self.ui.pushButton_setHKL2Int, QtCore.SIGNAL('clicked()'), - self.do_set_ub_tab_hkl_to_integers) - self.connect(self.ui.pushButton_undoSetToInteger, QtCore.SIGNAL('clicked()'), - self.do_undo_ub_tab_hkl_to_integers) - self.connect(self.ui.pushButton_clearIndexing, QtCore.SIGNAL('clicked()'), - self.do_clear_all_peaks_index_ub) - - self.connect(self.ui.pushButton_refineUB, QtCore.SIGNAL('clicked()'), - self.do_refine_ub_indexed_peaks) - self.connect(self.ui.pushButton_refineUBCalIndex, QtCore.SIGNAL('clicked()'), - self.do_refine_ub_cal_indexed_peaks) - - self.connect(self.ui.pushButton_refineUBFFT, QtCore.SIGNAL('clicked()'), - self.do_refine_ub_fft) - self.connect(self.ui.pushButton_findUBLattice, QtCore.SIGNAL('clicked()'), - self.do_refine_ub_lattice) - - self.connect(self.ui.radioButton_ubAdvancedSelection, QtCore.SIGNAL('toggled(bool)'), - self.do_select_all_peaks) - self.connect(self.ui.radioButton_ubSelectAllScans, QtCore.SIGNAL('toggled(bool)'), - self.do_select_all_peaks) - self.connect(self.ui.radioButton_ubSelectNoScan, QtCore.SIGNAL('toggled(bool)'), - self.do_select_all_peaks) + self.ui.pushButton_addUBScans.clicked.connect(self.do_add_ub_peaks) + self.ui.pushButton_calUB.clicked.connect(self.do_cal_ub_matrix) + self.ui.pushButton_acceptUB.clicked.connect(self.do_accept_ub) + self.ui.pushButton_indexUBPeaks.clicked.connect(self.do_index_ub_peaks) + self.ui.pushButton_deleteUBPeak.clicked.connect(self.do_del_ub_peaks) + self.ui.pushButton_clearUBPeakTable.clicked.connect(self.do_clear_ub_peaks) + self.ui.pushButton_resetPeakHKLs.clicked.connect(self.do_reset_ub_peaks_hkl) + self.ui.pushButton_viewScan3D.clicked.connect(self.do_view_data_3d) + self.ui.pushButton_plotSelectedData.clicked.connect(self.do_view_data_set_3d) + self.ui.pushButton_setHKL2Int.clicked.connect(self.do_set_ub_tab_hkl_to_integers) + self.ui.pushButton_undoSetToInteger.clicked.connect(self.do_undo_ub_tab_hkl_to_integers) + self.ui.pushButton_clearIndexing.clicked.connect(self.do_clear_all_peaks_index_ub) + + self.ui.pushButton_refineUB.clicked.connect(self.do_refine_ub_indexed_peaks) + self.ui.pushButton_refineUBCalIndex.clicked.connect(self.do_refine_ub_cal_indexed_peaks) + + self.ui.pushButton_refineUBFFT.clicked.connect(self.do_refine_ub_fft) + self.ui.pushButton_findUBLattice.clicked.connect(self.do_refine_ub_lattice) + + self.ui.radioButton_ubAdvancedSelection.toggled.connect(self.do_select_all_peaks) + self.ui.radioButton_ubSelectAllScans.toggled.connect(self.do_select_all_peaks) + self.ui.radioButton_ubSelectNoScan.toggled.connect(self.do_select_all_peaks) # Tab 'Setup' - self.connect(self.ui.pushButton_browseWorkDir, QtCore.SIGNAL('clicked()'), - self.do_browse_working_dir) - self.connect(self.ui.comboBox_instrument, QtCore.SIGNAL('currentIndexChanged(int)'), - self.do_change_instrument_name) - self.connect(self.ui.pushButton_browsePreprocessed, QtCore.SIGNAL('clicked()'), - self.do_browse_preprocessed_dir) + self.ui.pushButton_browseWorkDir.clicked.connect(self.do_browse_working_dir) + self.ui.comboBox_instrument.currentIndexChanged.connect(self.do_change_instrument_name) + self.ui.pushButton_browsePreprocessed.clicked.connect(self.do_browse_preprocessed_dir) # Tab 'UB Matrix' - self.connect(self.ui.pushButton_showUB2Edit, QtCore.SIGNAL('clicked()'), - self.do_show_ub_in_box) - self.connect(self.ui.pushButton_syncUB, QtCore.SIGNAL('clicked()'), - self.do_sync_ub) - self.connect(self.ui.pushButton_saveUB, QtCore.SIGNAL('clicked()'), - self.do_save_ub) + self.ui.pushButton_showUB2Edit.clicked.connect(self.do_show_ub_in_box) + self.ui.pushButton_syncUB.clicked.connect(self.do_sync_ub) + self.ui.pushButton_saveUB.clicked.connect(self.do_save_ub) # Tab 'Scans Processing' - self.connect(self.ui.pushButton_addScanSliceView, QtCore.SIGNAL('clicked()'), - self.do_add_scans_merge) - self.connect(self.ui.pushButton_mergeScans, QtCore.SIGNAL('clicked()'), - self.do_merge_scans) - self.connect(self.ui.pushButton_integratePeaks, QtCore.SIGNAL('clicked()'), - self.do_integrate_peaks) - self.connect(self.ui.pushButton_setupPeakIntegration, QtCore.SIGNAL('clicked()'), - self.do_switch_tab_peak_int) - self.connect(self.ui.pushButton_refreshMerged, QtCore.SIGNAL('clicked()'), - self.do_refresh_merged_scans_table) - self.connect(self.ui.pushButton_plotMergedScans, QtCore.SIGNAL('clicked()'), - self.do_view_merged_scans_3d) - self.connect(self.ui.pushButton_showUB, QtCore.SIGNAL('clicked()'), - self.do_view_ub) - self.connect(self.ui.pushButton_exportPeaks, QtCore.SIGNAL('clicked()'), - self.do_export_to_fp) - self.connect(self.ui.pushButton_selectAllScans2Merge, QtCore.SIGNAL('clicked()'), - self.do_select_merged_scans) - self.connect(self.ui.pushButton_indexMergedScans, QtCore.SIGNAL('clicked()'), - self.do_index_merged_scans_peaks) - self.connect(self.ui.pushButton_applyKShift, QtCore.SIGNAL('clicked()'), - self.do_apply_k_shift) - self.connect(self.ui.pushButton_clearMergeScanTable, QtCore.SIGNAL('clicked()'), - self.do_clear_merge_table) - self.connect(self.ui.pushButton_multipleScans, QtCore.SIGNAL('clicked()'), - self.do_merge_multi_scans) - self.connect(self.ui.pushButton_convertMerged2HKL, QtCore.SIGNAL('clicked()'), - self.do_convert_merged_to_hkl) - self.connect(self.ui.pushButton_showScanWSInfo, QtCore.SIGNAL('clicked()'), - self.do_show_workspaces) - self.connect(self.ui.pushButton_showIntegrateDetails, QtCore.SIGNAL('clicked()'), - self.do_show_integration_details) - self.connect(self.ui.pushButton_toggleIntegrateType, QtCore.SIGNAL('clicked()'), - self.do_toggle_table_integration) - self.connect(self.ui.pushButton_exportSelectedPeaks, QtCore.SIGNAL('clicked()'), - self.do_export_selected_peaks_to_integrate) + self.ui.pushButton_addScanSliceView.clicked.connect(self.do_add_scans_merge) + self.ui.pushButton_mergeScans.clicked.connect(self.do_merge_scans) + self.ui.pushButton_integratePeaks.clicked.connect(self.do_integrate_peaks) + self.ui.pushButton_setupPeakIntegration.clicked.connect(self.do_switch_tab_peak_int) + self.ui.pushButton_refreshMerged.clicked.connect(self.do_refresh_merged_scans_table) + self.ui.pushButton_plotMergedScans.clicked.connect(self.do_view_merged_scans_3d) + self.ui.pushButton_showUB.clicked.connect(self.do_view_ub) + self.ui.pushButton_exportPeaks.clicked.connect(self.do_export_to_fp) + self.ui.pushButton_selectAllScans2Merge.clicked.connect(self.do_select_merged_scans) + self.ui.pushButton_indexMergedScans.clicked.connect(self.do_index_merged_scans_peaks) + self.ui.pushButton_applyKShift.clicked.connect(self.do_apply_k_shift) + self.ui.pushButton_clearMergeScanTable.clicked.connect(self.do_clear_merge_table) + self.ui.pushButton_multipleScans.clicked.connect(self.do_merge_multi_scans) + self.ui.pushButton_convertMerged2HKL.clicked.connect(self.do_convert_merged_to_hkl) + self.ui.pushButton_showScanWSInfo.clicked.connect(self.do_show_workspaces) + self.ui.pushButton_showIntegrateDetails.clicked.connect(self.do_show_integration_details) + self.ui.pushButton_toggleIntegrateType.clicked.connect(self.do_toggle_table_integration) + self.ui.pushButton_exportSelectedPeaks.clicked.connect(self.do_export_selected_peaks_to_integrate) # Tab 'Integrate (single) Peaks' - self.connect(self.ui.pushButton_integratePt, QtCore.SIGNAL('clicked()'), - self.do_integrate_single_scan) - self.connect(self.ui.comboBox_ptCountType, QtCore.SIGNAL('currentIndexChanged(int)'), - self.evt_change_normalization) # calculate the normalized data again - self.connect(self.ui.pushButton_showIntPeakDetails, QtCore.SIGNAL('clicked()'), - self.do_show_single_peak_integration) - self.connect(self.ui.pushButton_clearPeakIntFigure, QtCore.SIGNAL('clicked()'), - self.do_clear_peak_integration_canvas) + self.ui.pushButton_integratePt.clicked.connect(self.do_integrate_single_scan) + self.ui.comboBox_ptCountType.currentIndexChanged.connect(self.evt_change_normalization) + + # calculate the normalized data again + self.ui.pushButton_showIntPeakDetails.clicked.connect(self.do_show_single_peak_integration) + self.ui.pushButton_clearPeakIntFigure.clicked.connect(self.do_clear_peak_integration_canvas) + + self.ui.lineEdit_numSurveyOutput.editingFinished.connect(self.evt_show_survey) + self.ui.lineEdit_numSurveyOutput.returnPressed.connect(self.evt_show_survey) + self.ui.lineEdit_numSurveyOutput.textEdited.connect(self.evt_show_survey) + self.ui.pushButton_exportToMovie.clicked.connect(self.do_export_detector_views_to_movie) self.ui.comboBox_viewRawDataMasks.currentIndexChanged.connect(self.evt_change_roi) self.ui.comboBox_mergePeakNormType.currentIndexChanged.connect(self.evt_change_norm_type) # Tab k-shift vector - self.connect(self.ui.pushButton_addKShift, QtCore.SIGNAL('clicked()'), - self.do_add_k_shift_vector) + self.ui.pushButton_addKShift.clicked.connect(self.do_add_k_shift_vector) # Menu and advanced tab - self.connect(self.ui.actionExit, QtCore.SIGNAL('triggered()'), - self.menu_quit) + self.ui.actionExit.triggered.connect(self.menu_quit) + self.ui.actionSave_Session.triggered.connect(self.save_current_session) + self.ui.actionLoad_Session.triggered.connect(self.load_session) + self.ui.actionLoad_Mask.triggered.connect(self.menu_load_mask) - self.connect(self.ui.actionSave_Session, QtCore.SIGNAL('triggered()'), - self.save_current_session) - self.connect(self.ui.actionLoad_Session, QtCore.SIGNAL('triggered()'), - self.load_session) - self.connect(self.ui.actionLoad_Mask, QtCore.SIGNAL('triggered()'), - self.menu_load_mask) + self.ui.actionSave_Project.triggered.connect(self.action_save_project) + self.ui.actionOpen_Project.triggered.connect(self.action_load_project) + self.ui.actionOpen_Last_Project.triggered.connect(self.action_load_last_project) - self.connect(self.ui.actionSave_Project, QtCore.SIGNAL('triggered()'), - self.action_save_project) - self.connect(self.ui.actionOpen_Project, QtCore.SIGNAL('triggered()'), - self.action_load_project) - self.connect(self.ui.actionOpen_Last_Project, QtCore.SIGNAL('triggered()'), - self.action_load_last_project) + self.ui.pushButton_loadLastNthProject.clicked.connect(self.do_load_nth_project) - self.connect(self.ui.pushButton_loadLastNthProject, QtCore.SIGNAL('clicked()'), - self.do_load_nth_project) + self.ui.actionPre_Processing.triggered.connect(self.menu_pre_process) + self.ui.actionData_Downloading.triggered.connect(self.menu_download_data) - self.connect(self.ui.actionPre_Processing, QtCore.SIGNAL('triggered()'), - self.menu_pre_process) + self.ui.actionSingle_Pt_Integration.triggered.connect(self.menu_integrate_peak_single_pt) + self.ui.actionSort_By_2Theta.triggered.connect(self.menu_sort_survey_2theta) + self.ui.actionSort_By_Pt.triggered.connect(self.menu_sort_by_pt_number) - # menu - self.connect(self.ui.actionData_Downloading, QtCore.SIGNAL('triggered()'), - self.menu_download_data) + # menu action: pop out a general figure plot window + self.ui.action2theta_Sigma.triggered.connect(self.menu_pop_2theta_sigma_window) + self.ui.actionSave_2theta_Sigma.triggered.connect(self.menu_save_2theta_sigma) # Validator ... (NEXT) + # blabla... ... # Declaration of class variable # IPTS number self._iptsNumber = None + self._current_exp_number = None # some configuration self._homeSrcDir = os.getcwd() @@ -368,6 +293,54 @@ class MainWindow(QtGui.QMainWindow): return + def _promote_widgets(self): + tableWidget_surveyTable_layout = QVBoxLayout() + self.ui.frame_tableWidget_surveyTable.setLayout(tableWidget_surveyTable_layout) + self.ui.tableWidget_surveyTable = ScanSurveyTable(self) + tableWidget_surveyTable_layout.addWidget(self.ui.tableWidget_surveyTable) + + graphicsView_detector2dPlot_layout = QVBoxLayout() + self.ui.frame_graphicsView_detector2dPlot.setLayout(graphicsView_detector2dPlot_layout) + self.ui.graphicsView_detector2dPlot = Detector2DView(self) + graphicsView_detector2dPlot_layout.addWidget(self.ui.graphicsView_detector2dPlot) + + tableWidget_peaksCalUB_layout = QVBoxLayout() + self.ui.frame_tableWidget_peaksCalUB.setLayout(tableWidget_peaksCalUB_layout) + self.ui.tableWidget_peaksCalUB = UBMatrixPeakTable(self) + tableWidget_peaksCalUB_layout.addWidget(self.ui.tableWidget_peaksCalUB) + + tableWidget_ubMatrix_layout = QVBoxLayout() + self.ui.frame_tableWidget_ubMatrix.setLayout(tableWidget_ubMatrix_layout) + self.ui.tableWidget_ubMatrix = UBMatrixTable(self) + tableWidget_ubMatrix_layout.addWidget(self.ui.tableWidget_ubMatrix) + + tableWidget_ubInUse_layout = QVBoxLayout() + self.ui.frame_tableWidget_ubInUse.setLayout(tableWidget_ubInUse_layout) + self.ui.tableWidget_ubInUse = UBMatrixTable(self) + tableWidget_ubInUse_layout.addWidget(self.ui.tableWidget_ubInUse) + + tableWidget_mergeScans_layout = QVBoxLayout() + self.ui.frame_tableWidget_mergeScans.setLayout(tableWidget_mergeScans_layout) + self.ui.tableWidget_mergeScans = ProcessTableWidget(self) + tableWidget_mergeScans_layout.addWidget(self.ui.tableWidget_mergeScans) + + graphicsView_integratedPeakView_layout = QVBoxLayout() + self.ui.frame_graphicsView_integratedPeakView.setLayout(graphicsView_integratedPeakView_layout) + self.ui.graphicsView_integratedPeakView = IntegratedPeakView(self) + graphicsView_integratedPeakView_layout.addWidget(self.ui.graphicsView_integratedPeakView) + + tableWidget_covariance_layout = QVBoxLayout() + self.ui.frame_tableWidget_covariance.setLayout(tableWidget_covariance_layout) + self.ui.tableWidget_covariance = MatrixTable(self) + tableWidget_covariance_layout.addWidget(self.ui.tableWidget_covariance) + + tableWidget_kShift_layout = QVBoxLayout() + self.ui.frame_tableWidget_kShift.setLayout(tableWidget_kShift_layout) + self.ui.tableWidget_kShift = KShiftTableWidget(self) + tableWidget_kShift_layout.addWidget(self.ui.tableWidget_kShift) + + return + @property def controller(self): """ Parameter controller @@ -400,11 +373,11 @@ class MainWindow(QtGui.QMainWindow): # Radio buttons self.ui.radioButton_ubFromTab1.setChecked(True) # group for the source of UB matrix to import - ub_source_group = QtGui.QButtonGroup(self) + ub_source_group = QButtonGroup(self) ub_source_group.addButton(self.ui.radioButton_ubFromList) ub_source_group.addButton(self.ui.radioButton_ubFromTab1) # group for the UB matrix's style - ub_style_group = QtGui.QButtonGroup(self) + ub_style_group = QButtonGroup(self) ub_style_group.addButton(self.ui.radioButton_ubMantidStyle) ub_style_group.addButton(self.ui.radioButton_ubSpiceStyle) @@ -434,7 +407,7 @@ class MainWindow(QtGui.QMainWindow): self.ui.tabWidget.setCurrentIndex(0) self.ui.radioButton_ubMantidStyle.setChecked(True) - self.ui.lineEdit_numSurveyOutput.setText('50') + self.ui.lineEdit_numSurveyOutput.setText('') self.ui.checkBox_sortDescending.setChecked(False) self.ui.radioButton_sortByCounts.setChecked(True) self.ui.radioButton_ubSelectNoScan.setChecked(True) @@ -456,9 +429,11 @@ class MainWindow(QtGui.QMainWindow): # background points self.ui.lineEdit_backgroundPts.setText('1, 1') self.ui.lineEdit_scaleFactor.setText('1.') + self.ui.lineEdit_numPt4BackgroundLeft.setText('1') + self.ui.lineEdit_numPt4BackgroundRight.setText('1') # about pre-processed data - self.ui.checkBox_searchPreprocessedFirst.setChecked(True) + # FIXME self.ui.checkBox_searchPreprocessedFirst.setChecked(True) # hide and disable some push buttons for future implementation self.ui.pushButton_viewScan3D.hide() @@ -560,7 +535,7 @@ class MainWindow(QtGui.QMainWindow): :return: """ # read project file name - project_file_name = str(QtGui.QFileDialog.getSaveFileName(self, 'Specify Project File', os.getcwd())) + project_file_name = str(QFileDialog.getSaveFileName(self, 'Specify Project File', os.getcwd())) # NEXT ISSUE - consider to allow incremental project saving technique if os.path.exists(project_file_name): yes = gutil.show_message(self, 'Project file %s does exist. This is supposed to be ' @@ -620,7 +595,7 @@ class MainWindow(QtGui.QMainWindow): Load project :return: """ - project_file_name = str(QtGui.QFileDialog.getOpenFileName(self, 'Choose Project File', os.getcwd())) + project_file_name = str(QFileDialog.getOpenFileName(self, 'Choose Project File', os.getcwd())) if len(project_file_name) == 0: # return if cancelled return @@ -741,12 +716,12 @@ class MainWindow(QtGui.QMainWindow): # set the button to next mode if str(self.ui.pushButton_switchROIMode.text()) == 'Enter ROI-Edit Mode': # enter adding ROI mode - self.ui.graphicsView_detector2dPlot.enter_roi_mode(state=True) + self.ui.graphicsView_detector2dPlot.enter_roi_mode(roi_state=True) # rename the button self.ui.pushButton_switchROIMode.setText('Quit ROI-Edit Mode') else: # quit editing ROI mode - self.ui.graphicsView_detector2dPlot.enter_roi_mode(state=False) + self.ui.graphicsView_detector2dPlot.enter_roi_mode(roi_state=False) # rename the button self.ui.pushButton_switchROIMode.setText('Enter ROI-Edit Mode') # END-IF-ELSE @@ -946,8 +921,9 @@ class MainWindow(QtGui.QMainWindow): # user specifies a non-exist directory. make an error message self.pop_one_button_dialog('Pre-processed directory {0} ({1}) does not exist.' ''.format(pre_process_dir, type(pre_process_dir))) - self._myControl.pre_processed_dir = None + self._myControl.pre_processed_dir = self._myControl.get_working_directory() self.ui.lineEdit_preprocessedDir.setStyleSheet('color: red;') + self.ui.lineEdit_preprocessedDir.setText(self._myControl.pre_processed_dir) # END-IF if len(error_message) > 0: @@ -959,6 +935,7 @@ class MainWindow(QtGui.QMainWindow): """ browse the pre-processed merged scans' directory :return: """ + print ('Here...2') # determine default directory exp_number_str = str(self.ui.lineEdit_exp.text()) default_pp_dir = os.path.join('/HFIR/HB3A/exp{0}/Shared/'.format(exp_number_str)) @@ -966,7 +943,7 @@ class MainWindow(QtGui.QMainWindow): default_pp_dir = os.path.expanduser('~') # use FileDialog to get the directory and set to preprocessedDir - pp_dir = str(QtGui.QFileDialog.getExistingDirectory(self, 'Get Directory', default_pp_dir)) + pp_dir = str(QFileDialog.getExistingDirectory(self, 'Get Directory', default_pp_dir)) self.ui.lineEdit_preprocessedDir.setText(pp_dir) return @@ -974,7 +951,8 @@ class MainWindow(QtGui.QMainWindow): def do_browse_local_spice_data(self): """ Browse local source SPICE data directory """ - src_spice_dir = str(QtGui.QFileDialog.getExistingDirectory(self, 'Get Directory', + print ('Here...1') + src_spice_dir = str(QFileDialog.getExistingDirectory(self, 'Get Directory', self._homeSrcDir)) # Set local data directory to controller status, error_message = self._myControl.set_local_data_dir(src_spice_dir) @@ -992,7 +970,8 @@ class MainWindow(QtGui.QMainWindow): Browse and set up working directory :return: """ - work_dir = str(QtGui.QFileDialog.getExistingDirectory(self, 'Get Working Directory', self._homeDir)) + print ('Here...2') + work_dir = str(QFileDialog.getExistingDirectory(self, 'Get Working Directory', self._homeDir)) status, error_message = self._myControl.set_working_directory(work_dir) if status is False: self.pop_one_button_dialog(error_message) @@ -1231,8 +1210,8 @@ class MainWindow(QtGui.QMainWindow): return # get the output file name - roi_file_name = str(QtGui.QFileDialog.getSaveFileName(self, 'Output mask/ROI file name', - self._myControl._workDir, + roi_file_name = str(QFileDialog.getSaveFileName(self, 'Output mask/ROI file name', + self._myControl.get_working_directory(), 'XML Files (*.xml);;All Files (*.*)')) if len(roi_file_name) == 0: return @@ -1242,6 +1221,50 @@ class MainWindow(QtGui.QMainWindow): return + def do_export_detector_views_to_movie(self): + """ + go through all surveyed scans. plot all the measurements from all the scans. record the plot to PNG files, + and possibly convert them to movies + :return: + """ + scan_list = self.ui.tableWidget_surveyTable.get_scan_numbers(range(self.ui.tableWidget_surveyTable.rowCount())) + # roi_name = str(self.ui.comboBox_viewRawDataMasks.currentText()) + file_name_out = '' + for i_scan, scan_number in enumerate(scan_list): + # get pt numbers + status, pt_number_list = self._myControl.get_pt_numbers(self._current_exp_number, scan_number) + # stop this loop if unable to get Pt. numbers + if not status: + print ('[DB...BAT] Unable to get list of Pt. number from scan {0} due to {1}.' + ''.format(scan_number, pt_number_list)) + continue + # plot + for pt_number in pt_number_list: + # ROI is set to None because only the ROI rectangular shall appear on the output. But with + # a ROI name, the detector is then masked. It is not the purpose to examine whether all the + # peaks are in ROI + file_name = self.load_plot_raw_data(self._current_exp_number, scan_number, pt_number, roi_name=None, + save=True, remove_workspace=True) + file_name_out += file_name + '\n' + # END-FOR + + # debug break + # if i_scan == 5: + # break + # END-FOR + + # write out the file list + list_name = os.path.join(self._myControl.get_working_directory(), 'png_exp{0}_list.txt' + ''.format(self._current_exp_number)) + ofile = open(list_name, 'w') + ofile.write(file_name_out) + ofile.close() + + message = 'convert -delay 10 -loop 0 @{0} {1}.mpeg'.format(list_name, '[Your Name]') + self.pop_one_button_dialog(message) + + return + def do_export_selected_peaks_to_integrate(self): """ export (to file or just print out) the scans that are selected for integration @@ -1271,7 +1294,7 @@ class MainWindow(QtGui.QMainWindow): return # get the file name - fp_name = str(QtGui.QFileDialog.getSaveFileName(self, 'Save to Fullprof File')) + fp_name = str(QFileDialog.getSaveFileName(self, 'Save to Fullprof File')) # return due to cancel if len(fp_name) == 0: @@ -1281,27 +1304,29 @@ class MainWindow(QtGui.QMainWindow): exp_number = int(self.ui.lineEdit_exp.text()) scan_number_list = list() for i_row in selected_rows: - scan_number_list.append(self.ui.tableWidget_mergeScans.get_scan_number(i_row)) + # add both ROI and scan number + scan_i = self.ui.tableWidget_mergeScans.get_scan_number(i_row) + roi_i = self.ui.tableWidget_mergeScans.get_mask(i_row) + scan_number_list.append((scan_i, roi_i)) # write user_header = str(self.ui.lineEdit_fpHeader.text()) try: export_absorption = self.ui.checkBox_exportAbsorptionToFP.isChecked() - status, file_content = self._myControl.export_to_fullprof(exp_number, scan_number_list, - user_header, export_absorption, fp_name, - self.ui.checkBox_fpHighPrecision.isChecked()) + file_content = self._myControl.export_to_fullprof(exp_number, scan_number_list, + user_header, export_absorption, fp_name, + self.ui.checkBox_fpHighPrecision.isChecked()) self.ui.plainTextEdit_fpContent.setPlainText(file_content) - if status is False: - error_msg = file_content - if error_msg.startswith('Peak index error'): - error_msg = 'You may forget to index peak\n' + error_msg - self.pop_one_button_dialog(error_msg) except AssertionError as a_err: self.pop_one_button_dialog(str(a_err)) - return except KeyError as key_err: self.pop_one_button_dialog(str(key_err)) + except RuntimeError as run_error: + error_msg = str(run_error) + if error_msg.count('Peak index error') > 0: + error_msg = 'You may forget to index peak\n' + error_msg + self.pop_one_button_dialog(error_msg) return @@ -1319,6 +1344,8 @@ class MainWindow(QtGui.QMainWindow): column_name = 'Max Counts' elif self.ui.radioButton_sortByTemp.isChecked(): column_name = 'Sample Temp' + elif self.ui.radioButton_sortBy2Theta.isChecked(): + column_name = '2theta' else: self.pop_one_button_dialog('No column is selected to sort.') return @@ -1571,10 +1598,10 @@ class MainWindow(QtGui.QMainWindow): norm_type = '' # background Pt. - status, num_bg_pt = gutil.parse_integers_editors(self.ui.lineEdit_numPt4Background, allow_blank=False) - if not status or num_bg_pt == 0: - self.pop_one_button_dialog('Number of Pt number for background must be larger than 0: %s!' % str(num_bg_pt)) - return + # status, num_bg_pt = gutil.parse_integers_editors(self.ui.lineEdit_numPt4Background, allow_blank=False) + # if not status or num_bg_pt == 0: + # self.pop_one_button_dialog('Number of Pt number for background must be larger than 0: %s!' % str(num_bg_pt)) + # return # get the merging information: each item should be a tuple as (scan number, pt number list, merged) scan_number_list = list() @@ -1608,7 +1635,7 @@ class MainWindow(QtGui.QMainWindow): self.ui.progressBar_mergeScans.setStatusTip('Hello') # process background setup - status, ret_obj = gutil.parse_integers_editors([self.ui.lineEdit_numPt4Background, + status, ret_obj = gutil.parse_integers_editors([self.ui.lineEdit_numPt4BackgroundLeft, self.ui.lineEdit_numPt4BackgroundRight], allow_blank=False) if not status: @@ -1695,7 +1722,7 @@ class MainWindow(QtGui.QMainWindow): # get the csv file file_filter = 'CSV Files (*.csv);;All Files (*)' - csv_file_name = str(QtGui.QFileDialog.getOpenFileName(self, 'Open Exp-Scan Survey File', self._homeDir, + csv_file_name = str(QFileDialog.getOpenFileName(self, 'Open Exp-Scan Survey File', self._homeDir, file_filter)) if csv_file_name is None or len(csv_file_name) == 0: # return if file selection is cancelled @@ -1725,7 +1752,7 @@ class MainWindow(QtGui.QMainWindow): scan_no = ret_obj[1] pt_no = ret_obj[2] else: - self.pop_one_button_dialog(ret_obj) + self.pop_one_button_dialog('Unable to plot detector counts as a 2D image due to '.format(ret_obj)) return if self.ui.checkBox_autoMask.isChecked(): @@ -1894,15 +1921,19 @@ class MainWindow(QtGui.QMainWindow): # scan_number = par_val_list[1] # get the user specified name from ... - roi_name, ok = QtGui.QInputDialog.getText(self, 'Input Mask Name', 'Enter mask name:') + roi_name, ok = QInputDialog.getText(self, 'Input Mask Name', 'Enter mask name:') # return if cancelled if not ok: return roi_name = str(roi_name) - # TODO : It is better to warn user if the given ROI is used already - pass + # check whether this ROI name is used or not. If it is, warn user if the given ROI is used already + current_roi_names = [str(self.ui.comboBox_maskNames1.itemText(i)) + for i in range(self.ui.comboBox_maskNames1.count())] + if roi_name in current_roi_names: + self.pop_one_button_dialog('[Warning] ROI name {} is used before. The previous ROI ' + 'will be overwritten by the new defined.'.format(roi_name)) # get current ROI ll_corner, ur_corner = self.ui.graphicsView_detector2dPlot.get_roi() @@ -2196,8 +2227,29 @@ class MainWindow(QtGui.QMainWindow): return + def process_single_pt_scan_intensity(self, scan_integrate_info_dict): + """ + process integrated single pt scan + :param scan_integrate_info_dict: + :return: + """ + # check inputs + assert isinstance(scan_integrate_info_dict, dict), 'Input scan-pt pairs {0} must be in a dict but not a {1}' \ + ''.format(scan_integrate_info_dict, + type(scan_integrate_info_dict)) + + # get intensities + for scan_number in scan_integrate_info_dict: + # self._single_pt_scan_intensity_dict: not been defined and used at all! + # TODO NOW3 : Check whether the intensity is recorded in the single_measurement already??? + intensity, roi_name = scan_integrate_info_dict[scan_number] + self.ui.tableWidget_mergeScans.add_single_measure_scan(scan_number, intensity, roi_name) + # END-FOR + + return + # add slot for UB refinement configuration window's signal to connect to - @QtCore.pyqtSlot(int) + # @QtCore.pyqtSlot(int) def refine_ub_lattice(self, val): """ Refine UB matrix by constraining on lattice type. @@ -2348,7 +2400,7 @@ class MainWindow(QtGui.QMainWindow): """ # Get file name file_filter = 'CSV Files (*.csv);;All Files (*)' - out_file_name = str(QtGui.QFileDialog.getSaveFileName(self, 'Save scan survey result', + out_file_name = str(QFileDialog.getSaveFileName(self, 'Save scan survey result', self._homeDir, file_filter)) # Save file @@ -2362,7 +2414,7 @@ class MainWindow(QtGui.QMainWindow): """ # get file name file_filter = 'Data Files (*.dat);;All Files (*)' - ub_file_name = str(QtGui.QFileDialog.getSaveFileName(self, 'ASCII File To Save UB Matrix', self._homeDir, + ub_file_name = str(QFileDialog.getSaveFileName(self, 'ASCII File To Save UB Matrix', self._homeDir, file_filter)) # early return if user cancels selecting a file name to save @@ -2423,25 +2475,41 @@ class MainWindow(QtGui.QMainWindow): return + # TEST NOW def do_select_all_survey(self): """ Select or de-select all rows in survey items :return: """ if self._surveyTableFlag: - # flag is turned on: select all peaks or all nuclear peaks - if self.ui.checkBox_surveySelectNuclearPeaks.isChecked(): - # only select nuclear peaks - num_rows = self.ui.tableWidget_surveyTable.rowCount() - for i_row in range(num_rows): + # there are cases: select all or select selected few with filters + # check whether there is any option + if self.ui.checkBox_surveySelectNuclearPeaks.isChecked() or self.ui.checkBox_singlePtScans.isChecked(): + # deselect all rows for future selection + self.ui.tableWidget_surveyTable.select_all_rows(False) + + # go through all rows + num_rows = self.ui.tableWidget_surveyTable.rowCount() + for i_row in range(num_rows): + + select_line = True + + # filter HKL (nuclear) + if self.ui.checkBox_surveySelectNuclearPeaks.isChecked(): + # only select nuclear peaks peak_hkl = self.ui.tableWidget_surveyTable.get_hkl(i_row) - if peak_util.is_peak_nuclear(peak_hkl[0], peak_hkl[1], peak_hkl[2]): - self.ui.tableWidget_surveyTable.select_row(i_row, True) - else: - self.ui.tableWidget_surveyTable.select_row(i_row, False) - else: - # all peaks - self.ui.tableWidget_surveyTable.select_all_rows(True) + select_line = peak_util.is_peak_nuclear(peak_hkl[0], peak_hkl[1], peak_hkl[2]) and select_line + + if self.ui.checkBox_singlePtScans.isChecked(): + # only select scan with single Pt + scan_number = self.ui.tableWidget_surveyTable.get_scan_numbers([i_row])[0] + status, pt_list = self._myControl.get_pt_numbers(self._current_exp_number, scan_number) + select_line = (status and len(pt_list) == 1) and select_line + + if select_line: + self.ui.tableWidget_surveyTable.select_row(i_row, True) + # END-FOR(i_row) + else: # de-select all peaks self.ui.tableWidget_surveyTable.select_all_rows(False) @@ -2470,6 +2538,18 @@ class MainWindow(QtGui.QMainWindow): return + def do_set_all_calibration(self): + """ + set up all the calibration parameters + :return: + """ + self.do_set_user_detector_center() + self.do_set_user_detector_distance() + self.do_set_user_wave_length() + self.do_set_detector_size() + + return + def do_set_detector_size(self): """ set the detector size to controller @@ -2529,6 +2609,7 @@ class MainWindow(QtGui.QMainWindow): """ # get exp number status, ret_obj = gutil.parse_integers_editors([self.ui.lineEdit_exp]) + if status: # new experiment number exp_number = ret_obj[0] @@ -2553,12 +2634,58 @@ class MainWindow(QtGui.QMainWindow): # find out the detector type status, ret_obj = self._myControl.find_detector_size(default_data_dir, exp_number) + # TEST new feature + # Check working directory. If not set or with different exp number, then set to + work_dir = str(self.ui.lineEdit_workDir.text()).strip() + if work_dir != '': + # check whether it contains exp number other than current one + if work_dir.lower().count('exp') > 0 and work_dir.count('{0}'.format(exp_number)) == 0: + use_default = True + else: + use_default = False + else: + use_default = False + if use_default: + # set default to analysis cluster or local + work_dir = '/HFIR/HB3A/exp{0}/Shared/'.format(exp_number) + # check whether user has writing permission + if os.access(work_dir, os.W_OK) is False: + work_dir = os.path.join(os.path.expanduser('~'), 'HB3A/Exp{0}'.format(exp_number)) + self.ui.lineEdit_workDir.setText(work_dir) + # END-IF + + # TEST new feature + # Check pre-processing directory. If not set or with different exp number, then set to + pre_process_dir = str(self.ui.lineEdit_preprocessedDir.text()).strip() + if pre_process_dir == '': + # empty + use_default = True + elif pre_process_dir.lower().count('exp') > 0 and pre_process_dir.count('{0}'.format(exp_number)) > 0: + # doesn't sound right + use_default = True + else: + # other cases + use_default = False + if use_default: + # shared Example: /HFIR/HB3A/exp668/Shared/preprocessed + pre_process_dir = '/HFIR/HB3A/exp{0}/Shared/preprocessed'.format(exp_number) + # check whether user has writing permission + if os.access(pre_process_dir, os.W_OK) is False: + # use local + pre_process_dir = os.path.join(os.path.expanduser('~'), + 'HB3A/Exp{0}/preprocessed'.format(exp_number)) + self.ui.lineEdit_preprocessedDir.setText(pre_process_dir) + # END-IF + else: err_msg = ret_obj self.pop_one_button_dialog('Unable to set experiment as %s' % err_msg) self.ui.lineEdit_exp.setStyleSheet('color: red') return + # register experiment number + self._current_exp_number = exp_number + self.ui.tabWidget.setCurrentIndex(0) # set the instrument geometry constants @@ -2686,18 +2813,28 @@ class MainWindow(QtGui.QMainWindow): hkl_src = str(self.ui.comboBox_indexFrom.currentText()) # loop through all rows + message = '' num_rows = self.ui.tableWidget_mergeScans.rowCount() for row_index in range(num_rows): # get scan number scan_i = self.ui.tableWidget_mergeScans.get_scan_number(row_index) - peak_info_i = self._myControl.get_peak_info(exp_number, scan_number=scan_i) - # skip non-merged sans - if peak_info_i is None: - continue + integral_type = self.ui.tableWidget_mergeScans.get_integration_type(row_index) + is_single_pt = False + if integral_type == 'single-pt': + roi_name = self.ui.tableWidget_mergeScans.get_roi_name(row_index) + peak_info_i = self._myControl.get_single_pt_info(exp_number, scan_number=scan_i, pt_number=1, + roi_name=roi_name) + is_single_pt = True + else: + peak_info_i = self._myControl.get_peak_info(exp_number, scan_number=scan_i) + # skip non-merged sans + if peak_info_i is None: + message += 'Row {0} Scan {1} is not integrated.\n'.format(row_index, scan_i) + continue # get or calculate HKL - if hkl_src == 'From SPICE': + if is_single_pt or hkl_src == 'From SPICE': # get HKL from SPICE (non-user-hkl) hkl_i = peak_info_i.get_hkl(user_hkl=False) else: @@ -2717,12 +2854,13 @@ class MainWindow(QtGui.QMainWindow): error_msg = 'Scan %d: %s' % (scan_i, str(ret_tup)) self.ui.tableWidget_mergeScans.set_status(scan_i, error_msg) # END-IF-ELSE(index) + + # set to peak info + peak_info_i.set_hkl_np_array(hkl_i) + # END-IF-ELSE (hkl_from_spice) - # set & show - peak_info_i.set_hkl_np_array(hkl_i) - # self._myControl.get_peak_info(exp_number, scan_i) - # round HKL? + # show by rounding HKL? if self.ui.checkBox_roundHKL.isChecked(): hkl_i = [hb3a_util.round_miller_index(hkl_i[0], 0.2), hb3a_util.round_miller_index(hkl_i[1], 0.2), @@ -2759,7 +2897,7 @@ class MainWindow(QtGui.QMainWindow): :return: """ file_filter = 'Data Files (*.dat);;Text Files (*.txt);;All Files (*)' - file_name = QtGui.QFileDialog.getOpenFileName(self, 'Open UB ASCII File', self._homeDir, + file_name = QFileDialog.getOpenFileName(self, 'Open UB ASCII File', self._homeDir, file_filter) # quit if cancelled if file_name is None: @@ -3014,7 +3152,13 @@ class MainWindow(QtGui.QMainWindow): start_scan = ret_obj[0] end_scan = ret_obj[1] - max_number = int(self.ui.lineEdit_numSurveyOutput.text()) + # maximum number to show on the table + max_str = str(self.ui.lineEdit_numSurveyOutput.text()) + if max_str.isdigit(): + max_number = int(max_str) + else: + max_number = end_scan - start_scan + 1 + self.ui.lineEdit_numSurveyOutput.setText(str(max_number)) # Get value status, ret_obj, err_msg = self._myControl.survey(exp_number, start_scan, end_scan) @@ -3343,11 +3487,21 @@ class MainWindow(QtGui.QMainWindow): # do nothing if the table is empty return - max_number = int(self.ui.lineEdit_numSurveyOutput.text()) + max_number_str = str(self.ui.lineEdit_numSurveyOutput.text()).strip() + if max_number_str == '': + # empty: select all + surveyed_scan_list = self._myControl.get_surveyed_scans() + max_number = len(surveyed_scan_list) + + else: + # get maximum number and + max_number = int(max_number_str) # ignore the situation that this line edit is cleared if max_number <= 0: return + + # reset row number if max_number != self.ui.tableWidget_surveyTable.rowCount(): # re-show survey self.ui.tableWidget_surveyTable.remove_all_rows() @@ -3357,7 +3511,7 @@ class MainWindow(QtGui.QMainWindow): def generate_peaks_integration_report(self): """ - generate a report for all integrated peaks + generate a report for all integrated peaks get MergeScan table and related PeakInfo instance :return: """ # get experiment number @@ -3457,7 +3611,7 @@ class MainWindow(QtGui.QMainWindow): """ assert isinstance(message, str), 'Input message %s must a string but not %s.' \ '' % (str(message), type(message)) - QtGui.QMessageBox.information(self, '4-circle Data Reduction', message) + QMessageBox.information(self, '4-circle Data Reduction', message) return @@ -3532,13 +3686,42 @@ class MainWindow(QtGui.QMainWindow): return + def menu_integrate_peak_single_pt(self): + """ Handle the event to integrate single-pt peak from menu operation + The operation includes + 1. initialize the single-pt scan integration window if it is not initialized + 2. add the selected scans to the table in single-pt scan integration window + 3. show the window + :return: + """ + if self._single_pt_peak_integration_window is None: + self._single_pt_peak_integration_window = IntegrateSingePtSubWindow.IntegrateSinglePtIntensityWindow(self) + self._single_pt_peak_integration_window.show() + + # set experiment + self._single_pt_peak_integration_window.set_experiment(self._current_exp_number) + + # collect selected Scan/Pt combo + scan_pt_tup_list = self.ui.tableWidget_surveyTable.get_selected_scan_pt() + # get other information + for scan_number, pt_number in scan_pt_tup_list: + # check HKL and 2theta + h_i = self._myControl.get_sample_log_value(self._current_exp_number, scan_number, pt_number, '_h') + k_i = self._myControl.get_sample_log_value(self._current_exp_number, scan_number, pt_number, '_k') + l_i = self._myControl.get_sample_log_value(self._current_exp_number, scan_number, pt_number, '_l') + hkl_str = '{0}, {1}, {2}'.format(h_i, k_i, l_i) + two_theta = self._myControl.get_sample_log_value(self._current_exp_number, scan_number, pt_number, '2theta') + self._single_pt_peak_integration_window.add_scan(scan_number, pt_number, hkl_str, two_theta) + + return + def menu_load_mask(self): """ Load Mask and apply to both workspaces and GUI hb3a_clean_ui_21210 """ # get the XML file to load file_filter = 'XML Files (*.xml);;All Files (*)' - mask_file_name = str(QtGui.QFileDialog.getOpenFileName(self, 'Open Masking File', + mask_file_name = str(QFileDialog.getOpenFileName(self, 'Open Masking File', self._myControl.get_working_directory(), file_filter)) @@ -3563,6 +3746,86 @@ class MainWindow(QtGui.QMainWindow): return + def menu_pop_2theta_sigma_window(self): + """ + pop out a general figure plot window for 2theta - sigma plot + :return: + """ + # create new window or clear existing window + if self._general_1d_plot_window is None: + self._general_1d_plot_window = generalplotview.GeneralPlotWindow(self) + pass + else: + self._general_1d_plot_window.reset_window() + # show + self._general_1d_plot_window.show() + + # set up the window + try: + output_arrays = self._myControl.get_peak_integration_parameters(xlabel='2theta', ylabel=['sigma', 'scan']) + # convert matrix to arrays + xye_matrix = output_arrays.transpose() + vec_2theta = xye_matrix[0] + vec_sigma = xye_matrix[1] + vec_sigma_error = xye_matrix[2] + + vec_scan_number = xye_matrix[3] + notes = list() + for i_scan in range(len(vec_scan_number)): + notes.append('{}'.format(int(vec_scan_number[i_scan]))) + + # get the latest (cached) vec_x and vec_y + self._general_1d_plot_window.plot_data(vec_2theta, vec_sigma, vec_sigma_error, '2theta', 'Gaussian-Sigma', + annotation_list=notes) + except RuntimeError as run_err: + self.pop_one_button_dialog(str(run_err)) + + return + + def menu_save_2theta_sigma(self): + """ + save 2theta - sigma - scan number to a csv file + :return: + """ + # get file name + out_file_name = QFileDialog.getSaveFileName(self, caption='Select a file to save 2theta-Sigma-Scan', + directory=self._myControl.get_working_directory(), + filter='Data Files (*.dat);;All File (*.*)') + out_file_name = str(out_file_name) + if len(out_file_name) == 0: + # cancelled + return + + # construct the table + try: + output_arrays = self._myControl.get_peak_integration_parameters(xlabel='2theta', ylabel=['sigma', 'scan'], + with_error=True) + except RuntimeError as run_err: + self.pop_one_button_dialog(str(run_err)) + return + else: + # xye_matrix = output_arrays.transpose() + # vec_2theta = xye_matrix[0] + # vec_sigma = xye_matrix[1] + # vec_sigma_error = xye_matrix[2] + # vec_scan_number = xye_matrix[3] + pass + + # write to the output file + wbuf = '# 2theta\tsigam\terror\tscan number\n' + for index in range(output_arrays.shape[0]): + # wbuf += '{:.5f}\t{:5f}\t{:5f}\t{}\n'.format(vec_2theta[index], vec_sigma[index], vec_sigma_error[index], + # vec_scan_number[index]) + wbuf += '{:.5f}\t{:5f}\t{:5f}\t{}\n'.format(output_arrays[index][0], output_arrays[index][1], + output_arrays[index][2], output_arrays[index][3]) + # END-FOR + + out_file = open(out_file_name, 'w') + out_file.write(wbuf) + out_file.close() + + return + def menu_quit(self): """ @@ -3618,6 +3881,29 @@ class MainWindow(QtGui.QMainWindow): return + def menu_sort_survey_2theta(self): + """ + sort survey table by 2theta + :return: + """ + self.ui.tableWidget_surveyTable.filter_and_sort(start_scan=0, end_scan=100000, + min_counts=0., max_counts=10000000000., + sort_by_column='2theta', sort_order=0) + + return + + # TESTME - recently implemented + def menu_sort_by_pt_number(self): + """ + sort survey table by pt number (with the maximum counts in the scan) + :return: + """ + self.ui.tableWidget_surveyTable.filter_and_sort(start_scan=0, end_scan=100000, + min_counts=0., max_counts=10000000000., + sort_by_column='Max Counts Pt', sort_order=0) + + return + def show_scan_pt_list(self): """ Show the range of Pt. in a scan :return: @@ -3690,7 +3976,7 @@ class MainWindow(QtGui.QMainWindow): Save settings (parameter set) upon quitting :return: """ - settings = QtCore.QSettings() + settings = QSettings() # directories local_spice_dir = str(self.ui.lineEdit_localSpiceDir.text()) @@ -3746,7 +4032,7 @@ class MainWindow(QtGui.QMainWindow): Load QSettings from previous saved file :return: """ - settings = QtCore.QSettings() + settings = QSettings() # directories try: @@ -3773,6 +4059,10 @@ class MainWindow(QtGui.QMainWindow): lattice_gamma = settings.value('gamma') self.ui.lineEdit_gamma.setText(str(lattice_gamma)) + # last project + last_1_project_path = str(settings.value('last1path')) + self.ui.label_last1Path.setText(last_1_project_path) + # calibrated instrument configurations user_wave_length = settings.value('wave_length') self.ui.lineEdit_userWaveLength.setText(user_wave_length) @@ -3786,10 +4076,6 @@ class MainWindow(QtGui.QMainWindow): det_sample_distance = settings.value('det_sample_distance') self.ui.lineEdit_userDetSampleDistance.setText(det_sample_distance) - # last project - last_1_project_path = str(settings.value('last1path')) - self.ui.label_last1Path.setText(last_1_project_path) - # survey survey_start = str(settings.value('survey_start_scan')) self.ui.lineEdit_surveyStartPt.setText(survey_start) @@ -3797,7 +4083,7 @@ class MainWindow(QtGui.QMainWindow): self.ui.lineEdit_surveyEndPt.setText(survey_stop) except TypeError as err: - self.pop_one_button_dialog(str(err)) + self.pop_one_button_dialog('Failed to load previous session successfully due to {0}'.format(err)) return return @@ -3858,13 +4144,15 @@ class MainWindow(QtGui.QMainWindow): return - def load_plot_raw_data(self, exp_no, scan_no, pt_no, roi_name=None): + def load_plot_raw_data(self, exp_no, scan_no, pt_no, roi_name=None, save=False, remove_workspace=False): """ Plot raw workspace from XML file for a measurement/pt. :param exp_no: :param scan_no: :param pt_no: :param roi_name: string (mask loaded data) or None (do nothing) + :param save: flag to save the ROI + :param remove_workspace: Flag to remove the raw data workspace :return: """ # check inputs @@ -3889,7 +4177,14 @@ class MainWindow(QtGui.QMainWindow): # Get data and plot raw_det_data = self._myControl.get_raw_detector_counts(exp_no, scan_no, pt_no) - self.ui.graphicsView_detector2dPlot.plot_detector_counts(raw_det_data) + this_title = 'Exp {} Scan {} Pt {} ROI {}'.format(exp_no, scan_no, pt_no, roi_name) + self.ui.graphicsView_detector2dPlot.plot_detector_counts(raw_det_data, title=this_title) + if save: + image_file = os.path.join(self.working_directory, 'exp{}_scan{}_pt{}_{}.png' + ''.format(exp_no, scan_no, pt_no, roi_name)) + self.ui.graphicsView_detector2dPlot.save_figure(image_file) + else: + image_file = None # Information info = '%-10s: %d\n%-10s: %d\n%-10s: %d\n' % ('Exp', exp_no, @@ -3897,7 +4192,10 @@ class MainWindow(QtGui.QMainWindow): 'Pt', pt_no) self.ui.plainTextEdit_rawDataInformation.setPlainText(info) - return + if remove_workspace: + self._myControl.remove_pt_xml_workspace(exp_no, scan_no, pt_no) + + return image_file def update_adding_peaks_status(self, exp_number, scan_number, progress): """ diff --git a/scripts/HFIR_4Circle_Reduction/refineubfftsetup.py b/scripts/HFIR_4Circle_Reduction/refineubfftsetup.py index 2014be263c7ad926d1395f417e4c95c30359ab2e..903a9ec9b3e27fcfcf869fee8257307be7d96bfc 100644 --- a/scripts/HFIR_4Circle_Reduction/refineubfftsetup.py +++ b/scripts/HFIR_4Circle_Reduction/refineubfftsetup.py @@ -5,18 +5,17 @@ # & Institut Laue - Langevin # SPDX - License - Identifier: GPL - 3.0 + from __future__ import (absolute_import, division, print_function) -import HFIR_4Circle_Reduction.ui_RefineUbFftDialog as ui_RefineUbFftDialog -from PyQt4 import QtCore -from PyQt4 import QtGui +from qtpy.QtWidgets import (QDialog) # noqa +from mantid.kernel import Logger try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - def _fromUtf8(s): - return s + from mantidqt.utils.qt import load_ui +except ImportError: + Logger("HFIR_4Circle_Reduction").information('Using legacy ui importer') + from mantidplot import load_ui -class RefineUBFFTSetupDialog(QtGui.QDialog): +class RefineUBFFTSetupDialog(QDialog): """A dialog window to get the setup for refining UB matrix by FFT. """ @@ -28,8 +27,8 @@ class RefineUBFFTSetupDialog(QtGui.QDialog): super(RefineUBFFTSetupDialog, self).__init__(parent) # create UI - self.ui = ui_RefineUbFftDialog.Ui_Dialog() - self.ui.setupUi(self) + ui_path = "RefineUbFftDialog.ui" + self.ui = load_ui(__file__, ui_path, baseinstance=self) # init widget value self.ui.lineEdit_minD.setText('1.0') @@ -37,11 +36,8 @@ class RefineUBFFTSetupDialog(QtGui.QDialog): self.ui.lineEdit_tolerance.setText('0.15') # connected to event handler - self.connect(self.ui.buttonBox, QtCore.SIGNAL('accepted()'), - self.do_ok) - - self.connect(self.ui.buttonBox, QtCore.SIGNAL('rejected()'), - self.do_cancel) + self.ui.buttonBox.accepted.connect(self.do_ok) + self.ui.buttonBox.rejected.connect(self.do_cancel) # class variables self._minD = 1.0 diff --git a/scripts/HFIR_4Circle_Reduction/viewspicedialog.py b/scripts/HFIR_4Circle_Reduction/viewspicedialog.py index 98b98352f0edcabff6fed744b198c6a8206cc453..15142ca747824b8a7cd03b6daedd7ee4c03aaf74 100644 --- a/scripts/HFIR_4Circle_Reduction/viewspicedialog.py +++ b/scripts/HFIR_4Circle_Reduction/viewspicedialog.py @@ -5,12 +5,16 @@ # & Institut Laue - Langevin # SPDX - License - Identifier: GPL - 3.0 + from __future__ import (absolute_import, division, print_function) -from . import ui_SpiceViewerDialog -from PyQt4 import QtCore -from PyQt4 import QtGui +from qtpy.QtWidgets import (QDialog) # noqa +from mantid.kernel import Logger +try: + from mantidqt.utils.qt import load_ui +except ImportError: + Logger("HFIR_4Circle_Reduction").information('Using legacy ui importer') + from mantidplot import load_ui -class ViewSpiceDialog(QtGui.QDialog): +class ViewSpiceDialog(QDialog): """Dialog to view SPICE """ def __init__(self, parent): """Initialization @@ -20,12 +24,11 @@ class ViewSpiceDialog(QtGui.QDialog): super(ViewSpiceDialog, self).__init__() # define UI - self.ui = ui_SpiceViewerDialog.Ui_Dialog() - self.ui.setupUi(self) + ui_path = "SpiceViewerDialog.ui" + self.ui = load_ui(__file__, ui_path, baseinstance=self) # define event handlers - self.connect(self.ui.pushButton_close, QtCore.SIGNAL('clicked()'), - self.do_quit) + self.ui.pushButton_close.clicked.connect(self.do_quit) return @@ -53,8 +56,6 @@ class ViewSpiceDialog(QtGui.QDialog): :param plain_text: :return: """ - assert isinstance(plain_text, str) or isinstance(plain_text, QtCore.QString), \ - 'Type of plain text is not supported.' - self.ui.textBrowser_spice.setText(plain_text) + self.ui.textBrowser_spice.setText('{}'.format(plain_text)) return diff --git a/scripts/Inelastic/IndirectReductionCommon.py b/scripts/Inelastic/IndirectReductionCommon.py index 1689bdc5434d8c74e5316d26082336cda0ea30e0..ca9a964e878f786f29aa5a604da7cad8c9fe78c8 100644 --- a/scripts/Inelastic/IndirectReductionCommon.py +++ b/scripts/Inelastic/IndirectReductionCommon.py @@ -592,12 +592,11 @@ def get_group_from_string(grouping_string): return [int(grouping_string)] -def create_group_from_string(input_workspace, grouping_string): - from mantid.simpleapi import GroupDetectors - return GroupDetectors(InputWorkspace=input_workspace, - Behaviour='Average', - SpectraList=get_group_from_string(grouping_string), - StoreInADS=False) +def create_group_from_string(group_detectors, grouping_string): + group_detectors.setProperty("WorkspaceIndexList", get_group_from_string(grouping_string)) + group_detectors.setProperty("OutputWorkspace", "__temp") + group_detectors.execute() + return group_detectors.getProperty("OutputWorkspace").value def conjoin_workspaces(*workspaces): @@ -609,9 +608,9 @@ def conjoin_workspaces(*workspaces): return conjoined -def group_on_string(input_workspace, grouping_string): +def group_on_string(group_detectors, grouping_string): grouping_string.replace(' ', '') - groups = [create_group_from_string(input_workspace, group) for group in grouping_string.split(',')] + groups = [create_group_from_string(group_detectors, group) for group in grouping_string.split(',')] return conjoin_workspaces(*groups) @@ -710,7 +709,7 @@ def group_spectra_of(workspace, masked_detectors, method, group_file=None, group # Mask detectors if required if len(masked_detectors) > 0: _mask_detectors(workspace, masked_detectors) - return group_on_string(workspace, group_string) + return group_on_string(group_detectors, group_string) else: raise RuntimeError('Invalid grouping method %s for workspace %s' % (grouping_method, workspace.getName())) diff --git a/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py b/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py index 2d519d2474eae58f6cd4dea73f6137ba9458f0bf..56961a6426239b6ccc8394dcc43cd45bf4b15dea 100644 --- a/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py +++ b/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py @@ -28,7 +28,7 @@ except ImportError: from . import ui_sans_data_processor_window as ui_sans_data_processor_window from sans.common.enums import (ReductionDimensionality, OutputMode, SaveType, SANSInstrument, - RangeStepType, SampleShape, ReductionMode, FitType) + RangeStepType, ReductionMode, FitType) from sans.common.file_information import SANSFileInformationFactory from sans.gui_logic.gui_common import (get_reduction_mode_from_gui_selection, get_reduction_mode_strings_for_gui, get_string_for_gui_from_reduction_mode, GENERIC_SETTINGS, load_file, @@ -107,6 +107,10 @@ class SANSDataProcessorGui(QtGui.QMainWindow, ui_sans_data_processor_window.Ui_S def on_multi_period_selection(self, show_periods): pass + @abstractmethod + def on_sample_geometry_selection(self, show_geometry): + pass + @abstractmethod def on_data_changed(self, row, column, new_value, old_value): pass @@ -276,6 +280,7 @@ class SANSDataProcessorGui(QtGui.QMainWindow, ui_sans_data_processor_window.Ui_S self._setup_main_tab() self.multi_period_check_box.stateChanged.connect(self._on_multi_period_selection) + self.sample_geometry_checkbox.stateChanged.connect(self._on_sample_geometry_selection) self.wavelength_step_type_combo_box.currentIndexChanged.connect(self._on_wavelength_step_type_changed) @@ -334,6 +339,7 @@ class SANSDataProcessorGui(QtGui.QMainWindow, ui_sans_data_processor_window.Ui_S self.q_resolution_moderator_file_push_button.clicked.connect(self._on_load_moderator_file) self.wavelength_stacked_widget.setCurrentIndex(0) + self.hide_geometry() return True @@ -358,7 +364,8 @@ class SANSDataProcessorGui(QtGui.QMainWindow, ui_sans_data_processor_window.Ui_S self.data_processor_table = MantidQt.MantidWidgets.Batch.JobTreeView( ["Sample Scatter", "ssp", "Sample Transmission", "stp", "Sample Direct", "sdp","Can Scatter", "csp", - "Can Transmission", "ctp", "Can Direct", "cdp", "Output Name", "User File", "Sample Thickness", "Options"] + "Can Transmission", "ctp", "Can Direct", "cdp", "Output Name", "User File", "Sample Thickness", + "Sample Height", "Sample Width", "Sample Shape", "Options"] , self.cell(""), self) self.data_processor_table.setRootIsDecorated(False) @@ -713,6 +720,9 @@ class SANSDataProcessorGui(QtGui.QMainWindow, ui_sans_data_processor_window.Ui_S def _on_multi_period_selection(self): self._call_settings_listeners(lambda listener: listener.on_multi_period_selection(self.is_multi_period_view())) + def _on_sample_geometry_selection(self): + self._call_settings_listeners(lambda listener: listener.on_sample_geometry_selection(self.is_sample_geometry())) + def _on_manage_directories(self): self._call_settings_listeners(lambda listener: listener.on_manage_directories()) @@ -780,6 +790,12 @@ class SANSDataProcessorGui(QtGui.QMainWindow, ui_sans_data_processor_window.Ui_S def set_multi_period_view_mode(self, mode): self.multi_period_check_box.setChecked(mode) + def is_sample_geometry(self): + return self.sample_geometry_checkbox.isChecked() + + def set_sample_geometry_mode(self, mode): + self.sample_geometry_checkbox.setChecked(mode) + # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ # START ACCESSORS @@ -1119,23 +1135,6 @@ class SANSDataProcessorGui(QtGui.QMainWindow, ui_sans_data_processor_window.Ui_S # ------------------------------------------------------------------------------------------------------------------ # Scale Group # ------------------------------------------------------------------------------------------------------------------ - @property - def sample_shape(self): - geometry_as_string = self.geometry_combo_box.currentText().encode('utf-8') - # Either the selection is something that can be converted to a SampleShape or we need to read from file - try: - return SampleShape.from_string(geometry_as_string) - except RuntimeError: - return None - - @sample_shape.setter - def sample_shape(self, value): - if value is None: - # Set to the default - self.geometry_combo_box.setCurrentIndex(0) - else: - self.update_gui_combo_box(value=value, expected_type=SampleShape, combo_box="geometry_combo_box") - @property def absolute_scale(self): return self.get_simple_line_edit_field(line_edit="absolute_scale_line_edit", expected_type=float) @@ -1144,30 +1143,6 @@ class SANSDataProcessorGui(QtGui.QMainWindow, ui_sans_data_processor_window.Ui_S def absolute_scale(self, value): self.update_simple_line_edit_field(line_edit="absolute_scale_line_edit", value=value) - @property - def sample_height(self): - return self.get_simple_line_edit_field(line_edit="height_line_edit", expected_type=float) - - @sample_height.setter - def sample_height(self, value): - self.update_simple_line_edit_field(line_edit="height_line_edit", value=value) - - @property - def sample_width(self): - return self.get_simple_line_edit_field(line_edit="width_line_edit", expected_type=float) - - @sample_width.setter - def sample_width(self, value): - self.update_simple_line_edit_field(line_edit="width_line_edit", value=value) - - @property - def sample_thickness(self): - return self.get_simple_line_edit_field(line_edit="thickness_line_edit", expected_type=float) - - @sample_thickness.setter - def sample_thickness(self, value): - self.update_simple_line_edit_field(line_edit="thickness_line_edit", value=value) - @property def z_offset(self): return self.get_simple_line_edit_field(line_edit="z_offset_line_edit", expected_type=float) @@ -1727,9 +1702,6 @@ class SANSDataProcessorGui(QtGui.QMainWindow, ui_sans_data_processor_window.Ui_S self.wavelength_step_line_edit.setValidator(positive_double_validator) self.absolute_scale_line_edit.setValidator(double_validator) - self.height_line_edit.setValidator(positive_double_validator) - self.width_line_edit.setValidator(positive_double_validator) - self.thickness_line_edit.setValidator(positive_double_validator) self.z_offset_line_edit.setValidator(double_validator) # -------------------------------- @@ -1796,10 +1768,6 @@ class SANSDataProcessorGui(QtGui.QMainWindow, ui_sans_data_processor_window.Ui_S self.wavelength_slices_line_edit.setText("") self.absolute_scale_line_edit.setText("") - self.geometry_combo_box.setCurrentIndex(0) - self.height_line_edit.setText("") - self.width_line_edit.setText("") - self.thickness_line_edit.setText("") self.z_offset_line_edit.setText("") # -------------------------------- @@ -1969,3 +1937,13 @@ class SANSDataProcessorGui(QtGui.QMainWindow, ui_sans_data_processor_window.Ui_S self.data_processor_table.showColumn(7) self.data_processor_table.showColumn(9) self.data_processor_table.showColumn(11) + + def show_geometry(self): + self.data_processor_table.showColumn(15) + self.data_processor_table.showColumn(16) + self.data_processor_table.showColumn(17) + + def hide_geometry(self): + self.data_processor_table.hideColumn(15) + self.data_processor_table.hideColumn(16) + self.data_processor_table.hideColumn(17) diff --git a/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui b/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui index 6ea10a07484e75168eb18a8032ebf6611a75db3e..f3df4cf3256f696bf48cdbf9f13d415a7d53f9b8 100644 --- a/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui +++ b/scripts/Interface/ui/sans_isis/sans_data_processor_window.ui @@ -351,6 +351,13 @@ QGroupBox::title { </item> <item> <layout class="QGridLayout" name="gridLayout_7"> + <item row="0" column="2"> + <widget class="QCheckBox" name="multi_period_check_box"> + <property name="text"> + <string>Multi-period</string> + </property> + </widget> + </item> <item row="0" column="3"> <widget class="QGroupBox" name="reduction_dimensionality_group_box"> <property name="maximumSize"> @@ -380,20 +387,7 @@ QGroupBox::title { </layout> </widget> </item> - <item row="0" column="1"> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="4" rowspan="2"> + <item row="0" column="4" rowspan="3"> <widget class="QGroupBox" name="save_group_box"> <property name="styleSheet"> <string notr="true"/> @@ -540,10 +534,23 @@ QGroupBox::title { </layout> </widget> </item> - <item row="0" column="2"> - <widget class="QCheckBox" name="multi_period_check_box"> + <item row="0" column="1"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="2"> + <widget class="QCheckBox" name="sample_geometry_checkbox"> <property name="text"> - <string>Multi-period</string> + <string>Sample Geometry</string> </property> </widget> </item> @@ -827,46 +834,6 @@ QGroupBox::title { <layout class="QGridLayout" name="gridLayout_5"> <item row="0" column="0"> <layout class="QGridLayout" name="gridLayout_4"> - <item row="3" column="0"> - <widget class="QLabel" name="width_label"> - <property name="toolTip"> - <string><html><head/><body><p>The width of the sample. If this input is left empty, then the value stored in the metadata of the file is used.</p></body></html></string> - </property> - <property name="text"> - <string>Width [mm]</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="height_label"> - <property name="toolTip"> - <string><html><head/><body><p>The height of the sample. If this input is left empty, then the value stored in the metadata of the file is used.</p></body></html></string> - </property> - <property name="text"> - <string>Height [mm]</string> - </property> - </widget> - </item> - <item row="4" column="0"> - <widget class="QLabel" name="thickness_label"> - <property name="toolTip"> - <string><html><head/><body><p>The thickness of the sample. If this input is left empty, then the value stored in the metadata of the file is used.</p></body></html></string> - </property> - <property name="text"> - <string>Thickness [mm]</string> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="geometry_label"> - <property name="toolTip"> - <string><html><head/><body><p>The geometry of the sample. If this is left at <span style=" font-style:italic;">Read from file </span>then the value stored in the metadata of the file is used.</p></body></html></string> - </property> - <property name="text"> - <string>Geometry</string> - </property> - </widget> - </item> <item row="0" column="0"> <widget class="QLabel" name="absolute_scale_label"> <property name="toolTip"> @@ -877,16 +844,6 @@ QGroupBox::title { </property> </widget> </item> - <item row="5" column="0"> - <widget class="QLabel" name="z_offset_label"> - <property name="toolTip"> - <string><html><head/><body><p>The z offset of the sample position.</p></body></html></string> - </property> - <property name="text"> - <string>Z Offset</string> - </property> - </widget> - </item> <item row="0" column="1"> <widget class="QLineEdit" name="absolute_scale_line_edit"> <property name="toolTip"> @@ -894,35 +851,17 @@ QGroupBox::title { </property> </widget> </item> - <item row="2" column="1"> - <widget class="QLineEdit" name="height_line_edit"> - <property name="toolTip"> - <string><html><head/><body><p>The height of the sample. If this input is left empty, then the value stored in the metadata of the file is used.</p></body></html></string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QLineEdit" name="width_line_edit"> + <item row="1" column="0"> + <widget class="QLabel" name="z_offset_label"> <property name="toolTip"> - <string><html><head/><body><p>The width of the sample. If this input is left empty, then the value stored in the metadata of the file is used.</p></body></html></string> + <string><html><head/><body><p>The z offset of the sample position.</p></body></html></string> </property> - </widget> - </item> - <item row="4" column="1"> - <widget class="QLineEdit" name="thickness_line_edit"> - <property name="toolTip"> - <string><html><head/><body><p>The thickness of the sample. If this input is left empty, then the value stored in the metadata of the file is used.</p></body></html></string> + <property name="text"> + <string>Z Offset</string> </property> </widget> </item> <item row="1" column="1"> - <widget class="QComboBox" name="geometry_combo_box"> - <property name="toolTip"> - <string><html><head/><body><p>The geometry of the sample. If this is left at <span style=" font-style:italic;">Read from file </span>then the value stored in the metadata of the file is used.</p></body></html></string> - </property> - </widget> - </item> - <item row="5" column="1"> <widget class="QLineEdit" name="z_offset_line_edit"> <property name="toolTip"> <string><html><head/><body><p>The z offset of the sample position.</p></body></html></string> @@ -2287,10 +2226,6 @@ QGroupBox::title { <tabstop>event_binning_group_box</tabstop> <tabstop>event_binning_line_edit</tabstop> <tabstop>absolute_scale_line_edit</tabstop> - <tabstop>geometry_combo_box</tabstop> - <tabstop>height_line_edit</tabstop> - <tabstop>width_line_edit</tabstop> - <tabstop>thickness_line_edit</tabstop> <tabstop>z_offset_line_edit</tabstop> <tabstop>mask_file_input_line_edit</tabstop> <tabstop>mask_file_browse_push_button</tabstop> diff --git a/scripts/Interface/ui/sans_isis/work_handler.py b/scripts/Interface/ui/sans_isis/work_handler.py index 31eca10dc967a2e9a0de19aff4ed0b612b8a8257..a093cd5142b5e9c5318f1a66588a8ce399d084f5 100644 --- a/scripts/Interface/ui/sans_isis/work_handler.py +++ b/scripts/Interface/ui/sans_isis/work_handler.py @@ -4,14 +4,40 @@ # NScD Oak Ridge National Laboratory, European Spallation Source # & Institut Laue - Langevin # SPDX - License - Identifier: GPL - 3.0 + +""" +The WorkHandler class handles the multi-threading of function calls; used to keep the GUI +responsive whilst processing time consuming functions. + +Concretely, the QThreadPool is used. + +The "worker" handles running the function via a unique process ID; "listeners" may be defined for +each process which are then notified upon certain actions (such as an error being thrown by the +worker, or the worker finishing its task) using the nested class WorkListener. +""" + from PyQt4.QtCore import pyqtSlot, QThreadPool from abc import ABCMeta, abstractmethod from six import with_metaclass from worker import Worker +import functools +import uuid class WorkHandler(object): + """ + Class to handle threading of "work" (concretely this is just the evaluation of a function of the + form func(*args, **kwargs). + + The process ID identifies (uniquely) each instance of a Worker; but the caller also defines + an ID which is then used to identify the Worker through the API. + """ + class WorkListener(with_metaclass(ABCMeta, object)): + """ + This abstract base class defines methods which must be overriden and which + handle responses to certain worker actions such as raised errors, or completion. + """ + def __init__(self): pass @@ -25,32 +51,70 @@ class WorkHandler(object): def __init__(self): self.thread_pool = None - self._listener = None - self._worker = None + self._listener = {} + self._worker = {} + self.thread_pool = QThreadPool() - def _add_listener(self, listener): + def _add_listener(self, listener, process_id, id): if not isinstance(listener, WorkHandler.WorkListener): - raise ValueError("The listener ist not of type WorkListener but rather {}".format(type(listener))) - self._listener = listener + raise ValueError("The listener is not of type " + "WorkListener but rather {}".format(type(listener))) + self._listener.update({process_id: {'id': id, 'listener': listener}}) @pyqtSlot() - def on_finished(self): - result = self._worker.result - self._worker = None - self._listener.on_processing_finished(result) + def on_finished(self, process_id): + if process_id not in self._worker: + return + result = self._worker.pop(process_id)['worker'].result + self._listener.pop(process_id)['listener'].on_processing_finished(result) @pyqtSlot() - def on_error(self, error): - self._worker = None - self._listener.on_processing_error(error) + def on_error(self, process_id, error): + if process_id not in self._worker: + return + self._listener[process_id]['listener'].on_processing_error(error) + + def process(self, caller, func, id, *args, **kwargs): + """ + Process a function call with arbitrary arguments on a new thread. - def process(self, caller, func, *args, **kwargs): + :param caller: ?? + :param func: The function to be evaluated. + :param id: An identifying integer for the task. + :param args: args for func. + :param kwargs: keyword args for func. + """ + self.remove_already_processing(id) + process_id = uuid.uuid4() # Add the caller - self._add_listener(caller) + self._add_listener(caller, process_id, id) + + finished_callback = functools.partial(self.on_finished, process_id) + error_callback = functools.partial(self.on_error, process_id) + + worker = Worker(func, *args, **kwargs) + worker.signals.finished.connect(finished_callback) + worker.signals.error.connect(error_callback) + + self._worker.update({process_id: {'id': id, 'worker': worker}}) + + self.thread_pool.start(self._worker[process_id]['worker']) + + def remove_already_processing(self, id): + """ + Remove workers with ID + :param id: + """ + for key, process in list(self._listener.items()): + if process['id'] == id: + self._listener.pop(key) + self._worker.pop(key) + + def wait_for_done(self): + self.thread_pool.waitForDone() - # Generate worker - self._worker = Worker(func, *args, **kwargs) - self._worker.signals.finished.connect(self.on_finished) - self._worker.signals.error.connect(self.on_error) + def __eq__(self, other): + return self.__dict__ == other.__dict__ - QThreadPool.globalInstance().start(self._worker) + def __ne__(self, other): + return self.__dict__ != other.__dict__ diff --git a/scripts/MPLwidgets.py b/scripts/MPLwidgets.py new file mode 100644 index 0000000000000000000000000000000000000000..fe5e3f495d3d34e64f9ccdaa802f9aca8ccc498d --- /dev/null +++ b/scripts/MPLwidgets.py @@ -0,0 +1,16 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +from __future__ import (absolute_import, division, print_function) +from gui_helper import set_matplotlib_backend + +backend = set_matplotlib_backend() # must be at the top of this file +if backend == 'Qt4Agg': + from matplotlib.backends.backend_qt4agg import * # noqa +elif backend == 'Qt5Agg': + from matplotlib.backends.backend_qt5agg import * # noqa +else: + raise RuntimeError('Unrecognized backend {}'.format(backend)) diff --git a/scripts/Muon/GUI/Common/context_example/__init__.py b/scripts/Muon/GUI/Common/context_example/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/scripts/Muon/GUI/Common/context_example/context_example_model.py b/scripts/Muon/GUI/Common/context_example/context_example_model.py new file mode 100644 index 0000000000000000000000000000000000000000..5f860dc3274e4b1395e360c179b2080864070eea --- /dev/null +++ b/scripts/Muon/GUI/Common/context_example/context_example_model.py @@ -0,0 +1,50 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +from __future__ import (absolute_import, division, print_function) + +from Muon.GUI.Common import group_object +from Muon.GUI.Common import pair_object + +from Muon.GUI.Common.muon_context.muon_context import * + + +class ContextExampleModel(object): + + def __init__(self, context): + self._context = context + + def getSubContext(self): + subContext = {} + group_names = [] + tmp = self._context.get(Groups) + for group in tmp: + group_names.append(group.name) + subContext["Group Names"] = group_names + pair = self._context.get(Pairs)[0] # there is only one + subContext["Pair_F"] = pair.FGroup + subContext["Pair_B"] = pair.BGroup + subContext["Pair_alpha"] = pair.alpha + + return subContext + + def updateContext(self, subContext): + group_names = subContext["Group Names"] + groups = [] + for name in group_names: + groups.append(group_object.group(name)) + self._context.set(Groups, groups) + + alpha = subContext["Pair_alpha"] + F_group = subContext["Pair_F"] + B_group = subContext["Pair_B"] + name = "pair test" + pair = pair_object.pair(name, F_group, B_group, alpha) + self._context.set(Pairs, [pair]) + + def getContext(self): + return self._context diff --git a/scripts/Muon/GUI/Common/context_example/context_example_presenter.py b/scripts/Muon/GUI/Common/context_example/context_example_presenter.py new file mode 100644 index 0000000000000000000000000000000000000000..bdb695f332d7918dc341b956ff08155cdd5c88e4 --- /dev/null +++ b/scripts/Muon/GUI/Common/context_example/context_example_presenter.py @@ -0,0 +1,41 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +from __future__ import (absolute_import, division, print_function) + + +class ContextExamplePresenter(object): + + def __init__(self, view, model): + self._model = model + self._view = view + self._view.groupChangedSignal.connect(self.groupChanged) + + @property + def widget(self): + return self._view + + @property + def model(self): + return self._model + + def groupChanged(self, row): + # the groups are all row 0 + if row == 0: + # make sure the context is up to date + self.updateContext() + # update the whole GUI (not just this MVP) + self._view.sendUpdateSignal() + + def updateContext(self): + """ + A simple method for updating the context. + Gets the current values from the view and the model + will update the context accordingly (inc. packing the data) + """ + subcontext = self._view.getSubContext() + self._model.updateContext(subcontext) diff --git a/scripts/Muon/GUI/Common/context_example/context_example_view.py b/scripts/Muon/GUI/Common/context_example/context_example_view.py new file mode 100644 index 0000000000000000000000000000000000000000..fe249aad739bd1b20352c7d5c5ee6f2925f9b6a3 --- /dev/null +++ b/scripts/Muon/GUI/Common/context_example/context_example_view.py @@ -0,0 +1,126 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +from __future__ import (absolute_import, division, print_function) + + +from PyQt4 import QtCore +from PyQt4 import QtGui + +from Muon.GUI.Common import table_utils + + +class ContextExampleView(QtGui.QWidget): + + updateSignal = QtCore.pyqtSignal() + groupChangedSignal = QtCore.pyqtSignal(object) + + def __init__(self, context, parent=None): + super(ContextExampleView, self).__init__(parent) + self.grid = QtGui.QGridLayout(self) + + self.table = QtGui.QTableWidget(self) + self.table.resize(800, 800) + self.table.setRowCount(2) + self.table.setColumnCount(4) + self.table.setColumnWidth(0, 300) + self.table.setColumnWidth(1, 100) + self.table.setColumnWidth(2, 100) + self.table.setColumnWidth(3, 100) + self.table.verticalHeader().setVisible(False) + self.table.horizontalHeader().setStretchLastSection(True) + self.table.setHorizontalHeaderLabels( + ("Property;Value 1; Value 2; Value3").split(";")) + # populate table + + # row of groups + table_utils.setRowName(self.table, 0, "Groups") + group_name = ["a", "b", "c"] + self.ws0 = table_utils.addDoubleToTable( + self.table, group_name[0], 0, 1) + self.ws1 = table_utils.addDoubleToTable( + self.table, group_name[1], 0, 2) + self.ws2 = table_utils.addDoubleToTable( + self.table, group_name[2], 0, 3) + + # row to describe a pair + table_utils.setRowName(self.table, 1, "Pair") + self.g1 = table_utils.addComboToTable(self.table, 1, group_name, 1) + self.g2 = table_utils.addComboToTable(self.table, 1, group_name, 2) + self.alpha = table_utils.addDoubleToTable(self.table, "2.", 1, 3) + + # explicit update button + btn = QtGui.QPushButton("print context", self) + + # create grid + self.grid.addWidget(self.table) + self.grid.addWidget(btn) + + # add connections + btn.clicked.connect(self.sendUpdateSignal) + # needed for updating the possible pairs when groups change + self.table.itemChanged.connect(self.groupChanged) + # load values into GUI from context + self.loadFromContext(context) + + # signals + def sendUpdateSignal(self): + self.updateSignal.emit() + + def groupChanged(self, cell): + self.groupChangedSignal.emit(cell.row()) + + # get layout + def getLayout(self): + return self.grid + + # context interaction + def loadFromContext(self, context): + """ + Create a simple dict of the values + from the GUI. This does not alter + how the information is stored + """ + # make sure we dont fire signals during update + self.table.blockSignals(True) + + # get the group names and update the GUI + group_name = context["Group Names"] + + self.ws0.setText(group_name[0]) + self.ws1.setText(group_name[1]) + self.ws2.setText(group_name[2]) + + # update combo boxes (clear and repopulate) + self.g1.clear() + self.g2.clear() + self.g1.addItems(group_name) + self.g2.addItems(group_name) + # set correct selection for pair + index = self.g1.findText(context["Pair_F"]) + self.g1.setCurrentIndex(index) + index = self.g2.findText(context["Pair_B"]) + self.g2.setCurrentIndex(index) + # set alpha for pair + self.alpha.setText(str(context["Pair_alpha"])) + + # turn signals back on + self.table.blockSignals(False) + + def getSubContext(self): + """ + This packs up the information + from the GUI into a simple dict that can be translated + into the context + """ + context = {} + context["Group Names"] = [ + self.ws0.text(), self.ws1.text(), self.ws2.text()] + context["Pair_F"] = str(self.g1.currentText()) + context["Pair_B"] = str(self.g2.currentText()) + context["Pair_alpha"] = float(self.alpha.text()) + return context diff --git a/scripts/Muon/GUI/Common/context_example/context_example_widget.py b/scripts/Muon/GUI/Common/context_example/context_example_widget.py new file mode 100644 index 0000000000000000000000000000000000000000..ca9f70084a5e1fe2fc3fb4e7c1c56778fbaabd29 --- /dev/null +++ b/scripts/Muon/GUI/Common/context_example/context_example_widget.py @@ -0,0 +1,56 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +from __future__ import (absolute_import, division, print_function) + + +from Muon.GUI.Common.context_example.context_example_view import ContextExampleView +from Muon.GUI.Common.context_example.context_example_presenter import ContextExamplePresenter +from Muon.GUI.Common.context_example.context_example_model import ContextExampleModel + + +class ContextExampleWidget(object): + + """ + An example of how to use the context with a widget class. + The widget class exposes the MVP to the rest of the GUI + """ + + def __init__(self, context, parent=None): + model = ContextExampleModel(context) + sub_context = model.getSubContext() + view = ContextExampleView(sub_context, parent) + self._presenter = ContextExamplePresenter(view, model) + + @property + def presenter(self): + return self._presenter + + @property + def widget(self): + return self._presenter.widget + + # interact with context + def setUpdateContext(self, slot): + """ + This function is to set the update + method from the main GUI to the signals + from this GUI + """ + view = self._presenter.widget + view.updateSignal.connect(slot) + + def updateContext(self): + self._presenter.updateContext() + + def loadFromContext(self, context): + # extract relevant info from context via model + model = self._presenter.model + sub_context = model.getSubContext() + # update the view with the subcontext + view = self._presenter.widget + view.loadFromContext(sub_context) diff --git a/scripts/Muon/GUI/Common/dummy_label/dummy_label_model.py b/scripts/Muon/GUI/Common/dummy_label/dummy_label_model.py new file mode 100644 index 0000000000000000000000000000000000000000..959316987d7e4e3648b4e3f9e445156505921797 --- /dev/null +++ b/scripts/Muon/GUI/Common/dummy_label/dummy_label_model.py @@ -0,0 +1,23 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +from __future__ import (absolute_import, division, print_function) + + +class DummyLabelModel(object): + + def __init__(self, context, key): + self._context = context + self._key = key + + def getSubContext(self): + subContext = {} + subContext["label"] = self._context.get(self._key) + return subContext + + def updateContext(self, subContext): + self._context.set(self._key, subContext["label"]) diff --git a/scripts/Muon/GUI/Common/dummy_label/dummy_label_presenter.py b/scripts/Muon/GUI/Common/dummy_label/dummy_label_presenter.py index c96eb8b6e3fcff7b610ec7ab168e96acfc9b132c..107501697c24b949d1f347518c7eeb3f7645dea0 100644 --- a/scripts/Muon/GUI/Common/dummy_label/dummy_label_presenter.py +++ b/scripts/Muon/GUI/Common/dummy_label/dummy_label_presenter.py @@ -8,15 +8,31 @@ from __future__ import (absolute_import, division, print_function) class DummyLabelPresenter(object): + """ """ - def __init__(self,view,model): - self.view=view - self.model=model + + def __init__(self, view, model): + self._view = view + self._model = model @property def widget(self): - return self.view + return self._view + + @property + def model(self): + return self._model + + def updateLabel(self, message): + self._view.updateLabel(message) - def updateLabel(self,message): - self.view.updateLabel(message) + # interact with context + def updateContext(self): + """ + A simple method for updating the context. + Gets the current values from the view and the model + will update the context accordingly (inc. packing the data) + """ + subcontext = self._view.getSubContext() + self._model.updateContext(subcontext) diff --git a/scripts/Muon/GUI/Common/dummy_label/dummy_label_view.py b/scripts/Muon/GUI/Common/dummy_label/dummy_label_view.py index 7edd7a80845fe02ed9e982625749cb8c22d01d0e..b82cf956687c024086b80871c30b938018606f23 100644 --- a/scripts/Muon/GUI/Common/dummy_label/dummy_label_view.py +++ b/scripts/Muon/GUI/Common/dummy_label/dummy_label_view.py @@ -12,15 +12,25 @@ from PyQt4 import QtGui class DummyLabelView(QtGui.QWidget): - def __init__(self, name, parent=None): + def __init__(self, subcontext, parent=None): super(DummyLabelView, self).__init__(parent) self.grid = QtGui.QGridLayout(self) - self.label = QtGui.QLabel(name) + self.label = QtGui.QLabel("none") self.grid.addWidget(self.label) + self.loadFromContext(subcontext) def getLayout(self): return self.grid def updateLabel(self, message): self.label.setText("The " + message + " has been pressed") + + # interact with context + def loadFromContext(self, subcontext): + self.label.setText(subcontext["label"]) + + def getSubContext(self): + subcontext = {} + subcontext["label"] = str(self.label.text()) + return subcontext diff --git a/scripts/Muon/GUI/Common/dummy_label/dummy_label_widget.py b/scripts/Muon/GUI/Common/dummy_label/dummy_label_widget.py index 4a1a438221241eb1276d6171b8105be23df8c5a6..b2e518b23a3b5b86a15eac7a8939d8181cc116e8 100644 --- a/scripts/Muon/GUI/Common/dummy_label/dummy_label_widget.py +++ b/scripts/Muon/GUI/Common/dummy_label/dummy_label_widget.py @@ -8,21 +8,35 @@ from __future__ import (absolute_import, division, print_function) from Muon.GUI.Common.dummy_label.dummy_label_view import DummyLabelView from Muon.GUI.Common.dummy_label.dummy_label_presenter import DummyLabelPresenter +from Muon.GUI.Common.dummy_label.dummy_label_model import DummyLabelModel class DummyLabelWidget(object): - def __init__(self, name, parent=None): - view = DummyLabelView(name, parent) - model = None - self.presenter = DummyLabelPresenter(view, model) + def __init__(self, context, key, parent=None): + model = DummyLabelModel(context, key) + sub_context = model.getSubContext() + view = DummyLabelView(sub_context, parent) + self._presenter = DummyLabelPresenter(view, model) def getPresenter(self): - return self.presenter + return self._presenter @property def widget(self): - return self.presenter.widget + return self._presenter.widget def updateLabel(self, message): - self.presenter.updateLabel(message) + self._presenter.updateLabel(message) + + # interact with context + def updateContext(self): + self._presenter.updateContext() + + def loadFromContext(self, context): + # extract relevant info from context via model + model = self._presenter.model + sub_context = model.getSubContext() + # update the view with the subcontext + view = self._presenter.widget + view.loadFromContext(sub_context) diff --git a/scripts/Muon/GUI/Common/group_object.py b/scripts/Muon/GUI/Common/group_object.py new file mode 100644 index 0000000000000000000000000000000000000000..089cf2f0da880923675c4a0c554a5fd7c4d49823 --- /dev/null +++ b/scripts/Muon/GUI/Common/group_object.py @@ -0,0 +1,29 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + + +class group(object): + + def __init__(self, name="", dets=[]): + self._name = name + self._dets = dets + + @property + def name(self): + return self._name + + def setName(self, name): + self._name = name + + def setDets(self, dets): + self._dets = dets + + def isValid(self): + if self._name != "" and len(self._dets): + return True + + return False diff --git a/scripts/Muon/GUI/Common/muon_context/__init__.py b/scripts/Muon/GUI/Common/muon_context/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/scripts/Muon/GUI/Common/muon_context/muon_context.py b/scripts/Muon/GUI/Common/muon_context/muon_context.py new file mode 100644 index 0000000000000000000000000000000000000000..ccdd8ef3dca5449c3eaa0bf06885186265030257 --- /dev/null +++ b/scripts/Muon/GUI/Common/muon_context/muon_context.py @@ -0,0 +1,57 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +# Muon context - contains all of the values from the GUI +from __future__ import (absolute_import, division, print_function) +from Muon.GUI.Common import group_object +from Muon.GUI.Common import pair_object + +# constant variable names +Tab2Text = "some text" +HelpText = "help text" +LoadText = "load dummy" +Groups = "groups" +Pairs = "pairs" + + +class MuonContext(object): + + def __init__(self): + self.common_context = {} + self.common_context[Tab2Text] = "boo - start up" + self.common_context[HelpText] = "Help dummy" + self.common_context[LoadText] = "load dummy" + self.common_context[Groups] = [group_object.group( + "fwd"), + group_object.group("bwd"), + group_object.group("top")] + self.common_context[ + Pairs] = [ + pair_object.pair( + "test pair", + "bwd", + "top", + 0.9)] + + def set(self, key, value): + self.common_context[key] = value + + def get(self, key): + return self.common_context[key] + + def printContext(self): + print(Tab2Text, self.common_context[Tab2Text]) + print(HelpText, self.common_context[HelpText]) + print(LoadText, self.common_context[LoadText]) + print(Groups) + for group in self.common_context[Groups]: + print(group.name) + print + print(Pairs) + for pair in self.common_context[Pairs]: + print(pair.name, pair.FGroup, pair.BGroup, pair.alpha) + print() diff --git a/scripts/Muon/GUI/Common/pair_object.py b/scripts/Muon/GUI/Common/pair_object.py new file mode 100644 index 0000000000000000000000000000000000000000..544918714a4285bb0effaebfa78705a4ccc01108 --- /dev/null +++ b/scripts/Muon/GUI/Common/pair_object.py @@ -0,0 +1,49 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + + +class pair(object): + + def __init__(self, name="", F_group="", B_group="", alpha=1.0): + self._name = name + self._F_group = F_group + self._B_group = B_group + self._alpha = alpha + + def setName(self, name): + self._name = name + + def setFGroup(self, group): + self._F_group = group + + def setBGroup(self, group): + self._B_group = group + + def setAlpha(self, alpha): + self._alpha = alpha + + @property + def name(self): + return self._name + + @property + def FGroup(self): + return self._F_group + + @property + def BGroup(self): + return self._B_group + + @property + def alpha(self): + return self._alpha + + def isValid(self): + if self._name != "" and self._F_group != "" and self._B_group != "": + return True + + return False diff --git a/scripts/Muon/GUI/Common/table_utils.py b/scripts/Muon/GUI/Common/table_utils.py index 4a7587dfd47a08ace520d61e1cbd5108f00a4649..799c5ccca09175c650eae941f02b5d9cbd2af8db 100644 --- a/scripts/Muon/GUI/Common/table_utils.py +++ b/scripts/Muon/GUI/Common/table_utils.py @@ -21,20 +21,20 @@ def setRowName(table,row,name): table.setItem(row,0, text) -def addComboToTable(table,row,options): +def addComboToTable(table,row,options,col=1): combo=QtGui.QComboBox() combo.addItems(options) - table.setCellWidget(row,1,combo) + table.setCellWidget(row,col,combo) return combo -def addDoubleToTable(table,value,row): +def addDoubleToTable(table,value,row,col=1): numberWidget = QtGui.QTableWidgetItem(str(value)) - table.setItem(row,1, numberWidget) + table.setItem(row,col, numberWidget) return numberWidget -def addCheckBoxToTable(table,state,row): +def addCheckBoxToTable(table,state,row,col=1): box = QtGui.QTableWidgetItem() box.setFlags(QtCore.Qt.ItemIsUserCheckable |QtCore.Qt.ItemIsEnabled) if state: @@ -42,16 +42,16 @@ def addCheckBoxToTable(table,state,row): else: box.setCheckState(QtCore.Qt.Unchecked) - table.setItem(row,1, box) + table.setItem(row,col, box) return box -def addSpinBoxToTable(table,default,row): +def addSpinBoxToTable(table,default,row,col=1): box = QtGui.QSpinBox() if default > 99: box.setMaximum(default*10) box.setValue(default) - table.setCellWidget(row,1,box) + table.setCellWidget(row,col,box) return box diff --git a/scripts/Muon/GUI/Common/thread_model.py b/scripts/Muon/GUI/Common/thread_model.py index 9acdbe0248890d263c2ff2af3aafe038870cea75..ed79652b6d7b4b4cea596a48ac9d2a842a5ad793 100644 --- a/scripts/Muon/GUI/Common/thread_model.py +++ b/scripts/Muon/GUI/Common/thread_model.py @@ -22,31 +22,10 @@ class ThreadModel(QThread): QThread.__init__(self) self.model = model - self.start_slot = None - self.end_slot = None - - self.check_model_has_correct_attributes() - - def check_model_has_correct_attributes(self): - if hasattr(self.model, "execute") and hasattr(self.model, "output"): - return - raise AttributeError("Please ensure the model passed to ThreadModel has implemented" - " execute() and output() methods") - - def connect_exception_slot(self): - self.exceptionSignal.connect(message_box.warning) - - def disconnect_exception_slot(self): - self.exceptionSignal.disconnect(message_box.warning) - def __del__(self): - try: - self.wait() - except RuntimeError: - pass + self.wait() def run(self): - self.connect_exception_slot() self.user_cancel = False try: self.model.execute() @@ -59,8 +38,6 @@ class ThreadModel(QThread): else: self.sendSignal(error) pass - finally: - self.disconnect_exception_slot() def sendSignal(self, error): self.exceptionSignal.emit(error) @@ -79,22 +56,14 @@ class ThreadModel(QThread): # if there are multiple inputs (alg>1) def loadData(self, inputs): - if not hasattr(self.model, "loadData"): - raise AttributeError("The model passed to ThreadModel has not implemented" - " loadData() method, which it is attempting to call.") self.model.loadData(inputs) def threadWrapperSetUp(self, startSlot, endSlot): - assert hasattr(startSlot, '__call__') - assert hasattr(endSlot, '__call__') - self.start_slot, self.end_slot = startSlot, endSlot - self.started.connect(self.start_slot) - self.finished.connect(self.end_slot) - self.finished.connect(self.threadWrapperTearDown) + self.started.connect(startSlot) + self.finished.connect(endSlot) + self.exceptionSignal.connect(message_box.warning) - def threadWrapperTearDown(self): - self.started.disconnect(self.start_slot) - self.finished.disconnect(self.end_slot) - self.finished.disconnect(self.threadWrapperTearDown) - self.start_slot = None - self.end_slot = None + def threadWrapperTearDown(self,startSlot,endSlot): + self.started.disconnect(startSlot) + self.finished.disconnect(endSlot) + self.exceptionSignal.disconnect(message_box.warning) diff --git a/scripts/Muon/GUI/ElementalAnalysis/Detectors/detectors_presenter.py b/scripts/Muon/GUI/ElementalAnalysis/Detectors/detectors_presenter.py index 45f85d8b875a24db59ec45280dfc3a64b8a7e302..a4c1eb4c776438c95b75871f71f724a05a720d38 100644 --- a/scripts/Muon/GUI/ElementalAnalysis/Detectors/detectors_presenter.py +++ b/scripts/Muon/GUI/ElementalAnalysis/Detectors/detectors_presenter.py @@ -7,10 +7,12 @@ class DetectorsPresenter(object): + def __init__(self, view): self.view = view - self.detectors = [ - self.view.GE1, - self.view.GE2, - self.view.GE3, - self.view.GE4] + self.detectors = [] + for name in self.view.widgets.keys(): + self.detectors.append(self.view.widgets[name]) + + def setStateQuietly(self, name, state): + self.view.setStateQuietly(name, state) diff --git a/scripts/Muon/GUI/ElementalAnalysis/Detectors/detectors_view.py b/scripts/Muon/GUI/ElementalAnalysis/Detectors/detectors_view.py index 737e6f215c597e7e50bfa1f8f2867506ab6e9089..0cbe03585b52d76c870121a64cd9531740388f10 100644 --- a/scripts/Muon/GUI/ElementalAnalysis/Detectors/detectors_view.py +++ b/scripts/Muon/GUI/ElementalAnalysis/Detectors/detectors_view.py @@ -6,21 +6,30 @@ # SPDX - License - Identifier: GPL - 3.0 + from PyQt4 import QtGui +from collections import OrderedDict + + from Muon.GUI.Common.checkbox import Checkbox class DetectorsView(QtGui.QWidget): + def __init__(self, parent=None): super(DetectorsView, self).__init__(parent) self.list = QtGui.QVBoxLayout() - self.GE1 = Checkbox("GE1") - self.GE2 = Checkbox("GE2") - self.GE3 = Checkbox("GE3") - self.GE4 = Checkbox("GE4") + self.widgets = OrderedDict() + labels = ["GE1", "GE2", "GE3", "GE4"] + for label in labels: + self.widgets[label] = Checkbox(label) self.list.addWidget(QtGui.QLabel("Detectors")) - for detector in [self.GE1, self.GE2, self.GE3, self.GE4]: - self.list.addWidget(detector) + for detector in self.widgets.keys(): + self.list.addWidget(self.widgets[detector]) self.setLayout(self.list) + + def setStateQuietly(self, name, state): + self.widgets[name].blockSignals(True) + self.widgets[name].setChecked(state) + self.widgets[name].blockSignals(False) diff --git a/scripts/Muon/GUI/ElementalAnalysis/Plotting/edit_windows/__init__.py b/scripts/Muon/GUI/ElementalAnalysis/Plotting/edit_windows/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/scripts/Muon/GUI/ElementalAnalysis/Plotting/edit_windows/remove_plot_window.py b/scripts/Muon/GUI/ElementalAnalysis/Plotting/edit_windows/remove_plot_window.py new file mode 100644 index 0000000000000000000000000000000000000000..7b3f6707dc28965b1cfd1f5209b248dbd3f9c5e4 --- /dev/null +++ b/scripts/Muon/GUI/ElementalAnalysis/Plotting/edit_windows/remove_plot_window.py @@ -0,0 +1,64 @@ +from __future__ import (absolute_import, division, print_function) + +from qtpy import QtCore, QtWidgets + +from Muon.GUI.Common import table_utils + + +class RemovePlotWindowView(QtWidgets.QDialog): + applyRemoveSignal = QtCore.Signal(object) + closeEventSignal = QtCore.Signal() + + def __init__(self, lines, subplot, parent=None): + super(RemovePlotWindowView, self).__init__() + + self._subplot = subplot + + self.grid = QtWidgets.QGridLayout() + + self.table = QtWidgets.QTableWidget(self) + self.table.resize(200, 200) + + self.table.setRowCount(len(lines)) + self.table.setColumnCount(2) + self.table.setColumnWidth(0, 150) + self.table.setColumnWidth(1, 50) + self.table.verticalHeader().setVisible(False) + self.table.horizontalHeader().setStretchLastSection(True) + self.table.setHorizontalHeaderLabels( + ("Line name;Remove").split(";")) + table_utils.setTableHeaders(self.table) + + self.widgets = {} + for index, line in enumerate(lines): + table_utils.setRowName(self.table, index, line) + tmp = { + "line": line, + "box": table_utils.addCheckBoxToTable( + self.table, + False, + index)} + self.widgets[line] = tmp + + self.grid.addWidget(self.table) + btn = QtWidgets.QPushButton("Remove") + self.grid.addWidget(btn) + self.setLayout(self.grid) + self.setWindowTitle("Remove Lines For " + self._subplot) + btn.clicked.connect(self.buttonClick) + + def closeEvent(self, event): + self.closeEventSignal.emit() + + def buttonClick(self): + self.applyRemoveSignal.emit(self.widgets.keys()) + + def getState(self, name): + return self.widgets[name]["box"].checkState() == QtCore.Qt.Checked + + def getLine(self, name): + return self.widgets[name]["line"] + + @property + def subplot(self): + return self._subplot diff --git a/scripts/Muon/GUI/ElementalAnalysis/Plotting/edit_windows/select_subplot.py b/scripts/Muon/GUI/ElementalAnalysis/Plotting/edit_windows/select_subplot.py new file mode 100644 index 0000000000000000000000000000000000000000..758828880fd4eb8e81a4c9350b1d9ac8d8290a2a --- /dev/null +++ b/scripts/Muon/GUI/ElementalAnalysis/Plotting/edit_windows/select_subplot.py @@ -0,0 +1,30 @@ +from __future__ import (absolute_import, division, print_function) + +from qtpy import QtCore, QtWidgets + + +class SelectSubplot(QtWidgets.QDialog): + + subplotSelectorSignal = QtCore.Signal(object) + closeEventSignal = QtCore.Signal() + + def __init__(self, subplots, parent=None): + super(SelectSubplot, self).__init__() + + self.grid = QtWidgets.QGridLayout() + self.combo = QtWidgets.QComboBox() + self.combo.addItems(subplots) + self.grid.addWidget(self.combo) + + btn = QtWidgets.QPushButton("ok") + self.grid.addWidget(btn) + self.setLayout(self.grid) + self.setWindowTitle("Edit Lines Subplot Selector") + btn.clicked.connect(self.buttonClick) + + def closeEvent(self, event): + self.closeEventSignal.emit() + + def buttonClick(self): + pick = self.combo.currentText() + self.subplotSelectorSignal.emit(pick) diff --git a/scripts/Muon/GUI/ElementalAnalysis/Plotting/navigation_toolbar.py b/scripts/Muon/GUI/ElementalAnalysis/Plotting/navigation_toolbar.py new file mode 100644 index 0000000000000000000000000000000000000000..55e5e526169d8ac74b2823f1a64fc09c3dd8c76a --- /dev/null +++ b/scripts/Muon/GUI/ElementalAnalysis/Plotting/navigation_toolbar.py @@ -0,0 +1,23 @@ +from __future__ import (absolute_import, division, print_function) +from qtpy import QtGui +from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar + + +class myToolbar(NavigationToolbar): + # only display the buttons we need + toolitems = [tool for tool in NavigationToolbar.toolitems if + tool[0] in ("Home", "Save", "Pan", "Zoom")] + + def __init__(self, *args, **kwargs): + super(myToolbar, self).__init__(*args, **kwargs) + self.layout().takeAt(5) # or more than 1 if you have more buttons + pm = QtGui.QPixmap() + ic = QtGui.QIcon(pm) + # self.add = self.addAction(ic, "Add line") + self.rm = self.addAction(ic, "Remove line") + + def setAddConnection(self, slot): + self.add.triggered.connect(slot) + + def setRmConnection(self, slot): + self.rm.triggered.connect(slot) diff --git a/scripts/Muon/GUI/ElementalAnalysis/Plotting/plotting_presenter.py b/scripts/Muon/GUI/ElementalAnalysis/Plotting/plotting_presenter.py index e9110f26f356d6985613a70ab7f3a3390e377b88..8e8dfd0a9cd752fa64dabf6df7b73a60a9dc7469 100644 --- a/scripts/Muon/GUI/ElementalAnalysis/Plotting/plotting_presenter.py +++ b/scripts/Muon/GUI/ElementalAnalysis/Plotting/plotting_presenter.py @@ -1,3 +1,4 @@ +from __future__ import (absolute_import, division, print_function) # Mantid Repository : https://github.com/mantidproject/mantid # # Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, @@ -5,10 +6,19 @@ # & Institut Laue - Langevin # SPDX - License - Identifier: GPL - 3.0 + +from Muon.GUI.ElementalAnalysis.Plotting.edit_windows.remove_plot_window import RemovePlotWindowView +from Muon.GUI.ElementalAnalysis.Plotting.edit_windows.select_subplot import SelectSubplot + class PlotPresenter(object): + def __init__(self, view): self.view = view + # self.view.setAddConnection(self.add) + self.view.setRmConnection(self.rm) + self.view.plotCloseConnection(self.close) + self.rmWindow = None + self.selectorWindow = None def update_canvas(self): """ Redraws the canvas. """ @@ -56,3 +66,89 @@ class PlotPresenter(object): def add_moveable_hline(self, plot_name, y_value, x_min, x_max, **kwargs): pass + + def removeSubplotConnection(self, slot): + self.view.subplotRemovedSignal.connect(slot) + + def close(self): + if self.selectorWindow is not None: + self.closeSelectorWindow() + if self.rmWindow is not None: + self.closeRmWindow() + + def add(self): + print("to do") + + def rm(self): + names = self.view.subplot_names + # if the remove window is not visable + if self.rmWindow is not None: + self.raiseRmWindow() + # if the selector is not visable + elif self.selectorWindow is not None: + self.raiseSelectorWindow() + # if only one subplot just skip selector + elif len(names) == 1: + self.getRmWindow(names[0]) + # if no selector and no remove window -> let user pick which subplot to + # change + else: + self.selectorWindow = self.createSelectWindow(names) + self.selectorWindow.subplotSelectorSignal.connect(self.getRmWindow) + self.selectorWindow.closeEventSignal.connect( + self.closeSelectorWindow) + self.selectorWindow.setMinimumSize(300, 100) + self.selectorWindow.show() + + def createSelectWindow(self, names): + return SelectSubplot(names) + + def raiseRmWindow(self): + self.rmWindow.raise_() + + def raiseSelectorWindow(self): + self.selectorWindow.raise_() + + def closeSelectorWindow(self): + if self.selectorWindow is not None: + self.selectorWindow.close + self.selectorWindow = None + + def createRmWindow(self, subplot): + return RemovePlotWindowView(lines=self.view.line_labels(subplot), subplot=subplot, parent=self) + + def getRmWindow(self, subplot): + # always close selector after making a selection + self.closeSelectorWindow() + # create the remove window + self.rmWindow = self.createRmWindow(subplot=subplot) + self.rmWindow.applyRemoveSignal.connect(self.applyRm) + self.rmWindow.closeEventSignal.connect(self.closeRmWindow) + self.rmWindow.setMinimumSize(200, 200) + self.rmWindow.show() + + def applyRm(self, names): + remove_subplot = True + # remove the lines from the subplot + for name in names: + if self.rmWindow.getState(name): + line = self.rmWindow.getLine(name) + # self.view.get_subplot(self.rmWindow.subplot).lines.remove(line) + self.view.removeLine(self.rmWindow.subplot, line) + else: + remove_subplot = False + # if all of the lines have been removed -> delete subplot + if remove_subplot: + # add a signal to this method - so we can catch it + self.remove_subplot(self.rmWindow.subplot) + self.update_canvas() + # if no subplots then close plotting window + if not self.get_subplots(): + self.closeRmWindow() + self.view.close() + else: + self.closeRmWindow() + + def closeRmWindow(self): + self.rmWindow.close + self.rmWindow = None diff --git a/scripts/Muon/GUI/ElementalAnalysis/Plotting/plotting_view.py b/scripts/Muon/GUI/ElementalAnalysis/Plotting/plotting_view.py index 59f457c475002c8cf822189e6af279f41bc969fc..7dd2fa4d99b38107ae3aca5d2c26f7c15a2bf9c4 100644 --- a/scripts/Muon/GUI/ElementalAnalysis/Plotting/plotting_view.py +++ b/scripts/Muon/GUI/ElementalAnalysis/Plotting/plotting_view.py @@ -1,3 +1,4 @@ +from __future__ import (absolute_import, division, print_function) # Mantid Repository : https://github.com/mantidproject/mantid # # Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, @@ -8,27 +9,34 @@ from six import iteritems from mantid import plots from collections import OrderedDict +from copy import copy -from qtpy import QtWidgets +from qtpy import QtWidgets, QtCore from matplotlib.figure import Figure from matplotlib import gridspec from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas + # pyplot should not be imported: # https://stackoverflow.com/posts/comments/26295260 +from Muon.GUI.ElementalAnalysis.Plotting.navigation_toolbar import myToolbar +from Muon.GUI.ElementalAnalysis.Plotting.subPlot_object import subPlot + from Muon.GUI.ElementalAnalysis.Plotting import plotting_utils as putils from Muon.GUI.ElementalAnalysis.Plotting.AxisChanger.axis_changer_presenter import AxisChangerPresenter from Muon.GUI.ElementalAnalysis.Plotting.AxisChanger.axis_changer_view import AxisChangerView class PlotView(QtWidgets.QWidget): + subplotRemovedSignal = QtCore.Signal(object) + plotCloseSignal = QtCore.Signal() + def __init__(self): super(PlotView, self).__init__() self.plots = OrderedDict({}) self.errors_list = set() - self.workspaces = {} - self.workspace_plots = {} # stores the plotted 'graphs' for deletion + self.plot_storage = {} # stores lines and info to create lines self.current_grid = None self.gridspecs = { 1: gridspec.GridSpec(1, 1), @@ -62,10 +70,21 @@ class PlotView(QtWidgets.QWidget): button_layout.addWidget(self.errors) grid = QtWidgets.QGridLayout() - grid.addWidget(self.canvas, 0, 0) - grid.addLayout(button_layout, 1, 0) + + self.toolbar = myToolbar(self.canvas, self) + self.toolbar.update() + + grid.addWidget(self.toolbar, 0, 0) + grid.addWidget(self.canvas, 1, 0) + grid.addLayout(button_layout, 2, 0) self.setLayout(grid) + def setAddConnection(self, slot): + self.toolbar.setAddConnection(slot) + + def setRmConnection(self, slot): + self.toolbar.setRmConnection(slot) + def _redo_layout(func): """ Simple decorator (@_redo_layout) to call tight_layout() on plots @@ -172,14 +191,12 @@ class PlotView(QtWidgets.QWidget): Removes the previous plot and redraws with/without errors depending on the state. """ self._modify_errors_list(name, state) - workspaces = self.workspaces[name] - self.workspaces[name] = [] + # get a copy of all the workspaces + workspaces = copy(self.plot_storage[name].ws) # get the limits before replotting, so they appear unchanged. x, y = plot.get_xlim(), plot.get_ylim() - for old_plot in self.workspace_plots[name]: - old_plot.remove() - del old_plot - self.workspace_plots[name] = [] + # clear out the old container + self.plot_storage[name].delete() for workspace in workspaces: self.plot(name, workspace) plot.set_xlim(x) @@ -228,46 +245,41 @@ class PlotView(QtWidgets.QWidget): self.plot_selector.addItem("All") self.plot_selector.addItems(list(self.plots.keys())) - def _add_workspace_name(self, name, workspace): - """ Adds a workspace to a plot's list of workspaces. """ - try: - if workspace not in self.workspaces[name]: - self.workspaces[name].append(workspace) - except KeyError: - self.workspaces[name] = [workspace] - @_redo_layout def plot(self, name, workspace): """ Plots a workspace to a subplot (with errors, if necessary). """ - self._add_workspace_name(name, workspace) if name in self.errors_list: self.plot_workspace_errors(name, workspace) else: self.plot_workspace(name, workspace) self._set_bounds(name) - def _add_plotted_line(self, name, lines): + def _add_plotted_line(self, name, label, lines, workspace): """ Appends plotted lines to the related subplot list. """ - try: - self.workspace_plots[name].extend(lines) - except KeyError: - self.workspace_plots[name] = lines + self.plot_storage[name].addLine(label, lines, workspace) def plot_workspace_errors(self, name, workspace): """ Plots a workspace with errors, and appends caps/bars to the subplot list. """ subplot = self.get_subplot(name) line, cap_lines, bar_lines = plots.plotfunctions.errorbar( subplot, workspace, specNum=1) + # make a tmp plot to get auto generated legend name + tmp, = plots.plotfunctions.plot(subplot, workspace, specNum=1) + label = tmp.get_label() + # remove the tmp line + tmp.remove() + del tmp + # collect results all_lines = [line] all_lines.extend(cap_lines) all_lines.extend(bar_lines) - self._add_plotted_line(name, all_lines) + self._add_plotted_line(name, label, all_lines, workspace) def plot_workspace(self, name, workspace): """ Plots a workspace normally. """ subplot = self.get_subplot(name) line, = plots.plotfunctions.plot(subplot, workspace, specNum=1) - self._add_plotted_line(name, [line]) + self._add_plotted_line(name, line.get_label(), [line], workspace) def get_subplot(self, name): """ Returns the subplot corresponding to a given name """ @@ -280,14 +292,19 @@ class PlotView(QtWidgets.QWidget): def add_subplot(self, name): """ will raise KeyError if: plots exceed 4 """ self._update_gridspec(len(self.plots) + 1, last=name) + self.plot_storage[name] = subPlot(name) return self.get_subplot(name) def remove_subplot(self, name): """ will raise KeyError if: 'name' isn't a plot; there are no plots """ self.figure.delaxes(self.get_subplot(name)) del self.plots[name] - del self.workspaces[name] + del self.plot_storage[name] self._update_gridspec(len(self.plots)) + self.subplotRemovedSignal.emit(name) + + def removeLine(self, subplot, label): + self.plot_storage[subplot].removeLine(label) @_redo_layout def add_moveable_vline(self, plot_name, x_value, y_minx, y_max, **kwargs): @@ -296,3 +313,16 @@ class PlotView(QtWidgets.QWidget): @_redo_layout def add_moveable_hline(self, plot_name, y_value, x_min, x_max, **kwargs): pass + + def closeEvent(self, event): + self.plotCloseSignal.emit() + + def plotCloseConnection(self, slot): + self.plotCloseSignal.connect(slot) + + @property + def subplot_names(self): + return self.plot_storage.keys() + + def line_labels(self, subplot): + return self.plot_storage[subplot].lines.keys() diff --git a/scripts/Muon/GUI/ElementalAnalysis/Plotting/subPlot_object.py b/scripts/Muon/GUI/ElementalAnalysis/Plotting/subPlot_object.py new file mode 100644 index 0000000000000000000000000000000000000000..d0d7ef50fbca413cfba7c653bdb35f8abc20a91a --- /dev/null +++ b/scripts/Muon/GUI/ElementalAnalysis/Plotting/subPlot_object.py @@ -0,0 +1,57 @@ +from __future__ import (absolute_import, division, print_function) +from six import iteritems + +# use this to manage lines and workspaces directly + + +class subPlot(object): + + def __init__(self, name): + self.name = name + self._ws = {} + self._lines = {} + self._specNum = {} + + def addLine(self, label, lines, ws, specNum=1): + # line will be a list - will include error bars + self._lines[label] = lines + self._specNum[label] = specNum + if ws not in self._ws.keys(): + self._ws[ws] = [label] + else: + self._ws[ws].append(label) + + @property + def lines(self): + return self._lines + + @property + def ws(self): + return self._ws + + @property + def specNum(self): + return self._specNum + + # seems to work - need to add remove specNum and ws. + def removeLine(self, name): + lines = self._lines[name] + for line in lines: + line.remove() + del line + del self._lines[name] + del self._specNum[name] + # remove line for ws + to_delete = [] + for key, list in iteritems(self._ws): + if name in list: + list.remove(name) + if len(list) == 0: + to_delete.append(key) + for key in to_delete: + del self._ws[key] + + def delete(self): + keys = self._lines.keys() + for label in keys: + self.removeLine(label) diff --git a/scripts/Muon/GUI/MuonAnalysis/dock/dock_widget.py b/scripts/Muon/GUI/MuonAnalysis/dock/dock_widget.py index 4eaa7c5cd381259eb6c8236102515cd72433e74f..e50f98460c84b56208cd81c02810e86bed32cdca 100644 --- a/scripts/Muon/GUI/MuonAnalysis/dock/dock_widget.py +++ b/scripts/Muon/GUI/MuonAnalysis/dock/dock_widget.py @@ -8,10 +8,13 @@ from __future__ import (absolute_import, division, print_function) from PyQt4 import QtGui +from Muon.GUI.Common.context_example.context_example_widget import ContextExampleWidget from Muon.GUI.Common.dummy.dummy_widget import DummyWidget from Muon.GUI.Common.dummy_label.dummy_label_widget import DummyLabelWidget from Muon.GUI.Common.dock.dock_view import DockView +from Muon.GUI.Common.muon_context.muon_context import * + class DockWidget(QtGui.QWidget): @@ -24,17 +27,21 @@ class DockWidget(QtGui.QWidget): populates it """ - def __init__(self, parent=None): + def __init__(self, context, parent=None): super(DockWidget, self).__init__(parent) + self.dockWidget = QtGui.QWidget() self.dock_view = DockView(self) + self.context_example = ContextExampleWidget(context, parent=self) + self.dock_view.addDock(self.context_example.widget, "Example context") + self.btn = DummyWidget("moo", self) self.dock_view.addDock(self.btn.widget, "first") self.btn.setButtonConnection(self.handleButton) - self.label = DummyLabelWidget("boo", self) + self.label = DummyLabelWidget(context, Tab2Text, self) self.dock_view.addDock(self.label.widget, "second") self.btn2 = DummyWidget("waaa", self) @@ -49,12 +56,30 @@ class DockWidget(QtGui.QWidget): self.dockWidget.setLayout(QHbox) + # set signals and slots + def setUpdateContext(self, slot): + self.context_example.setUpdateContext(slot) + # the buttons change the label value + # so we want to update context + self.btn.setButtonConnection(slot) + self.btn2.setButtonConnection(slot) + def loadFromProject(self, project): self.label.updateLabel(project) def handleButton(self, message): self.label.updateLabel(message) + # interaction with context + def updateContext(self): + self.label.updateContext() + self.context_example.updateContext() + + def loadFromContext(self, context): + self.label.loadFromContext(context) + self.context_example.loadFromContext(context) + + # needed for docking @property def widget(self): return self.dockWidget diff --git a/scripts/Muon_Analysis_2.py b/scripts/Muon_Analysis_2.py index 71d72babb12a703a2048a689977d4ac390ab0118..c7f9f016ba68db1fb96ec1b9d3328540454abde5 100644 --- a/scripts/Muon_Analysis_2.py +++ b/scripts/Muon_Analysis_2.py @@ -14,6 +14,7 @@ import PyQt4.QtCore as QtCore from Muon.GUI.Common.dummy_label.dummy_label_widget import DummyLabelWidget from Muon.GUI.MuonAnalysis.dock.dock_widget import DockWidget +from Muon.GUI.Common.muon_context.muon_context import *#MuonContext muonGUI = None @@ -23,19 +24,32 @@ class MuonAnalysis2Gui(QtGui.QMainWindow): def __init__(self, parent=None): super(MuonAnalysis2Gui, self).__init__(parent) - loadWidget = DummyLabelWidget("Load dummy", self) - self.dockWidget = DockWidget(self) + self._context = MuonContext() - helpWidget = DummyLabelWidget("Help dummy", self) + self.loadWidget = DummyLabelWidget(self._context ,LoadText, self) + self.dockWidget = DockWidget(self._context,self) + + self.helpWidget = DummyLabelWidget(self._context,HelpText, self) splitter = QtGui.QSplitter(QtCore.Qt.Vertical) - splitter.addWidget(loadWidget.widget) + splitter.addWidget(self.loadWidget.widget) splitter.addWidget(self.dockWidget.widget) - splitter.addWidget(helpWidget.widget) + splitter.addWidget(self.helpWidget.widget) self.setCentralWidget(splitter) self.setWindowTitle("Muon Analysis version 2") + self.dockWidget.setUpdateContext(self.update) + + def update(self): + # update load + self.loadWidget.updateContext() + self.dockWidget.updateContext() + self.helpWidget.updateContext() + + self._context.printContext() + self.dockWidget.loadFromContext(self._context) + # cancel algs if window is closed def closeEvent(self, event): self.dockWidget.closeEvent(event) diff --git a/scripts/PyChop.py b/scripts/PyChop.py index 92f9e351b60b9f2b13ed51f69aac663ab99e093f..4d8b1e5db97e89e6aeeb8d4104eb309b89e8d03b 100644 --- a/scripts/PyChop.py +++ b/scripts/PyChop.py @@ -11,17 +11,12 @@ Module to import and run the PyChop GUI for use either on the commandline or as """ import sys -from PyQt4 import QtGui from PyChop import PyChopGui +from gui_helper import set_matplotlib_backend, get_qapplication +set_matplotlib_backend() # must be called before anything tries to use matplotlib -if __name__ == '__main__': - if QtGui.QApplication.instance(): - app = QtGui.QApplication.instance() - else: - app = QtGui.QApplication(sys.argv) - window = PyChopGui.PyChopGui() - window.show() - try: # check if started from within mantidplot - import mantidplot # noqa - except ImportError: - sys.exit(app.exec_()) +app, within_mantid = get_qapplication() +window = PyChopGui.PyChopGui() +window.show() +if not within_mantid: + sys.exit(app.exec_()) diff --git a/scripts/PyChop/PyChopGui.py b/scripts/PyChop/PyChopGui.py index 7003cca79e5172a03c3aabc28bc4115c7c0e4910..2c180553579a5dc5273ae786f55c1b80d2139cdb 100755 --- a/scripts/PyChop/PyChopGui.py +++ b/scripts/PyChop/PyChopGui.py @@ -23,14 +23,18 @@ import os import warnings import copy from .Instruments import Instrument -from PyQt4 import QtGui, QtCore -from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar +from qtpy.QtCore import (QEventLoop, Qt) # noqa +from qtpy.QtWidgets import (QAction, QCheckBox, QComboBox, QDialog, QFileDialog, QGridLayout, QHBoxLayout, QMenu, QLabel, + QLineEdit, QMainWindow, QMessageBox, QPushButton, QSizePolicy, QSpacerItem, QTabWidget, + QTextEdit, QVBoxLayout, QWidget) # noqa +from MPLwidgets import FigureCanvasQTAgg as FigureCanvas +from MPLwidgets import NavigationToolbar2QT as NavigationToolbar +import matplotlib from matplotlib.figure import Figure from matplotlib.widgets import Slider -class PyChopGui(QtGui.QMainWindow): +class PyChopGui(QMainWindow): """ GUI Class using PyQT for PyChop to help users plan inelastic neutron experiments at spallation sources by calculating the resolution and flux at a given neutron energies. @@ -229,7 +233,8 @@ class PyChopGui(QtGui.QMainWindow): def _set_overplot(self, overplot, axisname): axis = getattr(self, axisname) if overplot: - axis.hold(True) + if matplotlib.compare_versions('2.1.0',matplotlib.__version__): + axis.hold(True) else: setattr(self, axisname+'_xlim', 0) axis.clear() @@ -248,7 +253,8 @@ class PyChopGui(QtGui.QMainWindow): if hasattr(freq, '__len__'): freq = freq[0] if multiplot: - self.resaxes.hold(True) + if matplotlib.compare_versions('2.1.0',matplotlib.__version__): + self.resaxes.hold(True) for ie, Ei in enumerate(self.eis): en = np.linspace(0, 0.95*Ei, 200) if any(self.res[ie]): @@ -260,7 +266,8 @@ class PyChopGui(QtGui.QMainWindow): if self.tabs.isTabEnabled(self.qetabID): self.plot_qe(Ei, label_text, hold=True) self.resaxes_xlim = max(Ei, self.resaxes_xlim) - self.resaxes.hold(False) + if matplotlib.compare_versions('2.1.0',matplotlib.__version__): + self.resaxes.hold(False) else: ei = self.engine.getEi() en = np.linspace(0, 0.95*ei, 200) @@ -326,8 +333,9 @@ class PyChopGui(QtGui.QMainWindow): if update: self.flxaxes1.clear() self.flxaxes2.clear() - self.flxaxes1.hold(True) - self.flxaxes2.hold(True) + if matplotlib.compare_versions('2.1.0',matplotlib.__version__): + self.flxaxes1.hold(True) + self.flxaxes2.hold(True) for ii, instrument in enumerate(tmpinst): for ie, ei in enumerate(eis): with warnings.catch_warnings(record=True): @@ -344,8 +352,9 @@ class PyChopGui(QtGui.QMainWindow): flux[ie] = self.engine.getFlux(ei) elres[ie] = self.engine.getResolution(0., ei)[0] if overplot: - self.flxaxes1.hold(True) - self.flxaxes2.hold(True) + if matplotlib.compare_versions('2.1.0',matplotlib.__version__): + self.flxaxes1.hold(True) + self.flxaxes2.hold(True) else: self.flxaxes1.clear() self.flxaxes2.clear() @@ -410,8 +419,9 @@ class PyChopGui(QtGui.QMainWindow): flux[ie] = self.engine.getFlux(ei) elres[ie] = self.engine.getResolution(0., ei)[0] if overplot: - self.frqaxes1.hold(True) - self.frqaxes2.hold(True) + if matplotlib.compare_versions('2.1.0',matplotlib.__version__): + self.frqaxes1.hold(True) + self.frqaxes2.hold(True) else: self.frqaxes1.clear() self.frqaxes2.clear() @@ -451,13 +461,13 @@ class PyChopGui(QtGui.QMainWindow): self.scrtab.hide() def errormessage(self, message): - msg = QtGui.QMessageBox() + msg = QMessageBox() msg.setText(str(message)) - msg.setStandardButtons(QtGui.QMessageBox.Ok) + msg.setStandardButtons(QMessageBox.Ok) msg.exec_() def loadYaml(self): - yaml_file = str(QtGui.QFileDialog().getOpenFileName(self.mainWidget, 'Open Instrument YAML File', self.folder, 'Files (*.yaml)')) + yaml_file = str(QFileDialog().getOpenFileName(self.mainWidget, 'Open Instrument YAML File', self.folder, 'Files (*.yaml)')) new_folder = os.path.dirname(yaml_file) if new_folder != self.folder: self.folder = new_folder @@ -483,11 +493,11 @@ class PyChopGui(QtGui.QMainWindow): self.setInstrument(newname) def _ask_overwrite(self): - msg = QtGui.QDialog() + msg = QDialog() msg.setWindowTitle('Load overwrite') - layout = QtGui.QGridLayout() - layout.addWidget(QtGui.QLabel('Instrument %s already exists in memory. Overwrite this?'), 0, 0, 1, -1) - buttons = [QtGui.QPushButton(label) for label in ['Load and overwrite', 'Cancel Load', 'Load and rename to']] + layout = QGridLayout() + layout.addWidget(QLabel('Instrument %s already exists in memory. Overwrite this?'), 0, 0, 1, -1) + buttons = [QPushButton(label) for label in ['Load and overwrite', 'Cancel Load', 'Load and rename to']] locations = [[1, 0], [1, 1], [2, 0]] self.overwrite_flag = 1 @@ -497,7 +507,7 @@ class PyChopGui(QtGui.QMainWindow): for idx, button in enumerate(buttons): button.clicked.connect(lambda _, idx=idx: overwriteCB(idx)) layout.addWidget(button, locations[idx][0], locations[idx][1]) - newname = QtGui.QLineEdit() + newname = QLineEdit() newname.editingFinished.connect(lambda: overwriteCB(2)) layout.addWidget(newname, 2, 1) msg.setLayout(layout) @@ -591,29 +601,29 @@ class PyChopGui(QtGui.QMainWindow): generatedText = self.genText() except ValueError: return - self.txtwin = QtGui.QDialog() - self.txtedt = QtGui.QTextEdit() - self.txtbtn = QtGui.QPushButton('OK') - self.txtwin.layout = QtGui.QVBoxLayout(self.txtwin) + self.txtwin = QDialog() + self.txtedt = QTextEdit() + self.txtbtn = QPushButton('OK') + self.txtwin.layout = QVBoxLayout(self.txtwin) self.txtwin.layout.addWidget(self.txtedt) self.txtwin.layout.addWidget(self.txtbtn) self.txtbtn.clicked.connect(self.txtwin.deleteLater) self.txtedt.setText(generatedText) self.txtedt.setReadOnly(True) self.txtwin.setWindowTitle('Resolution information') - self.txtwin.setWindowModality(QtCore.Qt.ApplicationModal) - self.txtwin.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.txtwin.setWindowModality(Qt.ApplicationModal) + self.txtwin.setAttribute(Qt.WA_DeleteOnClose) self.txtwin.setMinimumSize(400, 600) self.txtwin.resize(400, 600) self.txtwin.show() - self.txtloop = QtCore.QEventLoop() + self.txtloop = QEventLoop() self.txtloop.exec_() def saveText(self): """ Saves the generated text to a file (opens file dialog). """ - fname = QtGui.QFileDialog.getSaveFileName(self, 'Open file', '') + fname = QFileDialog.getSaveFileName(self, 'Open file', '') fid = open(fname, 'w') fid.write(self.genText()) fid.close() @@ -657,20 +667,20 @@ class PyChopGui(QtGui.QMainWindow): helpTxt += "all graphs will be updated. If the 'Hold current plot'\ncheck box is ticked, additional settings will be\n" helpTxt += "overplotted on the existing graphs if they are\ndifferent from previous settings.\n\nMore in-depth help " helpTxt += "can be obtained from the\nMantid help pages." - self.hlpwin = QtGui.QDialog() - self.hlpedt = QtGui.QLabel(helpTxt) - self.hlpbtn = QtGui.QPushButton('OK') - self.hlpwin.layout = QtGui.QVBoxLayout(self.hlpwin) + self.hlpwin = QDialog() + self.hlpedt = QLabel(helpTxt) + self.hlpbtn = QPushButton('OK') + self.hlpwin.layout = QVBoxLayout(self.hlpwin) self.hlpwin.layout.addWidget(self.hlpedt) self.hlpwin.layout.addWidget(self.hlpbtn) self.hlpbtn.clicked.connect(self.hlpwin.deleteLater) self.hlpwin.setWindowTitle('Help') - self.hlpwin.setWindowModality(QtCore.Qt.ApplicationModal) - self.hlpwin.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.hlpwin.setWindowModality(Qt.ApplicationModal) + self.hlpwin.setAttribute(Qt.WA_DeleteOnClose) self.hlpwin.setMinimumSize(370, 300) self.hlpwin.resize(370, 300) self.hlpwin.show() - self.hlploop = QtCore.QEventLoop() + self.hlploop = QEventLoop() self.hlploop.exec_() def drawLayout(self): @@ -697,21 +707,21 @@ class PyChopGui(QtGui.QMainWindow): self.singles = [] self.widgets = {} - self.leftPanel = QtGui.QVBoxLayout() - self.rightPanel = QtGui.QVBoxLayout() - self.tabs = QtGui.QTabWidget(self) - self.fullWindow = QtGui.QGridLayout() + self.leftPanel = QVBoxLayout() + self.rightPanel = QVBoxLayout() + self.tabs = QTabWidget(self) + self.fullWindow = QGridLayout() for widget in self.widgetslist: if 'pair' in widget[0]: - self.droplabels.append(QtGui.QLabel(widget[2])) + self.droplabels.append(QLabel(widget[2])) if 'combo' in widget[3]: - self.dropboxes.append(QtGui.QComboBox(self)) + self.dropboxes.append(QComboBox(self)) self.dropboxes[-1].activated['QString'].connect(widget[5]) for item in widget[4]: self.dropboxes[-1].addItem(item) self.widgets[widget[-1]] = {'Combo':self.dropboxes[-1], 'Label':self.droplabels[-1]} elif 'edit' in widget[3]: - self.dropboxes.append(QtGui.QLineEdit(self)) + self.dropboxes.append(QLineEdit(self)) self.dropboxes[-1].returnPressed.connect(widget[5]) self.widgets[widget[-1]] = {'Edit':self.dropboxes[-1], 'Label':self.droplabels[-1]} else: @@ -723,10 +733,10 @@ class PyChopGui(QtGui.QMainWindow): self.dropboxes[-1].hide() elif 'single' in widget[0]: if 'check' in widget[3]: - self.singles.append(QtGui.QCheckBox(widget[2], self)) + self.singles.append(QCheckBox(widget[2], self)) self.singles[-1].stateChanged.connect(widget[4]) elif 'button' in widget[3]: - self.singles.append(QtGui.QPushButton(widget[2])) + self.singles.append(QPushButton(widget[2])) self.singles[-1].clicked.connect(widget[4]) else: raise RuntimeError('Bug in code - widget %s is not recognised.' % (widget[3])) @@ -735,7 +745,7 @@ class PyChopGui(QtGui.QMainWindow): self.singles[-1].hide() self.widgets[widget[-1]] = self.singles[-1] elif 'spacer' in widget[0]: - self.leftPanel.addItem(QtGui.QSpacerItem(0, 35)) + self.leftPanel.addItem(QSpacerItem(0, 35)) else: raise RuntimeError('Bug in code - widget class %s is not recognised.' % (widget[0])) @@ -748,8 +758,8 @@ class PyChopGui(QtGui.QMainWindow): self.resaxes.set_xlabel('Energy Transfer (meV)') self.resaxes.set_ylabel(r'$\Delta$E (meV FWHM)') self.resfig_controls = NavigationToolbar(self.rescanvas, self) - self.restab = QtGui.QWidget(self.tabs) - self.restabbox = QtGui.QVBoxLayout() + self.restab = QWidget(self.tabs) + self.restabbox = QVBoxLayout() self.restabbox.addWidget(self.rescanvas) self.restabbox.addWidget(self.resfig_controls) self.restab.setLayout(self.restabbox) @@ -771,19 +781,19 @@ class PyChopGui(QtGui.QMainWindow): self.flxslder = Slider(self.flxsldax, 'Ei (meV)', 0, 100, valinit=100) self.flxslder.valtext.set_visible(False) self.flxslder.on_changed(self.update_slider) - self.flxedt = QtGui.QLineEdit() + self.flxedt = QLineEdit() self.flxedt.setText('1000') self.flxedt.returnPressed.connect(self.update_slider) - self.flxtab = QtGui.QWidget(self.tabs) - self.flxsldbox = QtGui.QHBoxLayout() + self.flxtab = QWidget(self.tabs) + self.flxsldbox = QHBoxLayout() self.flxsldbox.addWidget(self.flxsldcv) self.flxsldbox.addWidget(self.flxedt) - self.flxsldwdg = QtGui.QWidget() + self.flxsldwdg = QWidget() self.flxsldwdg.setLayout(self.flxsldbox) sz = self.flxsldwdg.maximumSize() sz.setHeight(50) self.flxsldwdg.setMaximumSize(sz) - self.flxtabbox = QtGui.QVBoxLayout() + self.flxtabbox = QVBoxLayout() self.flxtabbox.addWidget(self.flxcanvas) self.flxtabbox.addWidget(self.flxsldwdg) self.flxtabbox.addWidget(self.flxfig_controls) @@ -799,8 +809,8 @@ class PyChopGui(QtGui.QMainWindow): self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes2.set_ylabel('Elastic Resolution FWHM (meV)') self.frqfig_controls = NavigationToolbar(self.frqcanvas, self) - self.frqtab = QtGui.QWidget(self.tabs) - self.frqtabbox = QtGui.QVBoxLayout() + self.frqtab = QWidget(self.tabs) + self.frqtabbox = QVBoxLayout() self.frqtabbox.addWidget(self.frqcanvas) self.frqtabbox.addWidget(self.frqfig_controls) self.frqtab.setLayout(self.frqtabbox) @@ -813,19 +823,19 @@ class PyChopGui(QtGui.QMainWindow): self.repaxes.set_xlabel(r'TOF ($\mu$sec)') self.repaxes.set_ylabel('Distance (m)') self.repfig_controls = NavigationToolbar(self.repcanvas, self) - self.repfig_nframe_label = QtGui.QLabel('Number of frames to plot') - self.repfig_nframe_edit = QtGui.QLineEdit('1') - self.repfig_nframe_button = QtGui.QPushButton('Replot') + self.repfig_nframe_label = QLabel('Number of frames to plot') + self.repfig_nframe_edit = QLineEdit('1') + self.repfig_nframe_button = QPushButton('Replot') self.repfig_nframe_button.clicked.connect(lambda: self.plot_frame()) - self.repfig_nframe_box = QtGui.QHBoxLayout() + self.repfig_nframe_box = QHBoxLayout() self.repfig_nframe_box.addWidget(self.repfig_nframe_label) self.repfig_nframe_box.addWidget(self.repfig_nframe_edit) self.repfig_nframe_box.addWidget(self.repfig_nframe_button) - self.reptab = QtGui.QWidget(self.tabs) - self.repfig_nframe = QtGui.QWidget(self.reptab) + self.reptab = QWidget(self.tabs) + self.repfig_nframe = QWidget(self.reptab) self.repfig_nframe.setLayout(self.repfig_nframe_box) - self.repfig_nframe.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)) - self.reptabbox = QtGui.QVBoxLayout() + self.repfig_nframe.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)) + self.reptabbox = QVBoxLayout() self.reptabbox.addWidget(self.repcanvas) self.reptabbox.addWidget(self.repfig_nframe) self.reptabbox.addWidget(self.repfig_controls) @@ -839,17 +849,17 @@ class PyChopGui(QtGui.QMainWindow): self.qeaxes.set_xlabel(r'$|Q| (\mathrm{\AA}^{-1})$') self.qeaxes.set_ylabel('Energy Transfer (meV)') self.qefig_controls = NavigationToolbar(self.qecanvas, self) - self.qetabbox = QtGui.QVBoxLayout() + self.qetabbox = QVBoxLayout() self.qetabbox.addWidget(self.qecanvas) self.qetabbox.addWidget(self.qefig_controls) - self.qetab = QtGui.QWidget(self.tabs) + self.qetab = QWidget(self.tabs) self.qetab.setLayout(self.qetabbox) - self.scrtab = QtGui.QWidget(self.tabs) - self.scredt = QtGui.QTextEdit() - self.scrcls = QtGui.QPushButton("Clear") + self.scrtab = QWidget(self.tabs) + self.scredt = QTextEdit() + self.scrcls = QPushButton("Clear") self.scrcls.clicked.connect(lambda: self.scredt.clear()) - self.scrbox = QtGui.QVBoxLayout() + self.scrbox = QVBoxLayout() self.scrbox.addWidget(self.scredt) self.scrbox.addWidget(self.scrcls) self.scrtab.setLayout(self.scrbox) @@ -867,65 +877,33 @@ class PyChopGui(QtGui.QMainWindow): self.scrtabID = 5 self.rightPanel.addWidget(self.tabs) - self.menuLoad = QtGui.QMenu('Load') - self.loadAct = QtGui.QAction('Load YAML', self.menuLoad) + self.menuLoad = QMenu('Load') + self.loadAct = QAction('Load YAML', self.menuLoad) self.loadAct.triggered.connect(self.loadYaml) self.menuLoad.addAction(self.loadAct) - self.menuOptions = QtGui.QMenu('Options') - self.instSciAct = QtGui.QAction('Instrument Scientist Mode', self.menuOptions, checkable=True) + self.menuOptions = QMenu('Options') + self.instSciAct = QAction('Instrument Scientist Mode', self.menuOptions, checkable=True) self.instSciAct.triggered.connect(self.instSciCB) self.menuOptions.addAction(self.instSciAct) - self.eiPlots = QtGui.QAction('Press Enter in Ei box updates plots', self.menuOptions, checkable=True) + self.eiPlots = QAction('Press Enter in Ei box updates plots', self.menuOptions, checkable=True) self.menuOptions.addAction(self.eiPlots) - self.overwriteload = QtGui.QAction('Always overwrite instruments in memory', self.menuOptions, checkable=True) + self.overwriteload = QAction('Always overwrite instruments in memory', self.menuOptions, checkable=True) self.menuOptions.addAction(self.overwriteload) self.menuBar().addMenu(self.menuLoad) self.menuBar().addMenu(self.menuOptions) - self.leftPanelWidget = QtGui.QWidget() + self.leftPanelWidget = QWidget() self.leftPanelWidget.setLayout(self.leftPanel) - self.leftPanelWidget.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Preferred)) + self.leftPanelWidget.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)) self.fullWindow.addWidget(self.leftPanelWidget, 0, 0) self.fullWindow.addLayout(self.rightPanel, 0, 1) - self.helpbtn = QtGui.QPushButton("?", self) + self.helpbtn = QPushButton("?", self) self.helpbtn.setMaximumWidth(30) self.helpbtn.clicked.connect(self.onHelp) self.fullWindow.addWidget(self.helpbtn, 1, 0, 1, -1) - self.mainWidget = QtGui.QWidget() + self.mainWidget = QWidget() self.mainWidget.setLayout(self.fullWindow) self.setCentralWidget(self.mainWidget) self.setWindowTitle('PyChopGUI') self.show() - - -def show(): - """ - Create a Qt window in Python, or interactively in IPython with Qt GUI - event loop integration. - """ - app_created = False - app = QtCore.QCoreApplication.instance() - if app is None: - app = QtGui.QApplication(sys.argv) - app_created = True - app.references = set() - window = PyChopGui() - app.references.add(window) - window.show() - if app_created: - app.exec_() - return window - - -if __name__ == '__main__': - if QtGui.QApplication.instance(): - app = QtGui.QApplication.instance() - else: - app = QtGui.QApplication(sys.argv) - window = PyChopGui() - window.show() - try: # check if started from within mantidplot - import mantidplot # noqa - except ImportError: - sys.exit(app.exec_()) diff --git a/scripts/QECoverage.py b/scripts/QECoverage.py index df36a7c10ecf87f4859d4bb86aac49e96b594d59..5162e7650af438af8bad30bbd17002b00c9919bb 100644 --- a/scripts/QECoverage.py +++ b/scripts/QECoverage.py @@ -7,13 +7,12 @@ # pylint: disable=line-too-long, too-many-instance-attributes, invalid-name, missing-docstring, too-many-statements # pylint: disable= too-many-branches, no-self-use from __future__ import (absolute_import, division, print_function) -import sys - +from gui_helper import get_qapplication, show_interface_help import numpy as np import mantid -from PyQt4 import QtGui -from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar +from qtpy import QtWidgets +from MPLwidgets import * +import matplotlib from matplotlib.figure import Figure from scipy import constants @@ -47,7 +46,7 @@ def calcQE(efix, tthlims, **kwargs): return qe_array -class QECoverageGUI(QtGui.QWidget): +class QECoverageGUI(QtWidgets.QWidget): """ QECoverage - Calculates the Q(E) limits multi-detector spectrometers """ @@ -56,68 +55,68 @@ class QECoverageGUI(QtGui.QWidget): # Rewritten as a Mantid interface by Duc Le (2016) def __init__(self, parent=None): - QtGui.QWidget.__init__(self, parent) + QtWidgets.QWidget.__init__(self, parent) self.setWindowTitle("QECoverage") - self.grid = QtGui.QVBoxLayout() + self.grid = QtWidgets.QVBoxLayout() self.setLayout(self.grid) - self.mainframe = QtGui.QFrame(self) - self.mainframe_grid = QtGui.QHBoxLayout() + self.mainframe = QtWidgets.QFrame(self) + self.mainframe_grid = QtWidgets.QHBoxLayout() self.mainframe.setLayout(self.mainframe_grid) # Left panel - inputs - self.tabs = QtGui.QTabWidget(self) + self.tabs = QtWidgets.QTabWidget(self) # Direct geometry spectrometer tab - self.tab_direct = QtGui.QWidget(self.tabs) - self.direct_grid = QtGui.QVBoxLayout() + self.tab_direct = QtWidgets.QWidget(self.tabs) + self.direct_grid = QtWidgets.QVBoxLayout() self.tab_direct.setLayout(self.direct_grid) self.direct_inst_list = ['LET', 'MAPS', 'MARI', 'MERLIN', 'ARCS', 'CNCS', 'HYSPEC', 'SEQUOIA', 'IN4', 'IN5', 'IN6', 'FOCUS', 'MIBEMOL', 'DNS', 'TOFTOF'] - self.direct_inst_box = QtGui.QComboBox(self.tab_direct) + self.direct_inst_box = QtWidgets.QComboBox(self.tab_direct) for inst in self.direct_inst_list: self.direct_inst_box.addItem(inst) self.direct_grid.addWidget(self.direct_inst_box) self.direct_inst_box.activated[str].connect(self.onDirectInstActivated) - self.direct_ei = QtGui.QFrame(self.tab_direct) - self.direct_ei_grid = QtGui.QHBoxLayout() + self.direct_ei = QtWidgets.QFrame(self.tab_direct) + self.direct_ei_grid = QtWidgets.QHBoxLayout() self.direct_ei.setLayout(self.direct_ei_grid) - self.direct_ei_label = QtGui.QLabel("Ei", self.direct_ei) + self.direct_ei_label = QtWidgets.QLabel("Ei", self.direct_ei) self.direct_ei_grid.addWidget(self.direct_ei_label) - self.direct_ei_input = QtGui.QLineEdit("55", self.direct_ei) + self.direct_ei_input = QtWidgets.QLineEdit("55", self.direct_ei) self.direct_ei_input.setToolTip("Incident Energy in meV") self.direct_ei_grid.addWidget(self.direct_ei_input) - self.emptyfield_msgbox = QtGui.QMessageBox() + self.emptyfield_msgbox = QtWidgets.QMessageBox() self.emptyfield_msgbox.setText("Invalid input has been provided for Ei or Emin! Please try again") - self.ei_msgbox = QtGui.QMessageBox() + self.ei_msgbox = QtWidgets.QMessageBox() self.ei_msgbox.setText("Ei cannot be negative! Please try again") - self.ei_emin_msgbox = QtGui.QMessageBox() + self.ei_emin_msgbox = QtWidgets.QMessageBox() self.ei_emin_msgbox.setText("Emin must be less than the values provided for Ei! Please try again") self.direct_grid.addWidget(self.direct_ei) - self.emaxfield_msgbox = QtGui.QMessageBox() - self.direct_plotover = QtGui.QCheckBox("Plot Over", self.tab_direct) + self.emaxfield_msgbox = QtWidgets.QMessageBox() + self.direct_plotover = QtWidgets.QCheckBox("Plot Over", self.tab_direct) self.direct_plotover.setToolTip("Hold this plot?") self.direct_grid.addWidget(self.direct_plotover) self.direct_plotover.stateChanged.connect(self.onDirectPlotOverChanged) - self.direct_createws = QtGui.QCheckBox("Create Workspace", self.tab_direct) + self.direct_createws = QtWidgets.QCheckBox("Create Workspace", self.tab_direct) self.direct_createws.setToolTip("Create a Mantid workspace?") self.direct_grid.addWidget(self.direct_createws) self.direct_createws.stateChanged.connect(self.onDirectCreateWSChanged) - self.direct_emin = QtGui.QFrame(self.tab_direct) - self.direct_emin_grid = QtGui.QHBoxLayout() + self.direct_emin = QtWidgets.QFrame(self.tab_direct) + self.direct_emin_grid = QtWidgets.QHBoxLayout() self.direct_emin.setLayout(self.direct_emin_grid) - self.direct_emin_label = QtGui.QLabel("Emin", self.direct_emin) + self.direct_emin_label = QtWidgets.QLabel("Emin", self.direct_emin) self.direct_emin_grid.addWidget(self.direct_emin_label) - self.direct_emin_input = QtGui.QLineEdit("-10", self.direct_emin) + self.direct_emin_input = QtWidgets.QLineEdit("-10", self.direct_emin) self.direct_emin_input.setToolTip("Minimum energy transfer to plot down to.") self.direct_emin_grid.addWidget(self.direct_emin_input) self.direct_grid.addWidget(self.direct_emin) - self.direct_plotbtn = QtGui.QPushButton("Plot Q-E", self.tab_direct) + self.direct_plotbtn = QtWidgets.QPushButton("Plot Q-E", self.tab_direct) self.direct_grid.addWidget(self.direct_plotbtn) self.direct_plotbtn.clicked.connect(self.onClickDirectPlot) - self.direct_s2 = QtGui.QFrame(self.tab_direct) - self.direct_s2_grid = QtGui.QHBoxLayout() + self.direct_s2 = QtWidgets.QFrame(self.tab_direct) + self.direct_s2_grid = QtWidgets.QHBoxLayout() self.direct_s2.setLayout(self.direct_s2_grid) - self.direct_s2_label = QtGui.QLabel("s2", self.direct_s2) + self.direct_s2_label = QtWidgets.QLabel("s2", self.direct_s2) self.direct_s2_grid.addWidget(self.direct_s2_label) - self.direct_s2_input = QtGui.QLineEdit("30", self.direct_s2) + self.direct_s2_input = QtWidgets.QLineEdit("30", self.direct_s2) self.direct_s2_input.setToolTip("Scattering angle of middle of the HYSPEC detector bank.") self.direct_s2_grid.addWidget(self.direct_s2_input) self.direct_s2_input.textChanged[str].connect(self.onS2Changed) @@ -127,21 +126,21 @@ class QECoverageGUI(QtGui.QWidget): self.tabs.addTab(self.tab_direct, "Direct") self.tthlims = [2.65, 140] # Indirect geometry spectrometer tab - self.tab_indirect = QtGui.QWidget(self.tabs) - self.indirect_grid = QtGui.QVBoxLayout() + self.tab_indirect = QtWidgets.QWidget(self.tabs) + self.indirect_grid = QtWidgets.QVBoxLayout() self.tab_indirect.setLayout(self.indirect_grid) self.indirect_inst_list = ['IRIS', 'OSIRIS', 'TOSCA', 'VESUVIO', 'BASIS', 'VISION'] - self.indirect_inst_box = QtGui.QComboBox(self.tab_indirect) + self.indirect_inst_box = QtWidgets.QComboBox(self.tab_indirect) for inst in self.indirect_inst_list: self.indirect_inst_box.addItem(inst) self.indirect_grid.addWidget(self.indirect_inst_box) self.indirect_inst_box.activated[str].connect(self.onIndirectInstActivated) - self.indirect_ef = QtGui.QFrame(self.tab_indirect) - self.indirect_ef_grid = QtGui.QHBoxLayout() + self.indirect_ef = QtWidgets.QFrame(self.tab_indirect) + self.indirect_ef_grid = QtWidgets.QHBoxLayout() self.indirect_ef.setLayout(self.indirect_ef_grid) - self.indirect_ef_label = QtGui.QLabel("Ef", self.indirect_ef) + self.indirect_ef_label = QtWidgets.QLabel("Ef", self.indirect_ef) self.indirect_ef_grid.addWidget(self.indirect_ef_label) - self.indirect_ef_input = QtGui.QComboBox(self.indirect_ef) + self.indirect_ef_input = QtWidgets.QComboBox(self.indirect_ef) self.indirect_analysers = \ {'IRIS': {'PG002': 1.84, 'PG004': 7.38, 'Mica002': 0.207, 'Mica004': 0.826, 'Mica006': 1.86}, 'OSIRIS': {'PG002': 1.84, 'PG004': 7.38}, @@ -156,25 +155,25 @@ class QECoverageGUI(QtGui.QWidget): self.indirect_ef_grid.addWidget(self.indirect_ef_input) self.indirect_ef_input.activated[str].connect(self.onIndirectEfActivated) self.indirect_grid.addWidget(self.indirect_ef) - self.indirect_plotover = QtGui.QCheckBox("Plot Over", self.tab_indirect) + self.indirect_plotover = QtWidgets.QCheckBox("Plot Over", self.tab_indirect) self.indirect_plotover.setToolTip("Hold this plot?") self.indirect_grid.addWidget(self.indirect_plotover) self.indirect_plotover.stateChanged.connect(self.onIndirectPlotOverChanged) - self.indirect_createws = QtGui.QCheckBox("Create Workspace", self.tab_indirect) + self.indirect_createws = QtWidgets.QCheckBox("Create Workspace", self.tab_indirect) self.indirect_createws.setToolTip("Create a Mantid workspace?") self.indirect_grid.addWidget(self.indirect_createws) self.indirect_createws.stateChanged.connect(self.onIndirectCreateWSChanged) - self.indirect_emax = QtGui.QFrame(self.tab_direct) - self.indirect_emax = QtGui.QFrame(self.tab_indirect) - self.indirect_emax_grid = QtGui.QHBoxLayout() + self.indirect_emax = QtWidgets.QFrame(self.tab_direct) + self.indirect_emax = QtWidgets.QFrame(self.tab_indirect) + self.indirect_emax_grid = QtWidgets.QHBoxLayout() self.indirect_emax.setLayout(self.indirect_emax_grid) - self.indirect_emax_label = QtGui.QLabel("Emax", self.indirect_emax) + self.indirect_emax_label = QtWidgets.QLabel("Emax", self.indirect_emax) self.indirect_emax_grid.addWidget(self.indirect_emax_label) - self.indirect_emax_input = QtGui.QLineEdit("10", self.indirect_emax) + self.indirect_emax_input = QtWidgets.QLineEdit("10", self.indirect_emax) self.indirect_emax_input.setToolTip("Max energy loss to plot up to.") self.indirect_emax_grid.addWidget(self.indirect_emax_input) self.indirect_grid.addWidget(self.indirect_emax) - self.indirect_plotbtn = QtGui.QPushButton("Plot Q-E", self.tab_indirect) + self.indirect_plotbtn = QtWidgets.QPushButton("Plot Q-E", self.tab_indirect) self.indirect_grid.addWidget(self.indirect_plotbtn) self.indirect_plotbtn.clicked.connect(self.onClickIndirectPlot) self.indirect_grid.addStretch(10) @@ -182,8 +181,8 @@ class QECoverageGUI(QtGui.QWidget): self.mainframe_grid.addWidget(self.tabs) self.tabs.currentChanged.connect(self.onTabChange) # Right panel, matplotlib figure to show Q-E - self.figure_frame = QtGui.QFrame(self.mainframe) - self.figure_grid = QtGui.QVBoxLayout() + self.figure_frame = QtWidgets.QFrame(self.mainframe) + self.figure_grid = QtWidgets.QVBoxLayout() self.figure_frame.setLayout(self.figure_grid) self.figure = Figure() self.figure.patch.set_facecolor('white') @@ -194,23 +193,38 @@ class QECoverageGUI(QtGui.QWidget): self.axes.set_ylabel('Energy Transfer (meV)') self.mainframe_grid.addWidget(self.canvas) self.figure_grid.addWidget(self.canvas) - self.figure_controls = NavigationToolbar(self.canvas, self.figure_frame) + self.figure_controls = NavigationToolbar2QT(self.canvas, self.figure_frame) self.figure_grid.addWidget(self.figure_controls) self.mainframe_grid.addWidget(self.figure_frame) self.grid.addWidget(self.mainframe) - self.helpbtn = QtGui.QPushButton("?", self) + self.helpbtn = QtWidgets.QPushButton("?", self) self.helpbtn.setMaximumWidth(30) self.helpbtn.clicked.connect(self.onHelp) self.grid.addWidget(self.helpbtn) # Matplotlib does seem to rescale x-axis properly after axes.clear() self.xlim = 0 - + #help + self.assistant_process = QtCore.QProcess(self) + # pylint: disable=protected-access + self.mantidplot_name='QE Coverage' + self.collection_file = os.path.join(mantid._bindir, '../docs/qthelp/MantidProject.qhc') + version = ".".join(mantid.__version__.split(".")[:2]) + self.qt_url = 'qthelp://org.sphinx.mantidproject.' + version + '/doc/interfaces/QE Coverage.html' + self.external_url = 'http://docs.mantidproject.org/nightly/interfaces/QE Coverage.html' #register startup mantid.UsageService.registerFeatureUsage("Interface","QECoverage",False) def onHelp(self): - from pymantidplot.proxies import showCustomInterfaceHelp - showCustomInterfaceHelp("QE Coverage") + show_interface_help(self.mantidplot_name, + self.assistant_process, + self.collection_file, + self.qt_url, + self.external_url) + + def closeEvent(self, event): + self.assistant_process.close() + self.assistant_process.waitForFinished() + event.accept() def onDirectPlotOverChanged(self, state): self.indirect_plotover.setCheckState(state) @@ -342,7 +356,8 @@ class QECoverageGUI(QtGui.QWidget): self.xlim = 0 self.axes.clear() self.axes.axhline(color='k') - self.axes.hold(True) + if matplotlib.compare_versions('2.1.0',matplotlib.__version__): + self.axes.hold(True) # hold is deprecated since 2.1.0, true by default Inst = self.direct_inst_box.currentText() for n in range(len(qe)): name = Inst + '_Ei=' + str(ei_vec[n]) @@ -377,7 +392,8 @@ class QECoverageGUI(QtGui.QWidget): self.axes.clear() self.axes.axhline(color='k') else: - self.axes.hold(True) + if matplotlib.compare_versions('2.1.0',matplotlib.__version__): + self.axes.hold(True) # hold is deprecated since 2.1.0, true by default line, = self.axes.plot(qe[0][0], qe[0][1]) line.set_label(inst + '_' + ana) if max(qe[0][0]) > self.xlim: @@ -460,15 +476,8 @@ class QECoverageGUI(QtGui.QWidget): self.emaxfield_msgbox.show() -def qapp(): - if QtGui.QApplication.instance(): - _app = QtGui.QApplication.instance() - else: - _app = QtGui.QApplication(sys.argv) - return _app - - -app = qapp() +app, within_mantid = get_qapplication() mainForm = QECoverageGUI() mainForm.show() -app.exec_() +if not within_mantid: + app.exec_() diff --git a/scripts/SANS/sans/gui_logic/models/beam_centre_model.py b/scripts/SANS/sans/gui_logic/models/beam_centre_model.py index 096834c0cb222f09a37b298008a9b810827dd0c6..1cc6707eada529473f43add71846008c2844eb74 100644 --- a/scripts/SANS/sans/gui_logic/models/beam_centre_model.py +++ b/scripts/SANS/sans/gui_logic/models/beam_centre_model.py @@ -19,15 +19,15 @@ class BeamCentreModel(object): def __eq__(self, other): return self.__dict__ == other.__dict__ - def reset_to_defaults_for_instrument(self, state_data = None): + def reset_to_defaults_for_instrument(self, file_information = None): r_range = {} instrument = None - if state_data: - instrument_file_path = get_instrument_paths_for_sans_file(state_data.sample_scatter) + if file_information: + instrument_file_path = get_instrument_paths_for_sans_file(file_information=file_information) r_range = get_named_elements_from_ipf_file(instrument_file_path[1], ["beam_centre_radius_min", "beam_centre_radius_max"], float) - instrument = state_data.instrument + instrument = file_information.get_instrument() self._max_iterations = 10 self._r_min = r_range["beam_centre_radius_min"] if "beam_centre_radius_min" in r_range else 60 diff --git a/scripts/SANS/sans/gui_logic/models/create_state.py b/scripts/SANS/sans/gui_logic/models/create_state.py index 1401ad9d72694dc2114a4bf60a578bd3faec9476..6504e0fb6e1a2b45b4858af6b303521caadbdabf 100644 --- a/scripts/SANS/sans/gui_logic/models/create_state.py +++ b/scripts/SANS/sans/gui_logic/models/create_state.py @@ -31,6 +31,8 @@ def create_states(state_model, table_model, instrument, facility, row_index=None gui_state_director = GuiStateDirector(table_model, state_model, facility) for row in rows: + if file_lookup: + table_model.wait_for_file_information(row) state = _create_row_state(row, table_model, state_model, facility, instrument, file_lookup, gui_state_director) if isinstance(state, State): states.update({row: state}) @@ -43,6 +45,13 @@ def _create_row_state(row, table_model, state_model, facility, instrument, file_ try: sans_logger.information("Generating state for row {}".format(row)) state = None + + table_entry = table_model.get_table_entry(row) + if not table_entry.file_information and file_lookup: + error_message = "Trying to find the SANS file {0}, but cannot find it. Make sure that " \ + "the relevant paths are added and the correct instrument is selected." + raise RuntimeError(error_message.format(table_entry.sample_scatter)) + if not __is_empty_row(row, table_model): row_user_file = table_model.get_row_user_file(row) if row_user_file: diff --git a/scripts/SANS/sans/gui_logic/models/diagnostics_page_model.py b/scripts/SANS/sans/gui_logic/models/diagnostics_page_model.py index d8b65cd951c08e0c1e8c9a4ba063904e37b7342e..87f94b7cc311adb32ea3d2a649bc35e5c117fcb1 100644 --- a/scripts/SANS/sans/gui_logic/models/diagnostics_page_model.py +++ b/scripts/SANS/sans/gui_logic/models/diagnostics_page_model.py @@ -165,7 +165,7 @@ def get_detector_size_from_sans_file(state, detector): def create_state(state_model_with_view_update, file, period, facility): table_row = TableIndexModel(file, period, '', '', '', '', '', '', '', '', '', '') table = TableModel() - table.add_table_entry(0, table_row) + table.add_table_entry_no_thread_or_signal(0, table_row) gui_state_director = GuiStateDirector(table, state_model_with_view_update, facility) diff --git a/scripts/SANS/sans/gui_logic/models/run_summation.py b/scripts/SANS/sans/gui_logic/models/run_summation.py index 94a29e4697ba2fbddf722b14754af6918c75d5a1..15e7f6d1a05c2270b919a6ddadab99ea26a99e47 100644 --- a/scripts/SANS/sans/gui_logic/models/run_summation.py +++ b/scripts/SANS/sans/gui_logic/models/run_summation.py @@ -20,7 +20,7 @@ class RunSummation(object): pass def __call__(self, run_selection, settings, base_file_name): - self._work_handler.process(RunSummation.Listener(), self.run, run_selection, settings, base_file_name) + self._work_handler.process(RunSummation.Listener(), self.run, 0, run_selection, settings, base_file_name) def run(self, run_selection, settings, base_file_name): run_selection = self._run_selection_as_path_list(run_selection) diff --git a/scripts/SANS/sans/gui_logic/models/table_model.py b/scripts/SANS/sans/gui_logic/models/table_model.py index 15a2d6251a32632a6c67bbc00122fd135fdac887..71042f752d8beb174d5f179e35fcdb1edb53acd3 100644 --- a/scripts/SANS/sans/gui_logic/models/table_model.py +++ b/scripts/SANS/sans/gui_logic/models/table_model.py @@ -17,7 +17,11 @@ import re from sans.common.constants import ALL_PERIODS from sans.gui_logic.models.basic_hint_strategy import BasicHintStrategy -from sans.common.enums import RowState +from sans.common.enums import RowState, SampleShape +import functools +from sans.gui_logic.presenter.create_file_information import create_file_information +from ui.sans_isis.work_handler import WorkHandler +from sans.common.file_information import SANSFileInformationFactory class TableModel(object): @@ -25,13 +29,18 @@ class TableModel(object): "sample_transmission_period", "sample_direct", "sample_direct_period", "can_scatter", "can_scatter_period", "can_transmission", "can_transmission_period", "can_direct", "can_direct_period", - "output_name", "user_file", "sample_thickness", "options_column_model"] + "output_name", "user_file", "sample_thickness", "sample_height", "sample_width", + "sample_shape", "options_column_model"] + THICKNESS_ROW = 14 def __init__(self): super(TableModel, self).__init__() self._user_file = "" self._batch_file = "" self._table_entries = [] + self.work_handler = WorkHandler() + self._subscriber_list = [] + self._id_count = 0 @staticmethod def _validate_file_name(file_name): @@ -66,10 +75,20 @@ class TableModel(object): return self._table_entries[index] def add_table_entry(self, row, table_index_model): + table_index_model.id = self._id_count + self._id_count += 1 self._table_entries.insert(row, table_index_model) + if row >= self.get_number_of_rows(): + row = self.get_number_of_rows() - 1 + self.get_thickness_for_rows([row]) + self.notify_subscribers() def append_table_entry(self, table_index_model): + table_index_model.id = self._id_count + self._id_count += 1 self._table_entries.append(table_index_model) + self.get_thickness_for_rows([self.get_number_of_rows() - 1]) + self.notify_subscribers() def remove_table_entries(self, rows): # For speed rows should be a Set here but don't think it matters for the list sizes involved. @@ -77,6 +96,8 @@ class TableModel(object): if not self._table_entries: row_index_model = self.create_empty_row() self.append_table_entry(row_index_model) + else: + self.notify_subscribers() def replace_table_entries(self, row_to_replace_index, rows_to_insert): self.remove_table_entries(row_to_replace_index) @@ -95,6 +116,9 @@ class TableModel(object): self._table_entries[row].update_attribute(self.column_name_converter[column], value) self._table_entries[row].update_attribute('row_state', RowState.Unprocessed) self._table_entries[row].update_attribute('tool_tip', '') + if column == 0: + self.get_thickness_for_rows([row]) + self.notify_subscribers() def is_empty_row(self, row): return self._table_entries[row].is_empty() @@ -110,20 +134,107 @@ class TableModel(object): def set_row_to_processed(self, row, tool_tip): self._table_entries[row].update_attribute('row_state', RowState.Processed) self._table_entries[row].update_attribute('tool_tip', tool_tip) + self.notify_subscribers() def reset_row_state(self, row): self._table_entries[row].update_attribute('row_state', RowState.Unprocessed) self._table_entries[row].update_attribute('tool_tip', '') + self.notify_subscribers() def set_row_to_error(self, row, tool_tip): self._table_entries[row].update_attribute('row_state', RowState.Error) self._table_entries[row].update_attribute('tool_tip', tool_tip) + self.notify_subscribers() + + def get_thickness_for_rows(self, rows=None): + """ + Read in the sample thickness for the given rows from the file and set it in the table. + :param rows: list of table rows + """ + if not rows: + rows = range(len(self._table_entries)) + for row in rows: + entry = self._table_entries[row] + if entry.is_empty(): + continue + entry.file_finding = True + success_callback = functools.partial(self.update_thickness_from_file_information, entry.id) + + error_callback = functools.partial(self.failure_handler, entry.id) + create_file_information(entry.sample_scatter, error_callback, success_callback, + self.work_handler, entry.id) + + def failure_handler(self, id, error): + row = self.get_row_from_id(id) + self._table_entries[row].update_attribute('file_information', '') + self._table_entries[row].update_attribute('sample_thickness', '') + self._table_entries[row].update_attribute('sample_height', '') + self._table_entries[row].update_attribute('sample_width', '') + self._table_entries[row].update_attribute('sample_shape', '') + self._table_entries[row].file_finding = False + self.set_row_to_error(row, str(error[1])) + + def update_thickness_from_file_information(self, id, file_information): + row = self.get_row_from_id(id) + if file_information: + rounded_file_thickness = round(file_information.get_thickness(), 2) + rounded_file_height = round(file_information.get_height(), 2) + rounded_file_width = round(file_information.get_width(), 2) + + self._table_entries[row].update_attribute('file_information', file_information) + self._table_entries[row].update_attribute('sample_thickness', rounded_file_thickness) + self._table_entries[row].update_attribute('sample_height', rounded_file_height) + self._table_entries[row].update_attribute('sample_width', rounded_file_width) + self._table_entries[row].update_attribute('sample_shape', file_information.get_shape()) + self._table_entries[row].file_finding = False + self.reset_row_state(row) + + def subscribe_to_model_changes(self, subscriber): + self._subscriber_list.append(subscriber) + + def notify_subscribers(self): + for subscriber in self._subscriber_list: + subscriber.on_update_rows() + + def get_file_information_for_row(self, row): + return self._table_entries[row].file_information + + def get_row_from_id(self, id): + for row, entry in enumerate(self._table_entries): + if entry.id == id: + return row + return None + + def wait_for_file_finding_done(self): + self.work_handler.wait_for_done() + + def wait_for_file_information(self, row): + if self._table_entries[row].file_finding: + self.wait_for_file_finding_done() + + def add_table_entry_no_thread_or_signal(self, row, table_index_model): + table_index_model.id = self._id_count + self._id_count += 1 + self._table_entries.insert(row, table_index_model) + if row >= self.get_number_of_rows(): + row = self.get_number_of_rows() - 1 + + entry = self._table_entries[row] + file_information_factory = SANSFileInformationFactory() + file_information = file_information_factory.create_sans_file_information(entry.sample_scatter) + self.update_thickness_from_file_information(entry.id, file_information) def __eq__(self, other): - return self.__dict__ == other.__dict__ + return self.equal_dicts(self.__dict__, other.__dict__, ['work_handler']) def __ne__(self, other): - return self.__dict__ != other.__dict__ + return not self.equal_dicts(self.__dict__, other.__dict__, ['work_handler']) + + @staticmethod + def equal_dicts(d1, d2, ignore_keys): + d1_filtered = dict((k, v) for k, v in d1.items() if k not in ignore_keys) + d2_filtered = dict((k, v) for k, v in d2.items() if k not in ignore_keys) + return d1_filtered == d2_filtered class TableIndexModel(object): @@ -133,8 +244,10 @@ class TableIndexModel(object): can_scatter, can_scatter_period, can_transmission, can_transmission_period, can_direct, can_direct_period, - output_name="", user_file="", sample_thickness='0.0', options_column_string=""): + output_name="", user_file="", sample_thickness='', sample_height='', sample_width='', + sample_shape='', options_column_string=""): super(TableIndexModel, self).__init__() + self.id = None self.sample_scatter = sample_scatter self.sample_scatter_period = sample_scatter_period self.sample_transmission = sample_transmission @@ -151,12 +264,17 @@ class TableIndexModel(object): self.user_file = user_file self.sample_thickness = sample_thickness + self.sample_height = sample_height + self.sample_width = sample_width + self.sample_shape = sample_shape self.output_name = output_name self.options_column_model = options_column_string self.row_state = RowState.Unprocessed self.tool_tip = '' + self.file_information = None + self.file_finding = False # Options column entries @property @@ -183,8 +301,15 @@ class TableIndexModel(object): self._string_period(self.can_scatter_period), self.can_transmission, self._string_period(self.can_transmission_period), self.can_direct, self._string_period(self.can_direct_period), self.output_name, self.user_file, self.sample_thickness, + self.sample_height, self.sample_width, self._convert_sample_shape_to_string(self.sample_shape), self.options_column_model.get_options_string()] + def _convert_sample_shape_to_string(self, shape): + if shape: + return SampleShape.to_string(shape) + else: + return '' + def isMultiPeriod(self): return any ((self.sample_scatter_period, self.sample_transmission_period ,self.sample_direct_period, self.can_scatter_period, self.can_transmission_period, self.can_direct_period)) diff --git a/scripts/SANS/sans/gui_logic/presenter/beam_centre_presenter.py b/scripts/SANS/sans/gui_logic/presenter/beam_centre_presenter.py index 8f1c85bd7daf3f98b83a3522b666b9da02783793..9986d19268b6bca423d27b7bc543f7456d7c8f87 100644 --- a/scripts/SANS/sans/gui_logic/presenter/beam_centre_presenter.py +++ b/scripts/SANS/sans/gui_logic/presenter/beam_centre_presenter.py @@ -56,10 +56,10 @@ class BeamCentrePresenter(object): self._view.on_update_instrument(instrument) def on_update_rows(self): + file_information = self._parent_presenter._table_model.get_file_information_for_row(0) + if file_information: + self._beam_centre_model.reset_to_defaults_for_instrument(file_information=file_information) self._view.set_options(self._beam_centre_model) - state = self._parent_presenter.get_state_for_row(0) - if state: - self._beam_centre_model.reset_to_defaults_for_instrument(state_data=state.data) def on_processing_finished_centre_finder(self, result): # Enable button @@ -102,7 +102,7 @@ class BeamCentrePresenter(object): listener = BeamCentrePresenter.CentreFinderListener(self) state_copy = copy.copy(state) - self._work_handler.process(listener, self._beam_centre_model.find_beam_centre, state_copy) + self._work_handler.process(listener, self._beam_centre_model.find_beam_centre, 0, state_copy) def _update_beam_model_from_view(self): self._beam_centre_model.r_min = self._view.r_min diff --git a/scripts/SANS/sans/gui_logic/presenter/create_file_information.py b/scripts/SANS/sans/gui_logic/presenter/create_file_information.py new file mode 100644 index 0000000000000000000000000000000000000000..d21f8c9eae57dd07c3c03e6d3ee824e57af9e018 --- /dev/null +++ b/scripts/SANS/sans/gui_logic/presenter/create_file_information.py @@ -0,0 +1,27 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +from sans.gui_logic.presenter.work_handler_listener_wrapper import GenericWorkHandlerListener +from sans.common.file_information import SANSFileInformationFactory + + +def create_file_information(run_number, error_callback, success_callback, work_handler, id): + """ + The create_sans_file_information() is run in a new thread via the work_handler. + + :param run_number: Argument provided to create_sans_file_information. + :param error_callback: Callback for error in create_sans_file_information. + :param success_callback: Callback if create_sans_file_information succeeds. + :param work_handler: WorkHandler instance. + :param id: Identifier for processing. + """ + listener = GenericWorkHandlerListener(error_callback, success_callback) + file_information_factory = SANSFileInformationFactory() + work_handler.process(listener, + file_information_factory.create_sans_file_information, + id, + run_number) diff --git a/scripts/SANS/sans/gui_logic/presenter/diagnostic_presenter.py b/scripts/SANS/sans/gui_logic/presenter/diagnostic_presenter.py index 119b6e92866053f1f1d54fe4eb545d78fd2a86dc..87384faffeb90ab0c2dcb1daa9bfe9c8b76912cf 100644 --- a/scripts/SANS/sans/gui_logic/presenter/diagnostic_presenter.py +++ b/scripts/SANS/sans/gui_logic/presenter/diagnostic_presenter.py @@ -78,7 +78,7 @@ class DiagnosticsPagePresenter(object): range = self._view.horizontal_range listener = DiagnosticsPagePresenter.IntegralListener(self) detector = get_detector_from_gui_selection(self._view.detector) - self._work_handler.process(listener, self.run_integral, range, mask, IntegralEnum.Horizontal, + self._work_handler.process(listener, self.run_integral, 0, range, mask, IntegralEnum.Horizontal, detector, state) def on_vertical_clicked(self): @@ -90,7 +90,7 @@ class DiagnosticsPagePresenter(object): range = self._view.vertical_range listener = DiagnosticsPagePresenter.IntegralListener(self) detector = get_detector_from_gui_selection(self._view.detector) - self._work_handler.process(listener, self.run_integral, range, mask, IntegralEnum.Vertical, + self._work_handler.process(listener, self.run_integral, 0, range, mask, IntegralEnum.Vertical, detector, state) def on_time_clicked(self): @@ -102,7 +102,7 @@ class DiagnosticsPagePresenter(object): range = self._view.time_range listener = DiagnosticsPagePresenter.IntegralListener(self) detector = get_detector_from_gui_selection(self._view.detector) - self._work_handler.process(listener, self.run_integral, range, mask, IntegralEnum.Time, + self._work_handler.process(listener, self.run_integral, 0, range, mask, IntegralEnum.Time, detector, state) def on_processing_finished_integral(self, result): diff --git a/scripts/SANS/sans/gui_logic/presenter/gui_state_director.py b/scripts/SANS/sans/gui_logic/presenter/gui_state_director.py index 4c41bc8ce635fc30196dd8ee02784f8a45646d07..280b76a2b0a23e9f4f09292a716e149ab9f6d2fd 100644 --- a/scripts/SANS/sans/gui_logic/presenter/gui_state_director.py +++ b/scripts/SANS/sans/gui_logic/presenter/gui_state_director.py @@ -16,7 +16,6 @@ import copy from sans.state.data import get_data_builder from sans.user_file.state_director import StateDirectorISIS -from sans.common.file_information import SANSFileInformationFactory from sans.common.enums import (SANSInstrument) from sans.test_helper.file_information_mock import SANSFileInformationMock @@ -34,10 +33,8 @@ class GuiStateDirector(object): def create_state(self, row, file_lookup=True, instrument=SANSInstrument.SANS2D): # 1. Get the data settings, such as sample_scatter, etc... and create the data state. table_index_model = self._table_model.get_table_entry(row) - file_name = table_index_model.sample_scatter if file_lookup: - file_information_factory = SANSFileInformationFactory() - file_information = file_information_factory.create_sans_file_information(file_name) + file_information = table_index_model.file_information else: file_information = SANSFileInformationMock(instrument=instrument, facility=self._facility) @@ -70,6 +67,12 @@ class GuiStateDirector(object): if table_index_model.sample_thickness: state_gui_model.sample_thickness = float(table_index_model.sample_thickness) + if table_index_model.sample_height: + state_gui_model.sample_height = float(table_index_model.sample_height) + if table_index_model.sample_width: + state_gui_model.sample_width = float(table_index_model.sample_width) + if table_index_model.sample_shape: + state_gui_model.sample_shape = table_index_model.sample_shape # 4. Create the rest of the state based on the builder. user_file_state_director = StateDirectorISIS(data, file_information) diff --git a/scripts/SANS/sans/gui_logic/presenter/masking_table_presenter.py b/scripts/SANS/sans/gui_logic/presenter/masking_table_presenter.py index cfb9ffd19e4e7fcce47b142276dd69712472361f..90c7cd5ec54c21a7badc7733d9a8c741706cecc5 100644 --- a/scripts/SANS/sans/gui_logic/presenter/masking_table_presenter.py +++ b/scripts/SANS/sans/gui_logic/presenter/masking_table_presenter.py @@ -176,7 +176,7 @@ class MaskingTablePresenter(object): # Run the task listener = MaskingTablePresenter.DisplayMaskListener(self) state_copy = copy.copy(state) - self._work_handler.process(listener, load_and_mask_workspace, state_copy, self.DISPLAY_WORKSPACE_NAME) + self._work_handler.process(listener, load_and_mask_workspace, 0, state_copy, self.DISPLAY_WORKSPACE_NAME) def on_processing_finished_masking_display(self, result): # Enable button diff --git a/scripts/SANS/sans/gui_logic/presenter/run_tab_presenter.py b/scripts/SANS/sans/gui_logic/presenter/run_tab_presenter.py index e07cd2072e972d3f2ab90fe9181b07ef60d5c559..5573a594ac8ecd2e601df3bb29be5e0986f10ed5 100644 --- a/scripts/SANS/sans/gui_logic/presenter/run_tab_presenter.py +++ b/scripts/SANS/sans/gui_logic/presenter/run_tab_presenter.py @@ -36,7 +36,6 @@ from sans.gui_logic.models.diagnostics_page_model import run_integral, create_st from sans.sans_batch import SANSCentreFinder from sans.gui_logic.models.create_state import create_states from ui.sans_isis.work_handler import WorkHandler -from sans.common.file_information import SANSFileInformationFactory try: import mantidplot @@ -99,6 +98,9 @@ class RunTabPresenter(object): def on_cut_rows(self): self._presenter.on_cut_rows_requested() + def on_sample_geometry_selection(self, show_geometry): + self._presenter.on_sample_geometry_view_changed(show_geometry) + class ProcessListener(WorkHandler.WorkListener): def __init__(self, presenter): super(RunTabPresenter.ProcessListener, self).__init__() @@ -122,6 +124,7 @@ class RunTabPresenter(object): # Models that are being used by the presenter self._state_model = None self._table_model = TableModel() + self._table_model.subscribe_to_model_changes(self) # Presenter needs to have a handle on the view since it delegates it self._view = None @@ -139,9 +142,11 @@ class RunTabPresenter(object): # Masking table presenter self._masking_table_presenter = MaskingTablePresenter(self) + self._table_model.subscribe_to_model_changes(self._masking_table_presenter) # Beam centre presenter self._beam_centre_presenter = BeamCentrePresenter(self, WorkHandler, BeamCentreModel, SANSCentreFinder) + self._table_model.subscribe_to_model_changes(self._beam_centre_presenter) # Workspace Diagnostic page presenter self._workspace_diagnostic_presenter = DiagnosticsPagePresenter(self, WorkHandler, run_integral, create_state, self._facility) @@ -280,12 +285,6 @@ class RunTabPresenter(object): for index, row in enumerate(parsed_rows): self._add_row_to_table_model(row, index) self._table_model.remove_table_entries([len(parsed_rows)]) - - self.update_view_from_table_model() - - self._beam_centre_presenter.on_update_rows() - self._masking_table_presenter.on_update_rows() - except RuntimeError as e: self.sans_logger.error("Loading of the batch file failed. {}".format(str(e))) self.display_warning_box('Warning', 'Loading of the batch file failed', str(e)) @@ -318,20 +317,20 @@ class RunTabPresenter(object): can_direct = get_string_entry(BatchReductionEntry.CanDirect, row) can_direct_period = get_string_period(get_string_entry(BatchReductionEntry.CanDirectPeriod, row)) output_name = get_string_entry(BatchReductionEntry.Output, row) - file_information_factory = SANSFileInformationFactory() - file_information = file_information_factory.create_sans_file_information(sample_scatter) - sample_thickness = file_information._thickness user_file = get_string_entry(BatchReductionEntry.UserFile, row) row_entry = [sample_scatter, sample_scatter_period, sample_transmission, sample_transmission_period, sample_direct, sample_direct_period, can_scatter, can_scatter_period, can_transmission, can_transmission_period, can_direct, can_direct_period, - output_name, user_file, sample_thickness, ''] + output_name, user_file, '', ''] table_index_model = TableIndexModel(*row_entry) self._table_model.add_table_entry(index, table_index_model) + def on_update_rows(self): + self.update_view_from_table_model() + def update_view_from_table_model(self): self._view.clear_table() self._view.hide_period_columns() @@ -347,10 +346,6 @@ class RunTabPresenter(object): def on_data_changed(self, row, column, new_value, old_value): self._table_model.update_table_entry(row, column, new_value) - self._view.change_row_color(row_state_to_colour_mapping[RowState.Unprocessed], row) - self._view.set_row_tooltip('', row) - self._beam_centre_presenter.on_update_rows() - self._masking_table_presenter.on_update_rows() def on_instrument_changed(self): self._setup_instrument_specific_settings() @@ -374,7 +369,6 @@ class RunTabPresenter(object): selected_rows = selected_rows if selected_rows else range(self._table_model.get_number_of_rows()) for row in selected_rows: self._table_model.reset_row_state(row) - self.update_view_from_table_model() states, errors = self.get_states(row_index=selected_rows) for row, error in errors.items(): @@ -424,7 +418,6 @@ class RunTabPresenter(object): self.increment_progress() message = '' self._table_model.set_row_to_processed(row, message) - self.update_view_from_table_model() def on_processing_finished(self, result): self._view.enable_buttons() @@ -433,7 +426,6 @@ class RunTabPresenter(object): def on_processing_error(self, row, error_msg): self.increment_progress() self._table_model.set_row_to_error(row, error_msg) - self.update_view_from_table_model() def increment_progress(self): self.progress = self.progress + 1 @@ -448,18 +440,15 @@ class RunTabPresenter(object): selected_row = selected_rows[0] + 1 if selected_rows else self._table_model.get_number_of_rows() table_entry_row = self._table_model.create_empty_row() self._table_model.add_table_entry(selected_row, table_entry_row) - self.update_view_from_table_model() def on_erase_rows(self): selected_rows = self._view.get_selected_rows() - empty_row = TableModel.create_empty_row() for row in selected_rows: + empty_row = TableModel.create_empty_row() self._table_model.replace_table_entries([row], [empty_row]) - self.update_view_from_table_model() def on_rows_removed(self, rows): self._table_model.remove_table_entries(rows) - self.update_view_from_table_model() def on_copy_rows_requested(self): selected_rows = self._view.get_selected_rows() @@ -479,11 +468,16 @@ class RunTabPresenter(object): selected_rows = selected_rows if selected_rows else [self._table_model.get_number_of_rows()] replacement_table_index_models = [TableIndexModel(*x) for x in self._clipboard] self._table_model.replace_table_entries(selected_rows, replacement_table_index_models) - self.update_view_from_table_model() def on_manage_directories(self): self._view.show_directory_manager() + def on_sample_geometry_view_changed(self, show_geometry): + if show_geometry: + self._view.show_geometry() + else: + self._view.hide_geometry() + def get_row_indices(self): """ Gets the indices of row which are not empty. @@ -629,10 +623,6 @@ class RunTabPresenter(object): self._set_on_view("wavelength_step") self._set_on_view("absolute_scale") - self._set_on_view("sample_shape") - self._set_on_view("sample_height") - self._set_on_view("sample_width") - self._set_on_view("sample_thickness") self._set_on_view("z_offset") # Adjustment tab @@ -820,10 +810,6 @@ class RunTabPresenter(object): self._set_on_state_model("wavelength_range", state_model) self._set_on_state_model("absolute_scale", state_model) - self._set_on_state_model("sample_shape", state_model) - self._set_on_state_model("sample_height", state_model) - self._set_on_state_model("sample_width", state_model) - self._set_on_state_model("sample_thickness", state_model) self._set_on_state_model("z_offset", state_model) # Adjustment tab diff --git a/scripts/SANS/sans/gui_logic/presenter/work_handler_listener_wrapper.py b/scripts/SANS/sans/gui_logic/presenter/work_handler_listener_wrapper.py new file mode 100644 index 0000000000000000000000000000000000000000..fbc2fec0123ff1702764a8bfa04d80f72cca1216 --- /dev/null +++ b/scripts/SANS/sans/gui_logic/presenter/work_handler_listener_wrapper.py @@ -0,0 +1,30 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +from ui.sans_isis.work_handler import WorkHandler + + +class GenericWorkHandlerListener(WorkHandler.WorkListener): + """ + A concrete class to act as a "listener" for the WorkHandler. + """ + + def __init__(self, error_callback, success_callback): + """ + The callbacks are assigned to the abstract methods of the base class, and are called + after certain actions of a running Worker (for example errors being raised or the + Worker finishing its task). + """ + super(GenericWorkHandlerListener, self).__init__() + self.error_callback = error_callback + self.success_callback = success_callback + + def on_processing_finished(self, result): + self.success_callback(result) + + def on_processing_error(self, error): + self.error_callback(error) diff --git a/scripts/SANS/sans/test_helper/mock_objects.py b/scripts/SANS/sans/test_helper/mock_objects.py index 74adfc4ab2f95edf6e2ad940721818d4f965df48..4041a707a43e1c40128a535ef6ad783a9f7be0da 100644 --- a/scripts/SANS/sans/test_helper/mock_objects.py +++ b/scripts/SANS/sans/test_helper/mock_objects.py @@ -249,6 +249,7 @@ def get_state_for_row_mock_with_real_state(row_index, file_lookup=True): def create_run_tab_presenter_mock(use_fake_state=True): presenter = mock.create_autospec(RunTabPresenter, spec_set=False) presenter.get_row_indices = mock.MagicMock(return_value=[0, 1, 3]) + presenter._table_model = mock.MagicMock() presenter._facility = SANSFacility.ISIS if use_fake_state: presenter.get_state_for_row = mock.MagicMock(side_effect=get_state_for_row_mock) diff --git a/scripts/SCD_Reduction/BVGFitTools.py b/scripts/SCD_Reduction/BVGFitTools.py index 50f072c173e5c2e39f700ad7cc896c6ef5484554..77c07ef6d129aa3fdac95da8d5e048a4adebb098 100644 --- a/scripts/SCD_Reduction/BVGFitTools.py +++ b/scripts/SCD_Reduction/BVGFitTools.py @@ -21,7 +21,8 @@ def get3DPeak(peak, peaks_ws, box, padeCoefficients, qMask, nTheta=150, nPhi=150 plotResults=False, zBG=1.96, bgPolyOrder=1, fICCParams=None, oldICCFit=None, strongPeakParams=None, forceCutoff=250, edgeCutoff=15, neigh_length_m=3, q_frame='sample', dtSpread=0.03, pplmin_frac=0.8, pplmax_frac=1.5, mindtBinWidth=1, - maxdtBinWidth=50, figureNumber=2, peakMaskSize=5, iccFitDict=None): + maxdtBinWidth=50, figureNumber=2, peakMaskSize=5, iccFitDict=None, + sigX0Params=None, sigY0=None, sigP0Params=None, fitPenalty=None): n_events = box.getNumEventsArray() if q_frame == 'lab': @@ -37,12 +38,12 @@ def get3DPeak(peak, peaks_ws, box, padeCoefficients, qMask, nTheta=150, nPhi=150 n_events, peak=peak, box=box, qMask=qMask, calc_pp_lambda=True, padeCoefficients=padeCoefficients, neigh_length_m=neigh_length_m, pp_lambda=None, pplmin_frac=pplmin_frac, pplmax_frac=pplmax_frac, mindtBinWidth=mindtBinWidth, maxdtBinWidth=maxdtBinWidth, - peakMaskSize=peakMaskSize, iccFitDict=iccFitDict) + peakMaskSize=peakMaskSize, iccFitDict=iccFitDict, fitPenalty=fitPenalty) YTOF, fICC, x_lims = fitTOFCoordinate( box, peak, padeCoefficients, dtSpread=dtSpread, qMask=qMask, bgPolyOrder=bgPolyOrder, zBG=zBG, plotResults=plotResults, pp_lambda=pp_lambda, neigh_length_m=neigh_length_m, pplmin_frac=pplmin_frac, pplmax_frac=pplmax_frac, mindtBinWidth=mindtBinWidth, maxdtBinWidth=maxdtBinWidth, - peakMaskSize=peakMaskSize, iccFitDict=iccFitDict) + peakMaskSize=peakMaskSize, iccFitDict=iccFitDict, fitPenalty=fitPenalty) chiSqTOF = mtd['fit_Parameters'].column(1)[-1] else: # we already did I-C profile, so we'll just read the parameters pp_lambda = fICCParams[-1] @@ -57,7 +58,7 @@ def get3DPeak(peak, peaks_ws, box, padeCoefficients, qMask, nTheta=150, nPhi=150 fICC['KConv'] = fICCParams[11] goodIDX, _ = ICCFT.getBGRemovedIndices( n_events, pp_lambda=pp_lambda, qMask=qMask, peakMaskSize=peakMaskSize, - iccFitDict=iccFitDict) + iccFitDict=iccFitDict, fitPenalty=fitPenalty) chiSqTOF = fICCParams[4] #Last entry # Get the 3D TOF component, YTOF @@ -94,20 +95,7 @@ def get3DPeak(peak, peaks_ws, box, padeCoefficients, qMask, nTheta=150, nPhi=150 useForceParams = peak.getIntensity() < forceCutoff or peak.getRow() <= dEdge or peak.getRow( ) >= nPixels[0] - dEdge or peak.getCol() <= dEdge or peak.getCol() >= nPixels[1] - dEdge - #Here we retrieve some instrument specific parameters - try: - doPeakConvolution = peaks_ws.getInstrument().getBoolParameter("fitConvolvedPeak")[0] - except: - doPeakConvolution = False - try: - sigX0Scale = peaks_ws.getInstrument().getNumberParameter("sigX0Scale")[0] - except: - sigX0Scale = 1.0 - try: - sigY0Scale = peaks_ws.getInstrument().getNumberParameter("sigY0Scale")[0] - except: - sigY0Scale = 1.0 - + sigX0Params, sigY0, sigP0Params, doPeakConvolution = getBVGGuesses(peaks_ws, sigX0Params, sigY0, sigP0Params) if strongPeakParams is not None and useForceParams: # We will force parameters on this fit ph = np.arctan2(q0[1], q0[0]) th = np.arctan2(q0[2], np.hypot(q0[0], q0[1])) @@ -115,17 +103,16 @@ def get3DPeak(peak, peaks_ws, box, padeCoefficients, qMask, nTheta=150, nPhi=150 tmp = strongPeakParams[:, :2] - phthPeak distSq = tmp[:, 0]**2 + tmp[:, 1]**2 nnIDX = np.argmin(distSq) - #logger.information('Using [ph, th] = [{:2.2f},{:2.2f}] for [{:2.2f},{:2.2f}]'.format(strongPeakParams[nnIDX,0], - # strongPeakParams[nnIDX,1], - # phthPeak[0], - # phthPeak[1])) + params, h, t, p = doBVGFit(box, nTheta=nTheta, nPhi=nPhi, fracBoxToHistogram=fracBoxToHistogram, goodIDX=goodIDX, forceParams=strongPeakParams[nnIDX], - doPeakConvolution=doPeakConvolution, sigX0Scale=sigX0Scale, sigY0Scale=sigY0Scale) + doPeakConvolution=doPeakConvolution, sigX0Params=sigX0Params, + sigY0=sigY0, sigP0Params=sigP0Params, fitPenalty=fitPenalty) else: # Just do the fit - no nearest neighbor assumptions params, h, t, p = doBVGFit( box, nTheta=nTheta, nPhi=nPhi, fracBoxToHistogram=fracBoxToHistogram, goodIDX=goodIDX, - doPeakConvolution=doPeakConvolution, sigX0Scale=sigX0Scale, sigY0Scale=sigY0Scale) + doPeakConvolution=doPeakConvolution, sigX0Params=sigX0Params, sigY0=sigY0, sigP0Params=sigP0Params, + fitPenalty=fitPenalty) if plotResults: compareBVGFitData( @@ -186,6 +173,48 @@ def get3DPeak(peak, peaks_ws, box, padeCoefficients, qMask, nTheta=150, nPhi=150 return Y2, goodIDX, pp_lambda, retParams +def coshPeakWidthModel(x,A,x0,b,BG): + """ + coshPeakWidthModel: returns A*cosh((x-x0)/b) + BG + This phenomenologically describes the peak width along the scattering + direction. + """ + y = (x-x0)/b + return A/2.0*(np.exp(y)+np.exp(-y)) + BG + + +def getBVGGuesses(peaks_ws, sigX0Params, sigY0, sigP0Params): + """ + If we're not given initial guesses for the BVG, then we try to find instrument defaults. If those are not + available we use default values. If initial guesses are given, this function will return the initial guess, + allowing the function to be transparently added to workflows. + """ + + if sigX0Params is None: + if peaks_ws.getInstrument().hasParameter("sigSC0Params"): + sigX0Params = np.array(peaks_ws.getInstrument().getStringParameter("sigSC0Params")[0].split(),dtype=float) + else: + sigX0Params=[5.68860816e-06, 7.63702849e-01, 8.31642225e-02, 3.06656383e-03] + if sigY0 is None: + if peaks_ws.getInstrument().hasParameter("sigAZ0"): + sigY0 = peaks_ws.getInstrument().getNumberParameter("sigAZ0")[0] + else: + sigY0=0.0025 + + if sigP0Params is None: + if peaks_ws.getInstrument().hasParameter("sigP0Params"): + sigP0Params = np.array(peaks_ws.getInstrument().getStringParameter("sigP0Params")[0].split(),dtype=float) + else: + sigP0Params = [0.1460775, 1.85816592, 0.26850086, -0.00725352] + + if peaks_ws.getInstrument().hasParameter("fitConvolvedPeak"): + doPeakConvolution = peaks_ws.getInstrument().getBoolParameter("fitConvolvedPeak")[0] + else: + doPeakConvolution = False + + return sigX0Params, sigY0, sigP0Params, doPeakConvolution + + def boxToTOFThetaPhi(box, peak): QX, QY, QZ = ICCFT.getQXQYQZ(box) R, THETA, PHI = ICCFT.cart2sph(QX, QY, QZ) @@ -218,16 +247,14 @@ def fitScaling(n_events, box, YTOF, YBVG, goodIDX=None, neigh_length_m=3): max(fitMaxIDX[1] - dP, 0):min(fitMaxIDX[1] + dP, goodIDX.shape[1]), max(fitMaxIDX[2] - dP, 0):min(fitMaxIDX[2] + dP, goodIDX.shape[2])] = True goodIDX = np.logical_and(goodIDX, conv_n_events > 0) - # A1 = slope, A0 = offset + scaleLinear = Polynomial(n=1) scaleLinear.constrain("A1>0") scaleX = YJOINT[goodIDX] scaleY = n_events[goodIDX] - # , dataE=np.sqrt(scaleY)) - scaleWS = CreateWorkspace( - OutputWorkspace='scaleWS', dataX=scaleX, dataY=scaleY) - fitResultsScaling = Fit(Function=scaleLinear, InputWorkspace=scaleWS, - Output='scalefit', CostFunction='Unweighted least squares') + CreateWorkspace(OutputWorkspace='__scaleWS', dataX=scaleX, dataY=scaleY) + fitResultsScaling = Fit(Function=scaleLinear, InputWorkspace='__scaleWS', + Output='__scalefit', CostFunction='Unweighted least squares') A0 = fitResultsScaling[3].row(0)['Value'] A1 = fitResultsScaling[3].row(1)['Value'] YRET = A1 * YJOINT + A0 @@ -258,7 +285,7 @@ def getXTOF(box, peak): def fitTOFCoordinate(box, peak, padeCoefficients, dtSpread=0.03, minFracPixels=0.01, neigh_length_m=3, zBG=1.96, bgPolyOrder=1, qMask=None, plotResults=False, fracStop=0.01, pp_lambda=None, pplmin_frac=0.8, pplmax_frac=1.5, mindtBinWidth=1, - maxdtBinWidth=50, peakMaskSize=5, iccFitDict=None): + maxdtBinWidth=50, peakMaskSize=5, iccFitDict=None, fitPenalty=None): # Get info from the peak tof = peak.getTOF() # in us @@ -277,12 +304,11 @@ def fitTOFCoordinate(box, peak, padeCoefficients, dtSpread=0.03, minFracPixels=0 neigh_length_m=neigh_length_m, zBG=zBG, pp_lambda=pp_lambda, pplmin_frac=pplmin_frac, pplmax_frac=pplmax_frac, mindtBinWidth=mindtBinWidth, maxdtBinWidth=maxdtBinWidth, - peakMaskSize=peakMaskSize, - iccFitDict=iccFitDict) + peakMaskSize=peakMaskSize, iccFitDict=iccFitDict, fitPenalty=fitPenalty) fitResults, fICC = ICCFT.doICCFit(tofWS, energy, flightPath, padeCoefficients, fitOrder=bgPolyOrder, constraintScheme=1, - iccFitDict=iccFitDict) + iccFitDict=iccFitDict, fitPenalty=fitPenalty) for i, param in enumerate(['A', 'B', 'R', 'T0', 'Scale', 'HatWidth', 'KConv']): fICC[param] = mtd['fit_Parameters'].row(i)['Value'] @@ -436,7 +462,8 @@ def compareBVGFitData(box, params, nTheta=200, nPhi=200, figNumber=2, fracBoxToH def doBVGFit(box, nTheta=200, nPhi=200, zBG=1.96, fracBoxToHistogram=1.0, goodIDX=None, forceParams=None, forceTolerance=0.1, dth=10, dph=10, - doPeakConvolution=False, sigX0Scale=1., sigY0Scale=1.): + doPeakConvolution=False, sigX0Params=[5.68860816e-06, 7.63702849e-01, 8.31642225e-02, 3.06656383e-03], + sigY0=0.0025, sigP0Params=[0.1460775, 1.85816592, 0.26850086, -0.00725352], fitPenalty=None): """ doBVGFit takes a binned MDbox and returns the fit of the peak shape along the non-TOF direction. This is done in one of two ways: 1) Standard least squares fit of the 2D histogram. @@ -455,6 +482,10 @@ def doBVGFit(box, nTheta=200, nPhi=200, zBG=1.96, fracBoxToHistogram=1.0, goodID dth, dph: The peak center may move by (dth, dph) from predicted position (in units of histogram pixels). doPeakConvolution: boolean stating whether we should fit a convolved (smoothed) peak. This is useful for filling in gaps for 3He detector tube packs. + sigX0Params: a 4 element array with input arguments for coshPeakWidthModel [A,x0,b,BG]. Will ultimately be the + initial guess at sigma along the scattering direction. + sigY0: initial guess for sigma in the azimuthal direction. Units: rad + sigP0Params: a 4 element array with arguments for the covariance, fSigP [a, k, phi, b] """ h, thBins, phBins = getAngularHistogram( @@ -480,14 +511,8 @@ def doBVGFit(box, nTheta=200, nPhi=200, zBG=1.96, fracBoxToHistogram=1.0, goodID if forceParams is None: meanTH = TH.mean() meanPH = PH.mean() - # sigX0 = 0.0018 - # sigX0 = 0.002#ICCFT.oldScatFun(meanPH, 1.71151521e-02, 6.37218400e+00, 3.39439675e-03) - sigX0 = ICCFT.oldScatFun( - meanPH, 1.71151521e-02, 6.37218400e+00, 3.39439675e-03) - sigY0 = 0.0025 - sigP0 = fSigP(meanTH, 0.1460775, 1.85816592, - 0.26850086, -0.00725352) - + sigX0 = coshPeakWidthModel(meanPH, sigX0Params[0], sigX0Params[1], sigX0Params[2], sigX0Params[3]) + sigP0 = fSigP(meanTH, sigP0Params[0], sigP0Params[1], sigP0Params[2], sigP0Params[3]) # Set some constraints boundsDict = {} boundsDict['A'] = [0.0, np.inf] @@ -495,15 +520,12 @@ def doBVGFit(box, nTheta=200, nPhi=200, zBG=1.96, fracBoxToHistogram=1.0, goodID thBins[thBins.size // 2 + dth]] boundsDict['MuY'] = [phBins[phBins.size // 2 - dph], phBins[phBins.size // 2 + dph]] - boundsDict['SigX'] = [0.5*sigX0, 1.5*sigX0] - #boundsDict['SigX'] = [0., 0.02] + boundsDict['SigX'] = [0., 0.02] boundsDict['SigY'] = [0., 0.02] boundsDict['SigP'] = [-1., 1.] boundsDict['Bg'] = [0, np.inf] # Here we can make instrument-specific changes to our initial guesses and boundaries - sigX0 = sigX0*sigX0Scale - sigY0 = sigY0*sigY0Scale if doPeakConvolution: neigh_length_m = 5 @@ -516,8 +538,6 @@ def doBVGFit(box, nTheta=200, nPhi=200, zBG=1.96, fracBoxToHistogram=1.0, goodID m = BivariateGaussian.BivariateGaussian() m.init() m['A'] = 1. - #m['MuX'] = meanTH - #m['MuY'] = meanPH m['MuX'] = TH[np.unravel_index(h.argmax(), h.shape)] m['MuY'] = PH[np.unravel_index(h.argmax(), h.shape)] m['SigX'] = sigX0 @@ -525,15 +545,13 @@ def doBVGFit(box, nTheta=200, nPhi=200, zBG=1.96, fracBoxToHistogram=1.0, goodID m['SigP'] = sigP0 m.setAttributeValue('nX', h.shape[0]) m.setAttributeValue('nY', h.shape[1]) - m.setConstraints(boundsDict) + m.setConstraints(boundsDict, penalty=fitPenalty) # Do the fit - #bvgWS = CreateWorkspace(OutputWorkspace='bvgWS', DataX=pos.ravel( - #), DataY=H.ravel(), DataE=np.sqrt(H.ravel())) - bvgWS = CreateWorkspace(OutputWorkspace='bvgWS', DataX=pos.ravel( + CreateWorkspace(OutputWorkspace='__bvgWS', DataX=pos.ravel( ), DataY=H.ravel(), DataE=np.sqrt(H.ravel())) - - fitResults = Fit(Function=m, InputWorkspace='bvgWS', Output='bvgfit', + fitResults = Fit(Function=m, InputWorkspace='__bvgWS', Output='__bvgfit', Minimizer='Levenberg-MarquardtMD') + elif forceParams is not None: p0 = np.zeros(7) p0[0] = np.max(h) @@ -581,11 +599,6 @@ def doBVGFit(box, nTheta=200, nPhi=200, zBG=1.96, fracBoxToHistogram=1.0, goodID m = BivariateGaussian.BivariateGaussian() m.init() m['A'] = 0.1 - #m['muX'] = np.average(thCenters,weights=np.sum(h,axis=1)) - #m['muY'] = np.average(phCenters,weights=np.sum(h,axis=0)) - - #m['muX'] = TH.mean() - #m['muY'] = PH.mean() m['MuX'] = TH[np.unravel_index(h.argmax(), h.shape)] m['MuY'] = PH[np.unravel_index(h.argmax(), h.shape)] m['SigX'] = forceParams[5] @@ -593,24 +606,22 @@ def doBVGFit(box, nTheta=200, nPhi=200, zBG=1.96, fracBoxToHistogram=1.0, goodID m['SigP'] = forceParams[7] m.setAttributeValue('nX', h.shape[0]) m.setAttributeValue('nY', h.shape[1]) - m.setConstraints(boundsDict) + m.setConstraints(boundsDict, penalty=fitPenalty) # Do the fit - #plt.figure(18); plt.clf(); plt.imshow(m.function2D(pos)); plt.title('BVG Initial guess') - bvgWS = CreateWorkspace(OutputWorkspace='bvgWS', DataX=pos.ravel(), DataY=H.ravel(), DataE=np.sqrt(H.ravel())) + CreateWorkspace(OutputWorkspace='__bvgWS', DataX=pos.ravel(), DataY=H.ravel(), DataE=np.sqrt(H.ravel())) fitFun = m - fitResults = Fit(Function=fitFun, InputWorkspace=bvgWS, - Output='bvgfit', Minimizer='Levenberg-MarquardtMD') - + fitResults = Fit(Function=fitFun, InputWorkspace='__bvgWS', + Output='__bvgfit', Minimizer='Levenberg-MarquardtMD') # Recover the result m = BivariateGaussian.BivariateGaussian() m.init() - m['A'] = mtd['bvgfit_Parameters'].row(0)['Value'] - m['MuX'] = mtd['bvgfit_Parameters'].row(1)['Value'] - m['MuY'] = mtd['bvgfit_Parameters'].row(2)['Value'] - m['SigX'] = mtd['bvgfit_Parameters'].row(3)['Value'] - m['SigY'] = mtd['bvgfit_Parameters'].row(4)['Value'] - m['SigP'] = mtd['bvgfit_Parameters'].row(5)['Value'] - m['Bg'] = mtd['bvgfit_Parameters'].row(6)['Value'] + m['A'] = mtd['__bvgfit_Parameters'].row(0)['Value'] + m['MuX'] = mtd['__bvgfit_Parameters'].row(1)['Value'] + m['MuY'] = mtd['__bvgfit_Parameters'].row(2)['Value'] + m['SigX'] = mtd['__bvgfit_Parameters'].row(3)['Value'] + m['SigY'] = mtd['__bvgfit_Parameters'].row(4)['Value'] + m['SigP'] = mtd['__bvgfit_Parameters'].row(5)['Value'] + m['Bg'] = mtd['__bvgfit_Parameters'].row(6)['Value'] m.setAttributeValue('nX', h.shape[0]) m.setAttributeValue('nY', h.shape[1]) diff --git a/scripts/SCD_Reduction/ICCFitTools.py b/scripts/SCD_Reduction/ICCFitTools.py index fde92e2c50fd53dd894b889427debdad6ff39da9..819470ede8b56a102aed85ea8eaf518cb98c30f4 100644 --- a/scripts/SCD_Reduction/ICCFitTools.py +++ b/scripts/SCD_Reduction/ICCFitTools.py @@ -114,7 +114,7 @@ def getQXQYQZ(box): def getQuickTOFWS(box, peak, padeCoefficients, goodIDX=None, dtSpread=0.03, qMask=None, pp_lambda=None, minppl_frac=0.8, maxppl_frac=1.5, mindtBinWidth=1, maxdtBinWidth=50, - constraintScheme=1, peakMaskSize=5, iccFitDict=None): + constraintScheme=1, peakMaskSize=5, iccFitDict=None, fitPenalty=None): """ getQuickTOFWS - generates a quick-and-dirty TOFWS. Useful for determining the background. Input: @@ -153,10 +153,10 @@ def getQuickTOFWS(box, peak, padeCoefficients, goodIDX=None, dtSpread=0.03, qMas minFracPixels=0.01, neigh_length_m=3, zBG=1.96, pp_lambda=pp_lambda, calc_pp_lambda=calc_pp_lambda, pplmin_frac=minppl_frac, pplmax_frac=minppl_frac, mindtBinWidth=mindtBinWidth, maxdtBinWidth=maxdtBinWidth, - peakMaskSize=peakMaskSize, iccFitDict=iccFitDict) + peakMaskSize=peakMaskSize, iccFitDict=iccFitDict, fitPenalty=fitPenalty) fitResults, fICC = doICCFit( tofWS, energy, flightPath, padeCoefficients, fitOrder=1, constraintScheme=constraintScheme, - iccFitDict=iccFitDict) + iccFitDict=iccFitDict, fitPenalty=fitPenalty) h = [tofWS.readY(0), tofWS.readX(0)] chiSq = fitResults.OutputChi2overDoF @@ -220,7 +220,7 @@ def getPoissionGoodIDX(n_events, zBG=1.96, neigh_length_m=3): def getOptimizedGoodIDX(n_events, padeCoefficients, zBG=1.96, neigh_length_m=3, qMask=None, peak=None, box=None, pp_lambda=None, peakNumber=-1, minppl_frac=0.8, maxppl_frac=1.5, mindtBinWidth=1, maxdtBinWidth=50, - constraintScheme=1, peakMaskSize=5, iccFitDict=None): + constraintScheme=1, peakMaskSize=5, iccFitDict=None, fitPenalty=None): """ getOptimizedGoodIDX - returns a numpy arrays which is true if the voxel contains events at the zBG z level (1.96=95%CI). Rather than using Poission statistics, this function @@ -288,7 +288,6 @@ def getOptimizedGoodIDX(n_events, padeCoefficients, zBG=1.96, neigh_length_m=3, maxppl = maxppl_frac*pred_ppl pp_lambda_toCheck = pp_lambda_toCheck[pp_lambda_toCheck > minppl] pp_lambda_toCheck = pp_lambda_toCheck[pp_lambda_toCheck < maxppl] - if pp_lambda_toCheck == []: pp_lambda_toCheck = [meanBG*1.96] print('Cannot find suitable background. Consider adjusting MinpplFrac or MaxpplFrac') @@ -309,7 +308,7 @@ def getOptimizedGoodIDX(n_events, padeCoefficients, zBG=1.96, neigh_length_m=3, chiSq, h, intens, sigma = getQuickTOFWS(box, peak, padeCoefficients, goodIDX=goodIDX, qMask=qMask, pp_lambda=pp_lambda, minppl_frac=minppl_frac, maxppl_frac=maxppl_frac, mindtBinWidth=mindtBinWidth, maxdtBinWidth=maxdtBinWidth, constraintScheme=constraintScheme, - peakMaskSize=peakMaskSize, iccFitDict=iccFitDict) + peakMaskSize=peakMaskSize, iccFitDict=iccFitDict, fitPenalty=fitPenalty) except: #raise break @@ -333,8 +332,7 @@ def getOptimizedGoodIDX(n_events, padeCoefficients, zBG=1.96, neigh_length_m=3, chiSq, h, intens, sigma = getQuickTOFWS(box, peak, padeCoefficients, goodIDX=goodIDX, qMask=qMask, pp_lambda=pp_lambda, minppl_frac=minppl_frac, maxppl_frac=maxppl_frac, mindtBinWidth=mindtBinWidth, maxdtBinWidth=maxdtBinWidth, - peakMaskSize=peakMaskSize, - iccFitDict=iccFitDict) + peakMaskSize=peakMaskSize, iccFitDict=iccFitDict, fitPenalty=fitPenalty) if qMask is not None: return goodIDX*qMask, pp_lambda return goodIDX, pp_lambda @@ -343,7 +341,7 @@ def getOptimizedGoodIDX(n_events, padeCoefficients, zBG=1.96, neigh_length_m=3, def getBGRemovedIndices(n_events, zBG=1.96, calc_pp_lambda=False, neigh_length_m=3, qMask=None, peak=None, box=None, pp_lambda=None, peakNumber=-1, padeCoefficients=None, pplmin_frac=0.8, pplmax_frac=1.5, mindtBinWidth=1, maxdtBinWidth=50, - constraintScheme=1, peakMaskSize=5, iccFitDict=None): + constraintScheme=1, peakMaskSize=5, iccFitDict=None, fitPenalty=None): """ getBGRemovedIndices - A wrapper for getOptimizedGoodIDX Input: @@ -401,8 +399,8 @@ def getBGRemovedIndices(n_events, zBG=1.96, calc_pp_lambda=False, neigh_length_m minppl_frac=pplmin_frac, maxppl_frac=pplmax_frac, qMask=qMask, peak=peak, box=box, pp_lambda=pp_lambda, peakNumber=peakNumber, mindtBinWidth=mindtBinWidth, maxdtBinWidth=maxdtBinWidth, - constraintScheme=constraintScheme, - peakMaskSize=peakMaskSize, iccFitDict=iccFitDict) + constraintScheme=constraintScheme, peakMaskSize=peakMaskSize, + iccFitDict=iccFitDict, fitPenalty=fitPenalty) except KeyboardInterrupt: sys.exit() except: @@ -563,7 +561,7 @@ def get_pp_lambda(n_events, hasEventsIDX): def getTOFWS(box, flightPath, scatteringHalfAngle, tofPeak, peak, qMask, zBG=-1.0, dtSpread=0.02, minFracPixels=0.005, workspaceNumber=None, neigh_length_m=0, pp_lambda=None, calc_pp_lambda=False, padeCoefficients=None, pplmin_frac=0.8, pplmax_frac=1.5, peakMaskSize=5, - mindtBinWidth=1, maxdtBinWidth=50, constraintScheme=1, iccFitDict=None): + mindtBinWidth=1, maxdtBinWidth=50, constraintScheme=1, iccFitDict=None, fitPenalty=None): """ Builds a TOF profile from the data in box which is nominally centered around a peak. Input: @@ -607,8 +605,8 @@ def getTOFWS(box, flightPath, scatteringHalfAngle, tofPeak, peak, qMask, zBG=-1. calc_pp_lambda=calc_pp_lambda, padeCoefficients=padeCoefficients, pplmin_frac=pplmin_frac, pplmax_frac=pplmax_frac, mindtBinWidth=mindtBinWidth, maxdtBinWidth=maxdtBinWidth, - constraintScheme=constraintScheme, - peakMaskSize=peakMaskSize, iccFitDict=iccFitDict) + constraintScheme=constraintScheme, peakMaskSize=peakMaskSize, + iccFitDict=iccFitDict, fitPenalty=fitPenalty) hasEventsIDX = np.logical_and(goodIDX, qMask) boxMeanIDX = np.where(hasEventsIDX) else: # don't do background removal - just consider one pixel at a time @@ -662,7 +660,7 @@ def getTOFWS(box, flightPath, scatteringHalfAngle, tofPeak, peak, qMask, zBG=-1. if workspaceNumber is None: tofWS = CreateWorkspace( - OutputWorkspace='tofWS', DataX=tPoints, DataY=yPoints, DataE=np.sqrt(yPoints)) + OutputWorkspace='__tofWS', DataX=tPoints, DataY=yPoints, DataE=np.sqrt(yPoints)) else: tofWS = CreateWorkspace(OutputWorkspace='tofWS%i' % workspaceNumber, DataX=tPoints, DataY=yPoints, DataE=np.sqrt(yPoints)) @@ -837,7 +835,7 @@ def getBoxFracHKL(peak, peaks_ws, MDdata, UBMatrix, peakNumber, dQ, dQPixel=0.00 def doICCFit(tofWS, energy, flightPath, padeCoefficients, constraintScheme=None, outputWSName='fit', fitOrder=1, - iccFitDict=None): + iccFitDict=None, fitPenalty=None): """ doICCFit - Carries out the actual least squares fit for the TOF workspace. Intput: @@ -888,7 +886,6 @@ def doICCFit(tofWS, energy, flightPath, padeCoefficients, constraintScheme=None, HatWidth0 = [0., 5.] Scale0 = [0., np.inf] KConv0 = [100, 140] - # Now we see what instrument specific parameters we have if iccFitDict is not None: possibleKeys = ['iccA', 'iccB', 'iccR', 'iccT0', 'iccScale0', 'iccHatWidth', 'iccKConv'] @@ -899,25 +896,17 @@ def doICCFit(tofWS, energy, flightPath, padeCoefficients, constraintScheme=None, if len(iccFitDict[key] == 3): x0[keyIDX] = iccFitDict[key][2] fICC.setParameter(keyIDX, x0[keyIDX]) - try: - fICC.setPenalizedConstraints(A0=A0, B0=B0, R0=R0, T00=T00, KConv0=KConv0, penalty=1.0e10) - except: - fICC.setPenalizedConstraints(A0=A0, B0=B0, R0=R0, T00=T00, KConv0=KConv0, penalty=None) + fICC.setPenalizedConstraints(A0=A0, B0=B0, R0=R0, T00=T00, KConv0=KConv0, penalty=fitPenalty) if constraintScheme == 2: - try: - fICC.setPenalizedConstraints(A0=[0.0001, 1.0], B0=[0.005, 1.5], R0=[0.00, 1.], Scale0=[ - 0.0, 1.0e10], T00=[0, 1.0e10], KConv0=[100., 140.], penalty=1.0e20) - except: - fICC.setPenalizedConstraints(A0=[0.0001, 1.0], B0=[0.005, 1.5], R0=[0.00, 1.], Scale0=[ - 0.0, 1.0e10], T00=[0, 1.0e10], KConv0=[100, 140.], penalty=None) + fICC.setPenalizedConstraints(A0=[0.0001, 1.0], B0=[0.005, 1.5], R0=[0.00, 1.], Scale0=[ + 0.0, 1.0e10], T00=[0, 1.0e10], KConv0=[100., 140.], penalty=fitPenalty) f = FunctionWrapper(fICC) bg = Polynomial(n=fitOrder) - for i in range(fitOrder+1): bg['A'+str(fitOrder-i)] = bgx0[i] bg.constrain('-1.0 < A%i < 1.0' % fitOrder) fitFun = f + bg - fitResults = Fit(Function=fitFun, InputWorkspace='tofWS', + fitResults = Fit(Function=fitFun, InputWorkspace='__tofWS', Output=outputWSName) return fitResults, fICC @@ -927,7 +916,7 @@ def integrateSample(run, MDdata, peaks_ws, paramList, UBMatrix, dQ, qMask, padeC dQPixel=0.005, p=None, neigh_length_m=0, zBG=-1.0, bgPolyOrder=1, doIterativeBackgroundFitting=False, q_frame='sample', progressFile=None, minpplfrac=0.8, maxpplfrac=1.5, mindtBinWidth=1, maxdtBinWidth=50, - keepFitDict=False, constraintScheme=1, peakMaskSize=5, iccFitDict=None): + keepFitDict=False, constraintScheme=1, peakMaskSize=5, iccFitDict=None, fitPenalty=None): """ integrateSample contains the loop that integrates over all of the peaks in a run and saves the results. Importantly, it also handles errors (mostly by passing and recording special values for failed fits.) @@ -1003,19 +992,18 @@ def integrateSample(run, MDdata, peaks_ws, paramList, UBMatrix, dQ, qMask, padeC mindtBinWidth=mindtBinWidth, maxdtBinWidth=maxdtBinWidth, pplmin_frac=minpplfrac, pplmax_frac=maxpplfrac, - constraintScheme=constraintScheme, - peakMaskSize=peakMaskSize, iccFitDict=iccFitDict) - # --IN PRINCIPLE!!! WE CALCULATE THIS BEFORE GETTING HERE - tofWS = mtd['tofWS'] + constraintScheme=constraintScheme, peakMaskSize=peakMaskSize, + iccFitDict=iccFitDict, fitPenalty=fitPenalty) + tofWS = mtd['__tofWS'] fitResults, fICC = doICCFit( tofWS, energy, flightPath, padeCoefficients, fitOrder=bgPolyOrder, constraintScheme=constraintScheme, - iccFitDict=iccFitDict) + iccFitDict=iccFitDict, fitPenalty=fitPenalty) chiSq = fitResults.OutputChi2overDoF r = mtd['fit_Workspace'] param = mtd['fit_Parameters'] - tofWS = mtd['tofWS'] + tofWS = mtd['__tofWS'] iii = fICC.numParams() - 1 fitBG = [param.row(int(iii+bgIDX+1))['Value'] diff --git a/scripts/TofConverter.py b/scripts/TofConverter.py index de288605274385b0777d2f989d06d884ace58044..ec7294bed7da7d226ecd56593dca800acc5b5f10 100644 --- a/scripts/TofConverter.py +++ b/scripts/TofConverter.py @@ -7,19 +7,11 @@ #pylint: disable=invalid-name from __future__ import (absolute_import, division, print_function) from TofConverter import converterGUI -from PyQt4 import QtGui -import sys +from gui_helper import get_qapplication +app, within_mantid = get_qapplication() -def qapp(): - if QtGui.QApplication.instance(): - _app = QtGui.QApplication.instance() - else: - _app = QtGui.QApplication(sys.argv) - return _app - - -app = qapp() reducer = converterGUI.MainWindow()#the main ui class in this file is called MainWindow reducer.show() -app.exec_() +if not within_mantid: + app.exec_() diff --git a/scripts/TofConverter/converter.ui b/scripts/TofConverter/converter.ui index a3ce143faf75158b0c5699389174e9c76cf37a90..357112c63befc4ca35191e2307df1f842b668b5d 100644 --- a/scripts/TofConverter/converter.ui +++ b/scripts/TofConverter/converter.ui @@ -283,7 +283,7 @@ <string>Convert To:</string> </property> </widget> - <widget class="QPushButton" name="convert"> + <widget class="QPushButton" name="convertButton"> <property name="geometry"> <rect> <x>130</x> diff --git a/scripts/TofConverter/converterGUI.py b/scripts/TofConverter/converterGUI.py index 7f6abf7f1d3a90d93a15aac5f153ef12fbbfbcc6..0fe46da7d305f2c2ac3e74cb7b4e8839ddbadfb2 100644 --- a/scripts/TofConverter/converterGUI.py +++ b/scripts/TofConverter/converterGUI.py @@ -6,13 +6,24 @@ # SPDX - License - Identifier: GPL - 3.0 + #pylint: disable=invalid-name from __future__ import (absolute_import, division, print_function) -from .ui_converter import Ui_MainWindow #import line for the UI python class -from PyQt4 import QtCore, QtGui +from qtpy.QtWidgets import QMainWindow, QMessageBox +from qtpy.QtGui import QDoubleValidator +from qtpy import QtCore +import os +from mantid.kernel import Logger +from gui_helper import show_interface_help import math import TofConverter.convertUnits -class MainWindow(QtGui.QMainWindow): +try: + from mantidqt.utils.qt import load_ui +except ImportError: + Logger("TofConverter").information('Using legacy ui importer') + from mantidplot import load_ui + + +class MainWindow(QMainWindow): needsThetaInputList = ['Momentum transfer (Q Angstroms^-1)', 'd-spacing (Angstroms)'] needsThetaOutputList = ['Momentum transfer (Q Angstroms^-1)', 'd-spacing (Angstroms)'] needsFlightPathInputList = ['Time of flight (microseconds)'] @@ -52,16 +63,15 @@ class MainWindow(QtGui.QMainWindow): self.flightPathEnable(True) def __init__(self, parent=None): - QtGui.QMainWindow.__init__(self,parent) - self.ui = Ui_MainWindow() - self.ui.setupUi(self) - self.ui.InputVal.setValidator(QtGui.QDoubleValidator(self.ui.InputVal)) - self.ui.totalFlightPathInput.setValidator(QtGui.QDoubleValidator(self.ui.totalFlightPathInput)) - self.ui.scatteringAngleInput.setValidator(QtGui.QDoubleValidator(self.ui.scatteringAngleInput)) - QtCore.QObject.connect(self.ui.convert, QtCore.SIGNAL("clicked()"), self.convert ) - QtCore.QObject.connect(self.ui.helpButton, QtCore.SIGNAL("clicked()"), self.helpClicked) - QtCore.QObject.connect(self.ui.inputUnits, QtCore.SIGNAL("currentIndexChanged(QString)"), self.setInstrumentInputs ) - QtCore.QObject.connect(self.ui.outputUnits, QtCore.SIGNAL("currentIndexChanged(QString)"), self.setInstrumentInputs ) + QMainWindow.__init__(self,parent) + self.ui = load_ui(__file__, 'converter.ui', baseinstance=self) + self.ui.InputVal.setValidator(QDoubleValidator(self.ui.InputVal)) + self.ui.totalFlightPathInput.setValidator(QDoubleValidator(self.ui.totalFlightPathInput)) + self.ui.scatteringAngleInput.setValidator(QDoubleValidator(self.ui.scatteringAngleInput)) + self.ui.convertButton.clicked.connect(self.convert) + self.ui.helpButton.clicked.connect(self.helpClicked) + self.ui.inputUnits.currentIndexChanged.connect(self.setInstrumentInputs) + self.ui.outputUnits.currentIndexChanged.connect(self.setInstrumentInputs) self.setInstrumentInputs() ##defaults @@ -69,6 +79,16 @@ class MainWindow(QtGui.QMainWindow): self.Theta = -1.0 self.output = 0.0 + #help + self.assistant_process = QtCore.QProcess(self) + # pylint: disable=protected-access + import mantid + self.mantidplot_name='TOF Converter' + self.collection_file = os.path.join(mantid._bindir, '../docs/qthelp/MantidProject.qhc') + version = ".".join(mantid.__version__.split(".")[:2]) + self.qt_url = 'qthelp://org.sphinx.mantidproject.' + version + '/doc/interfaces/TOF Converter.html' + self.external_url = 'http://docs.mantidproject.org/nightly/interfaces/TOF Converter.html' + try: import mantid #register startup @@ -77,9 +97,16 @@ class MainWindow(QtGui.QMainWindow): pass def helpClicked(self): - # Temporary import while method is in the wrong place - from pymantidplot.proxies import showCustomInterfaceHelp - showCustomInterfaceHelp("TOF Converter") + show_interface_help(self.mantidplot_name, + self.assistant_process, + self.collection_file, + self.qt_url, + self.external_url) + + def closeEvent(self, event): + self.assistant_process.close() + self.assistant_process.waitForFinished() + event.accept() def convert(self): #Always reset these values before conversion. @@ -104,11 +131,11 @@ class MainWindow(QtGui.QMainWindow): self.ui.convertedVal.clear() self.ui.convertedVal.insert(str(self.output)) except UnboundLocalError as ule: - QtGui.QMessageBox.warning(self, "TofConverter", str(ule)) + QMessageBox.warning(self, "TofConverter", str(ule)) return except ArithmeticError as ae: - QtGui.QMessageBox.warning(self, "TofConverter", str(ae)) + QMessageBox.warning(self, "TofConverter", str(ae)) return except RuntimeError as re: - QtGui.QMessageBox.warning(self, "TofConverter", str(re)) + QMessageBox.warning(self, "TofConverter", str(re)) return diff --git a/scripts/directtools/__init__.py b/scripts/directtools/__init__.py index 3c266080cf5bcf570b273cbbda139d369ca40a6a..0f6e5027dc602e1db70b87a693fcc9d568892c9f 100644 --- a/scripts/directtools/__init__.py +++ b/scripts/directtools/__init__.py @@ -24,6 +24,11 @@ def _applyIfTimeSeries(logValue, function): return logValue +def _binCentres(edges): + """Return bin centers.""" + return (edges[:-1] + edges[1:]) / 2 + + def _chooseMarker(markers, index): """Pick a marker from markers and return next cyclic index.""" if index >= len(markers): @@ -33,10 +38,11 @@ def _chooseMarker(markers, index): return marker, index -def _clearlatex(s): - """Return string s with special LaTeX characters removed.""" - for c in ['%', '_', '$', '&', '\\', '^', '{', '}',]: +def _clearmath(s): + """Return string s with special math characters removed.""" + for c in ['%', '_', '$', '&', '\\', '^', '{', '}',]: s = s.replace(c, '') + s = s.replace(u'\u00c5', 'A') return s @@ -55,28 +61,56 @@ def _denormalizeLine(line): Es *= height +def _cutCentreAndWidth(line): + """Return cut centre and width as tuple.""" + axis = line.getAxis(1) + aMin = axis.getMin() + aMax = axis.getMax() + centre = (aMin + aMax) / 2. + width = aMax - aMin + return centre, width + + +def _energyLimits(workspaces): + """Find suitable xmin and xmax for energy transfer plots.""" + workspaces = _normwslist(workspaces) + eMax = 0. + for ws in workspaces: + Xs = workspaces[0].getAxis(1).extractValues() + eMax = max(eMax, Xs[-1]) + eMin = -eMax + logs = workspaces[0].run() + ei = _incidentEnergy(logs) + T = _applyIfTimeSeries(_sampleTemperature(logs), numpy.mean) + if ei is not None: + if T is not None: + eMin = min(-T / 6., -0.2 * ei) + else: + eMin = -0.2 * ei + return eMin, eMax + + def _finalizeprofileE(axes): """Set axes for const E axes.""" - axes.set_xlim(xmin=0.) - axes.set_xlabel('$Q$ (\\AA$^{-1}$)') - axes.set_ylim(0.) - xMin, xMax = axes.get_xlim() - print('Auto Q-range: {}...{} \xc5-1'.format(xMin, xMax)) + if axes.get_xscale() == 'linear': + axes.set_xlim(xmin=0.) + axes.set_xlabel(u'$Q$ (\u00c5$^{-1}$)') + if axes.get_yscale() == 'linear': + axes.set_ylim(0.) def _finalizeprofileQ(workspaces, axes): """Set axes for const Q axes.""" workspaces = _normwslist(workspaces) - axes.set_xlim(xmin=-10.) + eMin, eMax = _energyLimits(workspaces) + axes.set_xlim(xmax=eMax) + if axes.get_xscale() == 'linear': + axes.set_xlim(xmin=eMin) axes.set_xlabel('Energy (meV)') - cMax = 0. - for ws in workspaces: - c = numpy.nanmax(ws.readY(0)) - if c > cMax: - cMax = c - axes.set_ylim(ymin=0., ymax=cMax / 100.) - xMin, xMax = axes.get_xlim() - print('Auto E-range: {}...{} meV'.format(xMin, xMax)) + unusedMin, cMax = _globalnanminmax(workspaces) + if axes.get_yscale() == 'linear': + axes.set_ylim(ymin=0.) + axes.set_ylim(ymax=cMax / 100.) def _globalnanminmax(workspaces): @@ -86,24 +120,52 @@ def _globalnanminmax(workspaces): globalMax = -numpy.inf for ws in workspaces: cMin, cMax = nanminmax(ws) - if cMin < globalMin: - globalMin = cMin - if cMax > globalMax: - globalMax = cMax + globalMin = min(globalMin, cMin) + globalMax = max(globalMax, cMax) return globalMin, globalMax +def _horizontalLineAtZero(axes): + """Add a horizontal line at Y = 0.""" + spine = axes.spines['bottom'] + axes.axhline(linestyle=spine.get_linestyle(), color=spine.get_edgecolor(), linewidth=spine.get_linewidth()) + + +def _incidentEnergy(logs): + """Return the incident energy value from the logs or None.""" + if logs.hasProperty('Ei'): + return logs.getProperty('Ei').value + else: + return None + + +def _instrumentName(logs): + """Return the instrument name from the logs or None.""" + if logs.hasProperty('instrument.name'): + return logs.getProperty('instrument.name').value + else: + return None + + def _label(ws, cut, width, singleWS, singleCut, singleWidth, quantity, units): """Return a line label for a line profile.""" ws = _normws(ws) wsLabel = '' if not singleWS: - logs = SampleLogs(ws) - T = _applyIfTimeSeries(logs.sample.temperature, numpy.mean) - wsLabel = '\\#{:06d} $T$ = {:0.1f} K $E_i$ = {:0.2f} meV'.format(logs.run_number, T, logs.Ei) + logs = ws.run() + run = _runNumber(logs) + if run is not None: + wsLabel = wsLabel + r'#{:06d} '.format(run) + T = _sampleTemperature(logs) + if T is not None: + T = _applyIfTimeSeries(T, numpy.mean) + wsLabel = wsLabel + r'$T$ = {:0.1f} K '.format(T) + ei = _incidentEnergy(logs) + if ei is not None: + wsLabel = wsLabel + r'$E_i$ = {:0.2f} meV'.format(ei) cutLabel = '' if not singleCut or not singleWidth: - cutLabel = quantity + ' = {:0.2f} $\pm$ {:1.2f}'.format(cut, width) + ' ' + units + cutLabel = quantity + r' = {:0.2f} $\pm$ {:1.2f}'.format(cut, width) + ' ' + units return wsLabel + ' ' + cutLabel @@ -128,39 +190,34 @@ def _plottingtime(): return time.strftime('%d.%m.%Y %H:%M:%S') -def _binCentres(edges): - """Return bin centers.""" - return (edges[:-1] + edges[1:]) / 2 - - def _mantidsubplotsetup(): """Return the Mantid projection setup.""" return {'projection': 'mantid'} -def _profiletitle(workspaces, scan, units, cuts, widths, figure): +def _profiletitle(workspaces, cuts, scan, units, figure): """Add title to line profile figure.""" workspaces = _normwslist(workspaces) - if not isinstance(cuts, collections.Iterable): - cuts = [cuts] - if not isinstance(widths, collections.Iterable): - widths = [widths] * len(cuts) - if len(workspaces) == 1: + cuts = _normwslist(cuts) + if len(cuts) == 1: title = _singledatatitle(workspaces[0]) + centre, width = _cutCentreAndWidth(cuts[0]) + title = title + '\n' + scan + r' = {:0.2f} $\pm$ {:0.2f}'.format(centre, width) + ' ' + units else: - logs = SampleLogs(workspaces[0]) - title = logs.instrument.name + ' ' + _plottingtime() - if len(cuts) == 1 and len(widths) == 1: - title = title + '\n' + scan + ' = {:0.2f} $\pm$ {:0.2f}'.format(cuts[0], widths[0]) + ' ' + units + title = _plottingtime() + logs = workspaces[0].run() + instrument = _instrumentName(logs) + if instrument is not None: + title = instrument + ' ' + title figure.suptitle(title) def _profileytitle(workspace, axes): """Set the correct y label for profile axes.""" if workspace.YUnit() == 'Dynamic susceptibility': - axes.set_ylabel("$\\chi''(Q,E)$") + axes.set_ylabel(r"$\chi''(Q,E)$") else: - axes.set_ylabel('$S(Q,E)$') + axes.set_ylabel(r'$S(Q,E)$') def _removesingularity(ws, epsilon): @@ -177,12 +234,25 @@ def _removesingularity(ws, epsilon): es[binIndex] = 0. -def _sanitizeforlatex(s): - """Return a string with LaTeX special characters escaped.""" - s = s.replace('_', '\\_') - s = s.replace('#', '\\#') - s = s.replace('@', '\\@') - s = s.replace('&', '\\@') +def _runNumber(logs): + """Return run number from the logs or None.""" + if logs.hasProperty('run_number'): + return logs.getProperty('run_number').value + else: + return None + + +def _sampleTemperature(logs): + """Return the instrument specific sample temperature from the logs or None.""" + instrument = _instrumentName(logs) + if instrument in ['IN4', 'IN5', 'IN6']: + return logs.getProperty('sample.temperature').value + else: + return None + + +def _sanitize(s): + """Return a string with special characters escaped.""" s = s.replace('$', '\\$') return s @@ -190,12 +260,23 @@ def _sanitizeforlatex(s): def _singledatatitle(workspace): """Return title for a single data dataset.""" workspace = _normws(workspace) - logs = SampleLogs(workspace) - T = _applyIfTimeSeries(logs.sample.temperature, numpy.mean) - wsName = _sanitizeforlatex(str(workspace)) - title = (wsName + ' ' + logs.instrument.name + ' \\#{:06d}'.format(logs.run_number) + '\n' - + _plottingtime() + '\n' - + '$T$ = {:0.1f} K $E_i$ = {:0.2f} meV'.format(T, logs.Ei)) + wsName = _sanitize(str(workspace)) + title = wsName + logs = workspace.run() + instrument= _instrumentName(logs) + if instrument is not None: + title = title + ' ' + instrument + run = _runNumber(logs) + if run is not None: + title = title + ' #{:06d}'.format(run) + title = title + '\n' + _plottingtime() + '\n' + T = _sampleTemperature(logs) + if T is not None: + T = _applyIfTimeSeries(T, numpy.mean) + title = title + r'$T$ = {:0.1f} K'.format(T) + ei = _incidentEnergy(logs) + if ei is not None: + title = title + r' $E_i$ = {:0.2f} meV'.format(ei) return title @@ -206,6 +287,14 @@ def _SofQWtitle(workspace, figure): figure.suptitle(title) +def _wavelength(logs): + """Return the wavelength from the logs or None.""" + if logs.hasProperty('wavelength'): + return logs.getProperty('wavelength').value + else: + return None + + def box2D(xs, vertAxis, horMin=-numpy.inf, horMax=numpy.inf, vertMin=-numpy.inf, vertMax=numpy.inf): """Return slicing for a 2D numpy array limited by given min and max values. @@ -238,8 +327,7 @@ def defaultrcParams(): :returns: a :class:`dict` of default :mod:`matplotlib` rc parameters needed by :mod:`directtools` """ params = { - 'legend.numpoints': 1, - 'text.usetex': True, + 'legend.numpoints': 1 } return params @@ -276,7 +364,7 @@ def dynamicsusceptibility(workspace, temperature, outputName=None, zeroEnergyEps if doTranspose: outWS = Transpose(outWS, OutputWorkspace=outputName, EnableLogging=False) DeleteWorkspace('__transposed_SofQW_', EnableLogging=False) - outWS.setYUnit("Dynamic susceptibility") + outWS.setYUnit('Dynamic susceptibility') return outWS @@ -302,7 +390,7 @@ def nanminmax(workspace, horMin=-numpy.inf, horMax=numpy.inf, vertMin=-numpy.inf xs = workspace.extractX() ys = workspace.extractY() if xs.shape[1] > ys.shape[1]: - xs = _binCentres(xs) + xs = numpy.apply_along_axis(_binCentres, 1, xs) vertAxis = workspace.getAxis(1).extractValues() box = box2D(xs, vertAxis, horMin, horMax, vertMin, vertMax) ys = ys[box] @@ -336,15 +424,12 @@ def plotconstE(workspaces, E, dE, style='l', keepCutWorkspaces=True, xscale='lin :type yscale: str :returns: A tuple of (:class:`matplotlib.Figure`, :class:`matplotlib.Axes`, a :class:`list` of names) """ - figure, axes, cutWSList = plotcuts('Horizontal', workspaces, E, dE, '$E$', 'meV', style, keepCutWorkspaces, + figure, axes, cutWSList = plotcuts('Horizontal', workspaces, E, dE, r'$E$', 'meV', style, keepCutWorkspaces, xscale, yscale) - _profiletitle(workspaces, '$E$', 'meV', E, dE, figure) - axes.legend() - axes.set_xlim(xmin=0.) - axes.set_xlabel('$Q$ (\\AA$^{-1}$)') - axes.set_ylim(0.) - xMin, xMax = axes.get_xlim() - print('Auto Q-range: {}...{} \xc5-1'.format(xMin, xMax)) + _profiletitle(workspaces, cutWSList, r'$E$', 'meV', figure) + if len(cutWSList) > 1: + axes.legend() + _finalizeprofileE(axes) return figure, axes, cutWSList @@ -373,16 +458,12 @@ def plotconstQ(workspaces, Q, dQ, style='l', keepCutWorkspaces=True, xscale='lin :type yscale: str :returns: A tuple of (:class:`matplotlib.Figure`, :class:`matplotlib.Axes`, a :class:`list` of names) """ - figure, axes, cutWSList = plotcuts('Vertical', workspaces, Q, dQ, '$Q$', '\\AA$^{-1}$', style, keepCutWorkspaces, + figure, axes, cutWSList = plotcuts('Vertical', workspaces, Q, dQ, r'$Q$', u'\u00c5$^{-1}$', style, keepCutWorkspaces, xscale, yscale) - _profiletitle(workspaces, '$Q$', '\\AA$^{-1}$', Q, dQ, figure) - axes.legend() - axes.set_xlim(xmin=-10.) - axes.set_xlabel('Energy (meV)') - cMin, cMax = _globalnanminmax(workspaces) - axes.set_ylim(ymin=0., ymax=cMax / 100.) - xMin, xMax = axes.get_xlim() - print('Auto E-range: {}...{} meV'.format(xMin, xMax)) + _profiletitle(workspaces, cutWSList, r'$Q$', u'\u00c5$^{-1}$', figure) + if len(cutWSList) > 1: + axes.legend() + _finalizeprofileQ(workspaces, axes) return figure, axes, cutWSList @@ -436,8 +517,8 @@ def plotcuts(direction, workspaces, cuts, widths, quantity, unit, style='l', kee wsStr = str(ws) if wsStr == '': wsStr = str(wsCount) - quantityStr = _clearlatex(quantity) - unitStr = _clearlatex(unit) + quantityStr = _clearmath(quantity) + unitStr = _clearmath(unit) wsName = 'cut_{}_{}={}+-{}{}'.format(wsStr, quantityStr, cut, width, unitStr) if keepCutWorkspaces: cutWSList.append(wsName) @@ -447,10 +528,13 @@ def plotcuts(direction, workspaces, cuts, widths, quantity, unit, style='l', kee _denormalizeLine(line) if 'm' in style: markerStyle, markerIndex = _chooseMarker(markers, markerIndex) - label = _label(ws, cut, width, len(workspaces) == 1, len(cuts) == 1, len(widths) == 1, quantity, unit) + realCutCentre, realCutWidth = _cutCentreAndWidth(line) + label = _label(ws, realCutCentre, realCutWidth, len(workspaces) == 1, len(cuts) == 1, len(widths) == 1, quantity, unit) axes.errorbar(line, specNum=0, linestyle=lineStyle, marker=markerStyle, label=label, distribution=True) axes.set_xscale(xscale) axes.set_yscale(yscale) + if axes.get_yscale() == 'linear': + _horizontalLineAtZero(axes) _profileytitle(workspaces[0], axes) return figure, axes, cutWSList @@ -491,6 +575,7 @@ def plotprofiles(workspaces, labels=None, style='l', xscale='linear', yscale='li axes.errorbar(ws, specNum=0, linestyle=lineStyle, marker=markerStyle, label=label, distribution=True) axes.set_xscale(xscale) axes.set_yscale(yscale) + _horizontalLineAtZero(axes) _profileytitle(workspaces[0], axes) xUnit = workspaces[0].getAxis(0).getUnit().unitID() if xUnit == 'DeltaE': @@ -535,10 +620,10 @@ def plotSofQW(workspace, QMin=0., QMax=None, EMin=None, EMax=None, VMin=0., VMax if isSusceptibility: EMin = 0. else: - EMin = -10. + EMin, unusedEMax = _energyLimits(workspace) if EMax is None: EAxis = workspace.getAxis(1).extractValues() - EMax = numpy.amax(EAxis) + EMax = EAxis[-1] if VMax is None: vertMax = EMax if EMax is not None else numpy.inf dummy, VMax = nanminmax(workspace, horMin=QMin, horMax=QMax, vertMin=EMin, vertMax=vertMax) @@ -553,23 +638,22 @@ def plotSofQW(workspace, QMin=0., QMax=None, EMin=None, EMax=None, VMin=0., VMax if VMax > 0.: VMin = VMax / 1000. else: - raise RuntimeError('Cannot plot negative range in log scale.') + raise RuntimeError('Cannot plot nonpositive range in log scale.') colorNormalization = matplotlib.colors.LogNorm() else: raise RuntimeError('Unknown colorscale: ' + colorscale) - print('Plotting intensity range: {}...{}'.format(VMin, VMax)) contours = axes.pcolor(workspace, vmin=VMin, vmax=VMax, distribution=True, cmap=colormap, norm=colorNormalization) colorbar = figure.colorbar(contours) if isSusceptibility: - colorbar.set_label("$\\chi''(Q,E)$ (arb. units)") + colorbar.set_label(r"$\chi''(Q,E)$ (arb. units)") else: - colorbar.set_label('$S(Q,E)$ (arb. units)') + colorbar.set_label(r'$S(Q,E)$ (arb. units)') axes.set_xlim(left=QMin) axes.set_xlim(right=QMax) axes.set_ylim(bottom=EMin) if EMax is not None: axes.set_ylim(top=EMax) - axes.set_xlabel('$Q$ (\\AA$^{-1}$)') + axes.set_xlabel(u'$Q$ (\u00c5$^{-1}$)') axes.set_ylabel('Energy (meV)') _SofQWtitle(workspace, figure) return figure, axes @@ -624,20 +708,39 @@ def wsreport(workspace): :returns: None """ workspace = _normws(workspace) + logs = workspace.run() print(str(workspace)) - logs = SampleLogs(workspace) - print('Instrument: ' + logs.instrument.name) - print('Run Number: {:06d}'.format(logs.run_number)) - print('Start Time: {}'.format(logs.start_time)) - print('Ei = {:0.2f} meV lambda = {:0.2f} \xc5'.format(logs.Ei, logs.wavelength)) - meanT = _applyIfTimeSeries(logs.sample.temperature, numpy.mean) - stdT = _applyIfTimeSeries(logs.sample.temperature, numpy.std) - print('T = {:0.2f} +- {:4.2f} K'.format(meanT, stdT)) - minT = _applyIfTimeSeries(logs.sample.temperature, numpy.amin) - maxT = _applyIfTimeSeries(logs.sample.temperature, numpy.amax) - print('T in [{:0.2f},{:0.2f}]'.format(minT, maxT)) - fermiHertz = logs.FC.rotation_speed / 60. - print('Fermi = {:0.0f} rpm = {:0.1f} Hz'.format(logs.FC.rotation_speed, fermiHertz)) + instrument = _instrumentName(logs) + if instrument is not None: + print('Instrument: ' + instrument) + if logs.hasProperty('run_number'): + print('Run Number: {:06d}'.format(logs.getProperty('run_number').value)) + if logs.hasProperty('start_time'): + print('Start Time: {}'.format(logs.getProperty('start_time').value.replace('T', ' '))) + ei = _incidentEnergy(logs) + wavelength = _wavelength(logs) + if ei is not None and wavelength is not None: + print('Ei = {:0.2f} meV lambda = {:0.2f} A'.format(ei, wavelength)) + T = _sampleTemperature(logs) + if T is not None: + if isinstance(T, collections.Iterable): + meanT = _applyIfTimeSeries(T, numpy.mean) + stdT = _applyIfTimeSeries(T, numpy.std) + print('T = {:0.2f} +- {:4.2f} K'.format(meanT, stdT)) + minT = _applyIfTimeSeries(T, numpy.amin) + maxT = _applyIfTimeSeries(T, numpy.amax) + print('T in [{:0.2f},{:0.2f}]'.format(minT, maxT)) + else: + print('T = {:0.2f} K'.format(T)) + # Instrument specific additional information + if instrument == 'IN4': + rpm = logs.getProperty('FC.rotation_speed').value + hertz = rpm / 60. + print('Fermi = {:0.0f} rpm = {:0.1f} Hz'.format(rpm, hertz)) + elif instrument == 'IN6': + rpm = logs.getProperty('Fermi.rotation_speed').value + hertz = rpm / 60. + print('Fermi = {:0.0f} rpm = {:0.1f} Hz'.format(rpm, hertz)) class SampleLogs: diff --git a/scripts/gui_helper.py b/scripts/gui_helper.py new file mode 100644 index 0000000000000000000000000000000000000000..b5b08a9ba1d30b5adedb9b84bf3c94ff326568ed --- /dev/null +++ b/scripts/gui_helper.py @@ -0,0 +1,107 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +from __future__ import (absolute_import, division, print_function) +from qtpy.QtWidgets import (QApplication) # noqa +from qtpy import QtCore, QtGui +import matplotlib +import sys +import os + + +def set_matplotlib_backend(): + '''MUST be called before anything tries to use matplotlib + + This will set the backend if it hasn't been already. It also returns + the name of the backend to be the name to be used for importing the + correct matplotlib widgets.''' + backend = matplotlib.get_backend() + if backend.startswith('module://'): + if backend.endswith('qt4agg'): + backend = 'Qt4Agg' + elif backend.endswith('workbench') or backend.endswith('qt5agg'): + backend = 'Qt5Agg' + else: + from qtpy import PYQT4, PYQT5 # noqa + if PYQT4: + backend = 'Qt4Agg' + elif PYQT5: + backend = 'Qt5Agg' + else: + raise RuntimeError('Do not know which matplotlib backend to set') + matplotlib.use(backend) + return backend + + +def get_qapplication(): + ''' Example usage: + + app, within_mantid = get_qapplication() + reducer = eventFilterGUI.MainWindow() # the main ui class in this file + reducer.show() + if not within_mantid: + sys.exit(app.exec_())''' + app = QApplication.instance() + if app: + return app, app.applicationName().lower().startswith('mantid') + else: + return QApplication(sys.argv), False + + +def show_interface_help(mantidplot_name, assistant_process, collection_file, qt_url, external_url): + ''' Shows the help page for a custom interface + @param mantidplot_name: used by showCustomInterfaceHelp + @param assistant_process: needs to be started/closed from outside (see example below) + @param collection_file: qth file containing the help in format used by qtassistant + @param qt_url: location of the help in the qth file + @param external_url: location of external page to be displayed in the default browser + + Example: + + #in the __init__ function of the GUI add: + self.assistant_process = QtCore.QProcess(self) + self.mantidplot_name='DGS Planner' + self.collection_file = os.path.join(mantid._bindir, '../docs/qthelp/MantidProject.qhc') + version = ".".join(mantid.__version__.split(".")[:2]) + self.qt_url = 'qthelp://org.sphinx.mantidproject.' + version + '/doc/interfaces/DGS Planner.html' + self.external_url = 'http://docs.mantidproject.org/nightly/interfaces/DGS Planner.html' + + #add a help function in the GUI + def help(self): + show_interface_help(self.mantidplot_name, + self.assistant_process, + self.collection_file, + self.qt_url, + self.external_url) + + #make sure you close the qtassistant when the GUI is closed + def closeEvent(self, event): + self.assistant_process.close() + self.assistant_process.waitForFinished() + event.accept() + ''' + try: + import pymantidplot + pymantidplot.proxies.showCustomInterfaceHelp(mantidplot_name) + except: #(ImportError, ModuleNotFoundError) raises the wrong type of error + assistant_process.close() + assistant_process.waitForFinished() + helpapp = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.BinariesPath) + QtCore.QDir.separator() + helpapp += 'assistant' + args = ['-enableRemoteControl', '-collectionFile', collection_file, '-showUrl', qt_url] + if os.path.isfile(helpapp) and os.path.isfile(collection_file): + assistant_process.close() + assistant_process.waitForFinished() + assistant_process.start(helpapp, args) + else: + openUrl=QtGui.QDesktopServices.openUrl + sysenv=QtCore.QProcessEnvironment.systemEnvironment() + ldp=sysenv.value('LD_PRELOAD') + if ldp: + del os.environ['LD_PRELOAD'] + openUrl(QtCore.QUrl(external_url)) + if ldp: + os.environ['LD_PRELOAD']=ldp diff --git a/scripts/test/Muon/CMakeLists.txt b/scripts/test/Muon/CMakeLists.txt index e991ac70f7f79f2c198e2223262a8ea4d3d6ed59..06bd0ca1be0bf950718cd81bf2ca3ebf1e1f4da8 100644 --- a/scripts/test/Muon/CMakeLists.txt +++ b/scripts/test/Muon/CMakeLists.txt @@ -17,8 +17,9 @@ set ( TEST_PY_FILES PlottingPresenter_test.py PlottingUtils_test.py PlottingView_test.py - thread_model_test.py + #thread_model_test.py transformWidget_test.py + subplotObject_test.py ) check_tests_valid ( ${CMAKE_CURRENT_SOURCE_DIR} ${TEST_PY_FILES} ) diff --git a/scripts/test/Muon/PlottingPresenter_test.py b/scripts/test/Muon/PlottingPresenter_test.py index a02c19997bd45b2471245594fdf9ba43f38e01d1..41ff7a2f9db6c1306d68f40ce49f2f51eca2ab42 100644 --- a/scripts/test/Muon/PlottingPresenter_test.py +++ b/scripts/test/Muon/PlottingPresenter_test.py @@ -18,20 +18,69 @@ try: except ImportError: import mock +# simple class to mock QDialogs + + +class dummy_popup(object): + + def __init__(self, lines, parent=None): + self.dummy = True + + def setMinimumSize(self, min1, min2): + return + + def show(self): + return + + def raise_(self): + return + class PlottingPresenterTest(unittest.TestCase): + def setUp(self): view = mock.create_autospec(PlotView) + view.subplotRemovedSignal = mock.Mock() self.presenter = PlotPresenter(view) self.presenter.view.canvas = mock.Mock() self.presenter.view.canvas.draw = mock.Mock() + self.view = self.presenter.view + self.view.close = mock.Mock() + self.view.get_subplots = mock.Mock(return_value={}) + self.view.removeLine = mock.Mock() + + # explicitly mock pop up + self.mock_selector = mock.create_autospec(dummy_popup) + self.mock_selector.subplotSelectorSignal = mock.Mock() + self.mock_selector.closeEventSignal = mock.Mock() + + self.mock_rmWindow = mock.create_autospec(dummy_popup) + self.mock_rmWindow.applyRemoveSignal = mock.Mock() + self.mock_rmWindow.closeEventSignal = mock.Mock() + self.mock_rmWindow.subplot = mock.Mock(return_value="plot") + self.mock_rmWindow.getState = mock.Mock(side_effect=[True, True]) + self.mock_rmWindow.getLine = mock.Mock() + + # mock presenter to create mock pop ups + self.presenter.createRmWindow = mock.Mock( + return_value=self.mock_rmWindow) + self.presenter.createSelectWindow = mock.Mock( + return_value=self.mock_selector) self.mock_name = mock.Mock() self.mock_workspace = mock.Mock() self.mock_func = mock.Mock() self.mock_arbitrary_args = [mock.Mock() for i in range(3)] + def test_setup(self): + self.assertEquals(self.view.setRmConnection.call_count, 1) + # self.assertEquals(self.view.setAddConnection.call_count ,1) + self.assertEquals(self.view.plotCloseConnection.call_count, 1) + self.view.setRmConnection.assert_called_with(self.presenter.rm) + # self.view.setAddConnection.assert_called_with(self.presenter.add) + self.view.plotCloseConnection.assert_called_with(self.presenter.close) + def test_get_subplot(self): self.presenter.get_subplot(self.mock_name) self.view.get_subplot.assert_called_with(self.mock_name) @@ -68,6 +117,274 @@ class PlottingPresenterTest(unittest.TestCase): """ pass + def test_removeSubplotConnection(self): + self.presenter.removeSubplotConnection(self.mock_func) + self.assertEquals( + self.view.subplotRemovedSignal.connect.call_count, + 1) + self.view.subplotRemovedSignal.connect.assert_called_with( + self.mock_func) + + def test_closeSelector(self): + # mock called function to test they are called + self.presenter.closeSelectorWindow = mock.Mock() + self.presenter.closeRmWindow = mock.Mock() + # set windows to test them + self.presenter.selectorWindow = mock.Mock() + self.presenter.rmWindow = None + # do test + self.presenter.close() + self.assertEquals(self.presenter.closeSelectorWindow.call_count, 1) + self.assertEquals(self.presenter.closeRmWindow.call_count, 0) + + def test_closeRm(self): + # mock called function to test they are called + self.presenter.closeSelectorWindow = mock.Mock() + self.presenter.closeRmWindow = mock.Mock() + # set windows to test them + self.presenter.selectorWindow = None + self.presenter.rmWindow = mock.Mock() + # do test + self.presenter.close() + self.assertEquals(self.presenter.closeSelectorWindow.call_count, 0) + self.assertEquals(self.presenter.closeRmWindow.call_count, 1) + + def test_closeNone(self): + # mock called function to test they are called + self.presenter.closeSelectorWindow = mock.Mock() + self.presenter.closeRmWindow = mock.Mock() + # set windows to test them + self.presenter.selectorWindow = None + self.presenter.rmWindow = None + # do test + self.presenter.close() + self.assertEquals(self.presenter.closeSelectorWindow.call_count, 0) + self.assertEquals(self.presenter.closeRmWindow.call_count, 0) + + def test_closeAll(self): + # mock called function to test they are called + self.presenter.closeSelectorWindow = mock.Mock() + self.presenter.closeRmWindow = mock.Mock() + # set windows to test them + self.presenter.selectorWindow = mock.Mock() + self.presenter.rmWindow = mock.Mock() + # do test + self.presenter.close() + self.assertEquals(self.presenter.closeSelectorWindow.call_count, 1) + self.assertEquals(self.presenter.closeRmWindow.call_count, 1) + + def test_add(self): + # to do + pass + + def test_rmOneSubplot(self): + self.view.subplot_names = ["one plot"] + self.presenter.rmWindow = None + self.presenter.selectorWindow = None + self.presenter.getRmWindow = mock.Mock() + # run test + self.presenter.rm() + + # skip to rm window if subplot == 1 + self.assertEquals(self.presenter.getRmWindow.call_count, 1) + + # never create selector window + self.assertEquals(self.presenter.createSelectWindow.call_count, 0) + self.assertEquals( + self.mock_selector.subplotSelectorSignal.connect.call_count, + 0) + self.assertEquals( + self.mock_selector.closeEventSignal.connect.call_count, + 0) + self.assertEquals(self.mock_selector.setMinimumSize.call_count, 0) + self.assertEquals(self.mock_selector.show.call_count, 0) + # never make the remove window or raise any windows + self.assertEquals(self.mock_rmWindow.raise_.call_count, 0) + self.assertEquals(self.mock_selector.raise_.call_count, 0) + + def test_rmTwoSubplots(self): + self.view.subplot_names = ["one plot", "two plots"] + self.presenter.rmWindow = None + self.presenter.selectorWindow = None + self.presenter.getRmWindow = mock.Mock() + + # do test + self.presenter.rm() + # create selector window if > 2 + self.assertEquals(self.presenter.createSelectWindow.call_count, 1) + self.presenter.createSelectWindow.assert_called_with( + self.view.subplot_names) + self.assertEquals( + self.mock_selector.subplotSelectorSignal.connect.call_count, + 1) + self.mock_selector.subplotSelectorSignal.connect.assert_called_with( + self.presenter.getRmWindow) + self.assertEquals( + self.mock_selector.closeEventSignal.connect.call_count, + 1) + self.mock_selector.closeEventSignal.connect.assert_called_with( + self.presenter.closeSelectorWindow) + self.assertEquals(self.mock_selector.setMinimumSize.call_count, 1) + self.assertEquals(self.mock_selector.show.call_count, 1) + # never make the remove window or raise any windows + self.assertEquals(self.presenter.getRmWindow.call_count, 0) + self.assertEquals(self.mock_rmWindow.raise_.call_count, 0) + self.assertEquals(self.mock_selector.raise_.call_count, 0) + + def test_RaiseRmWindow(self): + self.view.subplot_names = ["one plot", "two plots"] + self.presenter.rmWindow = self.mock_rmWindow + self.presenter.selectorWindow = None + self.presenter.getRmWindow = mock.Mock() + + # do test + self.presenter.rm() + # raise rm window + self.assertEquals(self.mock_rmWindow.raise_.call_count, 1) + # never create selector + self.assertEquals(self.presenter.createSelectWindow.call_count, 0) + self.assertEquals( + self.mock_selector.subplotSelectorSignal.connect.call_count, + 0) + self.assertEquals( + self.mock_selector.closeEventSignal.connect.call_count, + 0) + self.assertEquals(self.mock_selector.setMinimumSize.call_count, 0) + self.assertEquals(self.mock_selector.show.call_count, 0) + # never make the remove window or raise selector windows + self.assertEquals(self.presenter.getRmWindow.call_count, 0) + self.assertEquals(self.mock_selector.raise_.call_count, 0) + + def test_RaiseSelector(self): + self.view.subplot_names = ["one plot", "two plots"] + self.presenter.rmWindow = None + self.presenter.selectorWindow = self.mock_selector + self.presenter.getRmWindow = mock.Mock() + + # do test + self.presenter.rm() + # raise selector window + self.assertEquals(self.mock_selector.raise_.call_count, 1) + # never create selector + self.assertEquals(self.presenter.createSelectWindow.call_count, 0) + self.assertEquals( + self.mock_selector.subplotSelectorSignal.connect.call_count, + 0) + self.assertEquals( + self.mock_selector.closeEventSignal.connect.call_count, + 0) + self.assertEquals(self.mock_selector.setMinimumSize.call_count, 0) + self.assertEquals(self.mock_selector.show.call_count, 0) + # never make the remove window or raise selector windows + self.assertEquals(self.mock_rmWindow.raise_.call_count, 0) + self.assertEquals(self.presenter.getRmWindow.call_count, 0) + + def test_getRmWindow(self): + + result = "plot" + self.presenter.closeSelectorWindow = mock.Mock() + self.presenter.createRmWindow = mock.Mock( + return_value=self.mock_rmWindow) + # run test + self.presenter.getRmWindow(result) + + self.assertEquals(self.presenter.createRmWindow.call_count, 1) + self.presenter.createRmWindow.assert_called_with(subplot=result) + self.assertEquals( + self.mock_rmWindow.applyRemoveSignal.connect.call_count, + 1) + self.mock_rmWindow.applyRemoveSignal.connect.assert_called_with( + self.presenter.applyRm) + self.assertEquals( + self.mock_rmWindow.closeEventSignal.connect.call_count, + 1) + self.mock_rmWindow.closeEventSignal.connect.assert_called_with( + self.presenter.closeRmWindow) + self.assertEquals(self.mock_rmWindow.show.call_count, 1) + self.assertEquals(self.mock_rmWindow.setMinimumSize.call_count, 1) + + def test_applyRm0(self): + names = ["line 1", "line 2"] + self.presenter.rmWindow = self.mock_rmWindow + self.mock_rmWindow.subplot = mock.Mock(return_value="plot") + self.mock_rmWindow.getState = mock.Mock(side_effect=[False, False]) + self.view.get_subplots = mock.Mock(return_value={"plot": 1}) + self.presenter.remove_subplot = mock.Mock() + self.presenter.closeRmWindow = mock.Mock() + + # do test + self.presenter.applyRm(names) + # check both lines + self.assertEquals(self.mock_rmWindow.getState.call_count, 2) + # only remove 1 + self.assertEquals(self.mock_rmWindow.getLine.call_count, 0) + self.assertEquals(self.view.removeLine.call_count, 0) + # delete subplot + self.assertEquals(self.presenter.remove_subplot.call_count, 0) + # close window but keep plot + self.assertEquals(self.presenter.closeRmWindow.call_count, 1) + self.assertEquals(self.view.close.call_count, 0) + + def test_applyRm1(self): + names = ["line 1", "line 2"] + self.presenter.rmWindow = self.mock_rmWindow + self.mock_rmWindow.getState = mock.Mock(side_effect=[True, False]) + self.presenter.remove_subplot = mock.Mock() + self.view.get_subplots = mock.Mock(return_value={"plot": 1}) + self.presenter.closeRmWindow = mock.Mock() + + # do test + self.presenter.applyRm(names) + # check both lines + self.assertEquals(self.mock_rmWindow.getState.call_count, 2) + # only remove 1 + self.assertEquals(self.mock_rmWindow.getLine.call_count, 1) + self.assertEquals(self.view.removeLine.call_count, 1) + # keep subplot + self.assertEquals(self.presenter.remove_subplot.call_count, 0) + # close window + self.assertEquals(self.presenter.closeRmWindow.call_count, 1) + self.assertEquals(self.view.close.call_count, 0) + + def test_applyRm2(self): + names = ["line 1", "line 2"] + self.presenter.rmWindow = self.mock_rmWindow + self.presenter.remove_subplot = mock.Mock() + self.mock_rmWindow.getState = mock.Mock(side_effect=[True, True]) + self.view.get_subplots = mock.Mock(return_value={"plot": 1}) + self.presenter.closeRmWindow = mock.Mock() + + # do test + self.presenter.applyRm(names) + # check both lines + self.assertEquals(self.mock_rmWindow.getState.call_count, 2) + # only remove 1 + self.assertEquals(self.mock_rmWindow.getLine.call_count, 2) + self.assertEquals(self.view.removeLine.call_count, 2) + # delete subplot + self.assertEquals(self.presenter.remove_subplot.call_count, 1) + # close window but keep plot + self.assertEquals(self.presenter.closeRmWindow.call_count, 1) + self.assertEquals(self.view.close.call_count, 0) + + def test_applyRmAndClose(self): + names = ["line 1", "line 2"] + self.presenter.rmWindow = self.mock_rmWindow + self.presenter.remove_subplot = mock.Mock() + self.presenter.closeRmWindow = mock.Mock() + + # do test + self.presenter.applyRm(names) + # check both lines + self.assertEquals(self.mock_rmWindow.getState.call_count, 2) + # only remove 1 + self.assertEquals(self.mock_rmWindow.getLine.call_count, 2) + self.assertEquals(self.view.removeLine.call_count, 2) + # delete subplot + self.assertEquals(self.presenter.remove_subplot.call_count, 1) + # close window but keep plot + self.assertEquals(self.presenter.closeRmWindow.call_count, 1) + self.assertEquals(self.view.close.call_count, 1) if __name__ == "__main__": unittest.main() diff --git a/scripts/test/Muon/PlottingView_test.py b/scripts/test/Muon/PlottingView_test.py index 8924e1c168f8e373587da9389db0af32e6599820..16c54482ebee6b48dbd2c307852e1545c0472af4 100644 --- a/scripts/test/Muon/PlottingView_test.py +++ b/scripts/test/Muon/PlottingView_test.py @@ -9,6 +9,11 @@ import unittest import os os.environ["QT_API"] = "pyqt" # noqa E402 +from matplotlib.figure import Figure + +from mantid.simpleapi import * +from mantid import plots +from Muon.GUI.ElementalAnalysis.Plotting.subPlot_object import subPlot from Muon.GUI.ElementalAnalysis.Plotting.plotting_view import PlotView from Muon.GUI.ElementalAnalysis.Plotting.AxisChanger.axis_changer_presenter import AxisChangerPresenter @@ -20,7 +25,21 @@ except ImportError: import mock +def get_subPlot(name): + ws1 = CreateWorkspace(DataX=[1, 2, 3, 4], DataY=[4, 5, 6, 7], NSpec=2) + label1 = "test" + # create real lines + fig = Figure() + sub = fig.add_subplot(1, 1, 1) + line1 = plots.plotfunctions.plot(sub, ws1, specNum=1) + # add them both + subplot = subPlot(name) + subplot.addLine(label1, line1, ws1, 2) + return subplot, ws1 + + class PlottingViewHelperFunctionTests(unittest.TestCase): + def setUp(self): self._qapp = mock_widget.mockQapp() @@ -45,7 +64,6 @@ class PlottingViewHelperFunctionTests(unittest.TestCase): self.view.canvas.draw = mock.Mock() self.view.plots = {self.plot_name: self.plots_return_value} - self.view.workspaces = {self.plot_name: [self.plots_return_value]} self.mock_plot = mock.Mock() self.mock_plot.get_xlim = mock.Mock() self.mock_plot.get_ylim = mock.Mock() @@ -219,12 +237,19 @@ class PlottingViewHelperFunctionTests(unittest.TestCase): args = [self.plot_name, self.plots_return_value, True] self.view._modify_errors_list = mock.Mock() self.view.plot = mock.Mock() - self.view.workspace_plots[args[0]] = [self.plots_return_value] + # create subplot object + subplot, ws = get_subPlot(self.plot_name) + self.view.plot_storage = {self.plot_name: subplot} + self.view.plot_storage[self.plot_name].delete = mock.Mock() + self.view._change_plot_errors(*args) self.view._modify_errors_list.assert_called_once_with(*args[::2]) - self.assertEquals(self.plots_return_value.remove.call_count, 1) + self.assertEquals( + self.view.plot_storage[ + self.plot_name].delete.call_count, + 1) self.view.plot.assert_called_once_with( - self.plot_name, self.plots_return_value) + self.plot_name, ws) def test_set_positions(self): self.view._set_positions([[0, 0]]) @@ -268,19 +293,9 @@ class PlottingViewHelperFunctionTests(unittest.TestCase): self.view.plot_selector.addItems.assert_called_once_with( list(self.view.plots.keys())) - def test_add_workspace_name_if_not_in_workspaces(self): - self.view._add_workspace_name(self.plot_name, self.mock_workspace) - self.assertEquals(self.view.workspaces[self.plot_name], [ - self.plots_return_value, self.mock_workspace]) - - def test_add_workspace_name_if_in_workspaces(self): - self.view.workspaces = {} - self.view._add_workspace_name(self.plot_name, self.mock_workspace) - self.assertEquals(self.view.workspaces[self.plot_name], [ - self.mock_workspace]) - class PlottingViewPlotFunctionsTests(unittest.TestCase): + def setUp(self): self._qapp = mock_widget.mockQapp() @@ -310,7 +325,6 @@ class PlottingViewPlotFunctionsTests(unittest.TestCase): self.view._set_bounds = mock.Mock() self.view.plots = {self.plot_name: self.plots_return_value} - self.view.workspaces = {self.plot_name: self.plots_return_value} self.view.plot_additions = {self.plot_name: self.plots_return_value} def test_plot_errors_in_errors_list(self): @@ -329,13 +343,25 @@ class PlottingViewPlotFunctionsTests(unittest.TestCase): self.view._set_bounds.assert_called_once_with(self.plot_name) @mock.patch("mantid.plots.plotfunctions.errorbar") - def test_plot_workspace_errors(self, error_bar): + #@mock.patch("mantid.plots.plotfunctions.plot") + def test_plot_workspace_errors(self, error_bar): # , normal_plot): + self.view._add_plotted_line = mock.Mock() error_bar.return_value = tuple([[] for i in range(3)]) - self.view.plot_workspace_errors(self.plot_name, self.mock_workspace) - self.assertEquals(error_bar.call_count, 1) + mock_line = mock.Mock() + mock_line.get_label = mock.Mock(return_value="test") + mock_line.remove = mock.Mock() + with mock.patch("mantid.plots.plotfunctions.plot") as normal_plot: + normal_plot.return_value = tuple([mock_line]) + self.view.plot_workspace_errors( + self.plot_name, + self.mock_workspace) + self.assertEquals(error_bar.call_count, 1) + self.assertEquals(self.view._add_plotted_line.call_count, 1) + self.assertEquals(normal_plot.call_count, 1) @mock.patch("mantid.plots.plotfunctions.plot") def test_plot_workspace(self, plot): + self.view._add_plotted_line = mock.Mock() plot.return_value = tuple([mock.Mock()]) self.view.plot_workspace(self.plot_name, self.mock_workspace) self.assertEquals(plot.call_count, 1) @@ -362,10 +388,14 @@ class PlottingViewPlotFunctionsTests(unittest.TestCase): self.assertEquals(return_value, True) def test_remove_subplot(self): + self.view.plot_storage = {self.plot_name: get_subPlot(self.plot_name)} + self.view.subplotRemovedSignal = mock.Mock() self.view.remove_subplot(self.plot_name) + self.view.subplotRemovedSignal.callled_once_with(self.plot_name) self.view.figure.delaxes.assert_called_once_with( self.plots_return_value) - for _dict in [self.view.plots, self.view.workspaces]: + print(self.view.plots) + for _dict in [self.view.plots, self.view.plot_storage]: self.assertEquals(_dict, {}) self.view._update_gridspec.assert_called_once_with( len(self.view.plots)) diff --git a/scripts/test/Muon/subplotObject_test.py b/scripts/test/Muon/subplotObject_test.py new file mode 100644 index 0000000000000000000000000000000000000000..b0826fed6ccaeeed22f1bdbca112c23cb26e4ae2 --- /dev/null +++ b/scripts/test/Muon/subplotObject_test.py @@ -0,0 +1,141 @@ +import unittest + + +from Muon.GUI.ElementalAnalysis.Plotting.subPlot_object import subPlot +from mantid.simpleapi import * +from mantid import plots + +from matplotlib.figure import Figure + + +class subplotTest(unittest.TestCase): + + def setUp(self): + self.subplot = subPlot("unit test") + + def test_basicAddLine(self): + # can use dummy line here + line = ["a", "test"] + ws = CreateWorkspace(DataX=[1, 2, 3, 4], DataY=[4, 5, 6, 7], NSpec=2) + label = "label" + # add line + self.subplot.addLine(label, line, ws) + # get output + out = self.subplot.lines + self.assertEqual(len(out), 1) + self.assertEqual(out[label], line) + self.assertEqual(self.subplot.specNum[label], 1) + stored = self.subplot.ws + self.assertEqual(stored[ws], [label]) + + DeleteWorkspace(ws) + + def test_addTwoLines(self): + # can use dummy lines here + line1 = ["a", "unit"] + line2 = ["b", "test"] + ws = CreateWorkspace(DataX=[1, 2, 3, 4], DataY=[4, 5, 6, 7], NSpec=2) + label1 = "label1" + label2 = "label2" + # add lines - share a ws + self.subplot.addLine(label1, line1, ws, 1) + self.subplot.addLine(label2, line2, ws, 2) + # get output + out = self.subplot.lines + + self.assertEqual(len(out), 2) + self.assertEqual(out[label1], line1) + self.assertEqual(self.subplot.specNum[label1], 1) + + self.assertEqual(out[label2], line2) + self.assertEqual(self.subplot.specNum[label2], 2) + # since both use same ws, should have 2 labels + stored = self.subplot.ws + self.assertEqual(stored[ws], [label1, label2]) + DeleteWorkspace(ws) + + def test_addTwoLinesDiff(self): + # can use dummy lines here + line1 = ["a", "unit"] + line2 = ["b", "test"] + ws1 = CreateWorkspace(DataX=[1, 2, 3, 4], DataY=[4, 5, 6, 7], NSpec=2) + ws2 = CreateWorkspace(DataX=[1, 2, 3, 4], DataY=[4, 5, 6, 7], NSpec=1) + label1 = "label1" + label2 = "label2" + # add lines + self.subplot.addLine(label1, line1, ws1, 2) + self.subplot.addLine(label2, line2, ws2, 1) + # get output + out = self.subplot.lines + # should have both lines + self.assertEqual(len(out), 2) + self.assertEqual(out[label1], line1) + self.assertEqual(self.subplot.specNum[label1], 2) + + self.assertEqual(out[label2], line2) + self.assertEqual(self.subplot.specNum[label2], 1) + # each ws should have 1 label + stored = self.subplot.ws + self.assertEqual(stored[ws1], [label1]) + self.assertEqual(stored[ws2], [label2]) + + DeleteWorkspace(ws1) + DeleteWorkspace(ws2) + + def test_removeLine(self): + ws1 = CreateWorkspace(DataX=[1, 2, 3, 4], DataY=[4, 5, 6, 7], NSpec=2) + ws2 = CreateWorkspace(DataX=[1, 2, 3, 4], DataY=[4, 5, 6, 7], NSpec=1) + label1 = "label1" + label2 = "label2" + # create real lines + fig = Figure() + sub = fig.add_subplot(1, 1, 1) + line1 = plots.plotfunctions.plot(sub, ws1, specNum=1) + line2 = plots.plotfunctions.plot(sub, ws2, specNum=1) + # add them both + self.subplot.addLine(label1, line1, ws1, 2) + self.subplot.addLine(label2, line2, ws2, 1) + # remove one line + self.subplot.removeLine(label2) + # check output + out = self.subplot.lines + # should only have line 1 + self.assertEqual(len(out), 1) + self.assertEqual(out[label1], line1) + self.assertEqual(self.subplot.specNum[label1], 2) + + stored = self.subplot.ws + self.assertEqual(stored[ws1], [label1]) + + DeleteWorkspace(ws1) + DeleteWorkspace(ws2) + + def test_removeLineShare(self): + ws = CreateWorkspace(DataX=[1, 2, 3, 4], DataY=[4, 5, 6, 7], NSpec=2) + label1 = "label1" + label2 = "label2" + # create real lines + fig = Figure() + sub = fig.add_subplot(1, 1, 1) + line1 = plots.plotfunctions.plot(sub, ws, specNum=1) + line2 = plots.plotfunctions.plot(sub, ws, specNum=2) + # add them both + self.subplot.addLine(label1, line1, ws, 1) + self.subplot.addLine(label2, line2, ws, 2) + # remove one line + self.subplot.removeLine(label2) + # check output + out = self.subplot.lines + # should only have 1 + self.assertEqual(len(out), 1) + self.assertEqual(out[label1], line1) + self.assertEqual(self.subplot.specNum[label1], 1) + # only have 1 label for ws + stored = self.subplot.ws + self.assertEqual(stored[ws], [label1]) + + DeleteWorkspace(ws) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/test/SANS/gui_logic/CMakeLists.txt b/scripts/test/SANS/gui_logic/CMakeLists.txt index 729a9b8683afe9d9dc4c03d41d72c91bcc79f4ff..0913dfbab32758925fa38ee929756e7a3f3f409d 100644 --- a/scripts/test/SANS/gui_logic/CMakeLists.txt +++ b/scripts/test/SANS/gui_logic/CMakeLists.txt @@ -23,6 +23,7 @@ set ( TEST_PY_FILES diagnostics_page_model_test.py create_state_test.py batch_process_runner_test.py + create_file_information_test.py ) check_tests_valid ( ${CMAKE_CURRENT_SOURCE_DIR} ${TEST_PY_FILES} ) diff --git a/scripts/test/SANS/gui_logic/beam_centre_model_test.py b/scripts/test/SANS/gui_logic/beam_centre_model_test.py index b9f2f41c4525b9e31f06b93088ab08c0e62558ec..1050c26e989a9be300e732c5bcf5fe19d6090ba8 100644 --- a/scripts/test/SANS/gui_logic/beam_centre_model_test.py +++ b/scripts/test/SANS/gui_logic/beam_centre_model_test.py @@ -56,26 +56,16 @@ class BeamCentreModelTest(unittest.TestCase): self.assertEqual(self.beam_centre_model.scale_2, 1.0) def test_that_correct_values_are_set_for_LARMOR(self): - facility = SANSFacility.ISIS file_information = SANSFileInformationMock(run_number=2260, instrument=SANSInstrument.LARMOR) - data_builder = get_data_builder(facility, file_information) - data_builder.set_sample_scatter("LARMOR00002260") - data_state = data_builder.build() - - self.beam_centre_model.reset_to_defaults_for_instrument(data_state) + self.beam_centre_model.reset_to_defaults_for_instrument(file_information) self.assertEqual(self.beam_centre_model.scale_1, 1.0) def test_that_correct_values_are_set_for_LOQ(self): - facility = SANSFacility.ISIS file_information = SANSFileInformationMock(run_number=74044, instrument=SANSInstrument.LOQ) - data_builder = get_data_builder(facility, file_information) - data_builder.set_sample_scatter("LOQ74044") - data_state = data_builder.build() - - self.beam_centre_model.reset_to_defaults_for_instrument(data_state) + self.beam_centre_model.reset_to_defaults_for_instrument(file_information) self.assertEqual(self.beam_centre_model.r_max, 200) diff --git a/scripts/test/SANS/gui_logic/create_file_information_test.py b/scripts/test/SANS/gui_logic/create_file_information_test.py new file mode 100644 index 0000000000000000000000000000000000000000..33c9c255d65b768b58a0cc834953869131b8e4d9 --- /dev/null +++ b/scripts/test/SANS/gui_logic/create_file_information_test.py @@ -0,0 +1,75 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +from sans.gui_logic.presenter.create_file_information import create_file_information +from ui.sans_isis.work_handler import WorkHandler +import sys +import unittest +from PyQt4.QtCore import QCoreApplication + +if sys.version_info.major == 3: + from unittest import mock +else: + import mock + + +class CreateFileInformationTest(unittest.TestCase): + def setUp(self): + self.success_callback = mock.MagicMock() + self.success_callback_1 = mock.MagicMock() + self.error_callback = mock.MagicMock() + self.work_handler = WorkHandler() + self.qApp = QCoreApplication(['test_app']) + + def test_retieves_file_information_and_passes_to_callback(self): + create_file_information('LOQ74044', self.error_callback, self.success_callback, self.work_handler, 0) + self.work_handler.wait_for_done() + self.qApp.processEvents() + + self.assertEqual(self.success_callback.call_count, 1) + self.assertEqual(self.error_callback.call_count, 0) + + def test_that_retrieved_file_information_is_correct(self): + create_file_information('LOQ74044', self.error_callback, self.success_callback, self.work_handler, 0) + self.work_handler.wait_for_done() + self.qApp.processEvents() + + file_information = self.success_callback.call_args[0][0] + self.assertEqual(file_information.is_event_mode(), False) + self.assertEqual(file_information.get_run_number(), 74044) + self.assertEqual(file_information.get_thickness(), 1.0) + + def test_that_multiple_threading_calls_at_once_are_handled_cleanly(self): + create_file_information('LOQ74044', self.error_callback, self.success_callback, self.work_handler, 0) + create_file_information('LOQ74044', self.error_callback, self.success_callback_1, self.work_handler, 0) + create_file_information('LOQ74044', self.error_callback, self.success_callback_1, self.work_handler, 1) + create_file_information('LOQ74044', self.error_callback, self.success_callback_1, self.work_handler, 0) + create_file_information('LOQ74044', self.error_callback, self.success_callback_1, self.work_handler, 2) + self.work_handler.wait_for_done() + self.qApp.processEvents() + + self.assertEqual(self.success_callback.call_count, 0) + self.assertEqual(self.success_callback_1.call_count, 3) + self.assertEqual(self.error_callback.call_count, 0) + + @mock.patch('sans.gui_logic.presenter.create_file_information.SANSFileInformationFactory') + def test_that_error_callback_is_called_on_error_with_correct_message(self, file_information_factory_mock): + file_information_factory_instance = mock.MagicMock() + file_information_factory_instance.create_sans_file_information.side_effect = RuntimeError('File Error') + file_information_factory_mock.return_value = file_information_factory_instance + + create_file_information('LOQ74044', self.error_callback, self.success_callback, self.work_handler, 0) + self.work_handler.wait_for_done() + self.qApp.processEvents() + + self.success_callback.assert_called_once_with(None) + self.assertEqual(self.error_callback.call_count, 1) + self.assertEqual(str(self.error_callback.call_args[0][0][1]), 'File Error') + + +if __name__ == '__main__': + unittest.main() diff --git a/scripts/test/SANS/gui_logic/create_state_test.py b/scripts/test/SANS/gui_logic/create_state_test.py index b698906dc6a188bab753e86c35eae471fe0af3de..805c5d298f55841946ec8da9d30d01041175e999 100644 --- a/scripts/test/SANS/gui_logic/create_state_test.py +++ b/scripts/test/SANS/gui_logic/create_state_test.py @@ -15,6 +15,7 @@ from sans.common.enums import (SANSInstrument, ISISReductionMode, SANSFacility, from sans.gui_logic.models.state_gui_model import StateGuiModel from sans.gui_logic.models.table_model import TableModel, TableIndexModel from sans.state.state import State +from PyQt4.QtCore import QCoreApplication if sys.version_info.major == 3: from unittest import mock @@ -23,12 +24,15 @@ else: class GuiCommonTest(unittest.TestCase): def setUp(self): + self.qApp = QCoreApplication(['test_app']) self.table_model = TableModel() self.state_gui_model = StateGuiModel({}) table_index_model_0 = TableIndexModel('LOQ74044', '', '', '', '', '', '', '', '', '', '', '') table_index_model_1 = TableIndexModel('LOQ74044', '', '', '', '', '', '', '', '', '', '', '') self.table_model.add_table_entry(0, table_index_model_0) self.table_model.add_table_entry(1, table_index_model_1) + self.table_model.wait_for_file_finding_done() + self.qApp.processEvents() self.fake_state = mock.MagicMock(spec=State) self.gui_state_director_instance = mock.MagicMock() @@ -54,6 +58,8 @@ class GuiCommonTest(unittest.TestCase): def test_skips_empty_rows(self): table_index_model = TableIndexModel('', '', '', '', '', '', '', '', '', '', '', '') self.table_model.add_table_entry(1, table_index_model) + self.table_model.wait_for_file_finding_done() + self.qApp.processEvents() states, errors = create_states(self.state_gui_model, self.table_model, SANSInstrument.LOQ, SANSFacility.ISIS, row_index=[0,1, 2]) @@ -67,6 +73,8 @@ class GuiCommonTest(unittest.TestCase): user_file='MaskLOQData.txt') table_model = TableModel() table_model.add_table_entry(0, table_index_model) + table_model.wait_for_file_finding_done() + self.qApp.processEvents() states, errors = create_states(self.state_gui_model, table_model, SANSInstrument.LOQ, SANSFacility.ISIS, row_index=[0,1, 2]) diff --git a/scripts/test/SANS/gui_logic/diagnostics_page_presenter_test.py b/scripts/test/SANS/gui_logic/diagnostics_page_presenter_test.py index dc86db774b1d3927342523090af2bbae297d0f9a..593766b219aceb7b427f66b4d7ed22c2b3a76310 100644 --- a/scripts/test/SANS/gui_logic/diagnostics_page_presenter_test.py +++ b/scripts/test/SANS/gui_logic/diagnostics_page_presenter_test.py @@ -35,33 +35,33 @@ class DiagnosticsPagePresenterTest(unittest.TestCase): self.assertEqual(self.presenter._work_handler.process.call_count, 1) self.assertEqual(self.presenter._work_handler.process.call_args[0][1], self.run_integral) - self.assertEqual(self.presenter._work_handler.process.call_args[0][2], self.presenter._view.horizontal_range) - self.assertEqual(self.presenter._work_handler.process.call_args[0][3], self.presenter._view.horizontal_mask) - self.assertEqual(self.presenter._work_handler.process.call_args[0][4], IntegralEnum.Horizontal) - self.assertEqual(self.presenter._work_handler.process.call_args[0][5], DetectorType.LAB) - self.assertEqual(self.presenter._work_handler.process.call_args[0][6], self.state) + self.assertEqual(self.presenter._work_handler.process.call_args[0][3], self.presenter._view.horizontal_range) + self.assertEqual(self.presenter._work_handler.process.call_args[0][4], self.presenter._view.horizontal_mask) + self.assertEqual(self.presenter._work_handler.process.call_args[0][5], IntegralEnum.Horizontal) + self.assertEqual(self.presenter._work_handler.process.call_args[0][6], DetectorType.LAB) + self.assertEqual(self.presenter._work_handler.process.call_args[0][7], self.state) def test_that_on_vertical_clicked_calls_work_handler_with_correct_parameters(self): self.presenter.on_vertical_clicked() self.assertEqual(self.presenter._work_handler.process.call_count, 1) self.assertEqual(self.presenter._work_handler.process.call_args[0][1], self.run_integral) - self.assertEqual(self.presenter._work_handler.process.call_args[0][2], self.presenter._view.vertical_range) - self.assertEqual(self.presenter._work_handler.process.call_args[0][3], self.presenter._view.vertical_mask) - self.assertEqual(self.presenter._work_handler.process.call_args[0][4], IntegralEnum.Vertical) - self.assertEqual(self.presenter._work_handler.process.call_args[0][5], DetectorType.LAB) - self.assertEqual(self.presenter._work_handler.process.call_args[0][6], self.state) + self.assertEqual(self.presenter._work_handler.process.call_args[0][3], self.presenter._view.vertical_range) + self.assertEqual(self.presenter._work_handler.process.call_args[0][4], self.presenter._view.vertical_mask) + self.assertEqual(self.presenter._work_handler.process.call_args[0][5], IntegralEnum.Vertical) + self.assertEqual(self.presenter._work_handler.process.call_args[0][6], DetectorType.LAB) + self.assertEqual(self.presenter._work_handler.process.call_args[0][7], self.state) def test_that_on_time_clicked_calls_work_handler_with_correct_parameters(self): self.presenter.on_time_clicked() self.assertEqual(self.presenter._work_handler.process.call_count, 1) self.assertEqual(self.presenter._work_handler.process.call_args[0][1], self.run_integral) - self.assertEqual(self.presenter._work_handler.process.call_args[0][2], self.presenter._view.time_range) - self.assertEqual(self.presenter._work_handler.process.call_args[0][3], self.presenter._view.time_mask) - self.assertEqual(self.presenter._work_handler.process.call_args[0][4], IntegralEnum.Time) - self.assertEqual(self.presenter._work_handler.process.call_args[0][5], DetectorType.LAB) - self.assertEqual(self.presenter._work_handler.process.call_args[0][6], self.state) + self.assertEqual(self.presenter._work_handler.process.call_args[0][3], self.presenter._view.time_range) + self.assertEqual(self.presenter._work_handler.process.call_args[0][4], self.presenter._view.time_mask) + self.assertEqual(self.presenter._work_handler.process.call_args[0][5], IntegralEnum.Time) + self.assertEqual(self.presenter._work_handler.process.call_args[0][6], DetectorType.LAB) + self.assertEqual(self.presenter._work_handler.process.call_args[0][7], self.state) def test_that_on_user_file_load_sets_user_file_name_on_view(self): user_file_name = 'user_file_name' diff --git a/scripts/test/SANS/gui_logic/gui_state_director_test.py b/scripts/test/SANS/gui_logic/gui_state_director_test.py index e462b23b75bfd2f2715d548b2178739ebb422a72..ee97977ef15451b1a679269db13816abc2a432d6 100644 --- a/scripts/test/SANS/gui_logic/gui_state_director_test.py +++ b/scripts/test/SANS/gui_logic/gui_state_director_test.py @@ -25,7 +25,7 @@ class GuiStateDirectorTest(unittest.TestCase): "", "", "",options_column_string=option_string, sample_thickness=sample_thickness) table_model = TableModel() - table_model.add_table_entry(0, table_index_model) + table_model.add_table_entry_no_thread_or_signal(0, table_index_model) return table_model @staticmethod @@ -72,28 +72,14 @@ class GuiStateDirectorTest(unittest.TestCase): self.assertTrue(state.wavelength.wavelength_high == [10.3]) def test_that_sample_thickness_set_on_state(self): - table_model = self._get_table_model(sample_thickness = 78.0) - state_model = self._get_state_gui_model() - director = GuiStateDirector(table_model, state_model, SANSFacility.ISIS) - - state = director.create_state(0) - self.assertTrue(isinstance(state, State)) - - self.assertEqual(state.scale.thickness, 78.0) - - def test_state_created_with_default_sample_thickness_when_file_lookup_disabled(self): table_model = self._get_table_model() state_model = self._get_state_gui_model() director = GuiStateDirector(table_model, state_model, SANSFacility.ISIS) - state = director.create_state(0, file_lookup=False) + state = director.create_state(0) self.assertTrue(isinstance(state, State)) - self.assertEqual(state.scale.thickness_from_file, 1.0) - self.assertEqual(state.scale.height_from_file, 8.0) - self.assertEqual(state.scale.width_from_file, 8.0) - self.assertEqual(state.scale.thickness, 8.0) - + self.assertEqual(state.scale.thickness, 1.0) if __name__ == '__main__': unittest.main() diff --git a/scripts/test/SANS/gui_logic/run_tab_presenter_test.py b/scripts/test/SANS/gui_logic/run_tab_presenter_test.py index b701b3b711db47a91ed6e796123431564a93b05e..4a303877cc305abf75bb34b65d96eea01ce9e83f 100644 --- a/scripts/test/SANS/gui_logic/run_tab_presenter_test.py +++ b/scripts/test/SANS/gui_logic/run_tab_presenter_test.py @@ -21,7 +21,7 @@ from sans.test_helper.mock_objects import (create_mock_view) from sans.test_helper.common import (remove_file) from sans.common.enums import BatchReductionEntry from sans.gui_logic.models.table_model import TableModel, TableIndexModel - +from sans.test_helper.file_information_mock import SANSFileInformationMock if sys.version_info.major == 3: from unittest import mock @@ -77,6 +77,10 @@ class RunTabPresenterTest(unittest.TestCase): self.addCleanup(self.os_patcher.stop) self.osMock = self.os_patcher.start() + self.thickness_patcher = mock.patch('sans.gui_logic.models.table_model.create_file_information') + self.addCleanup(self.thickness_patcher.stop) + self.thickness_patcher.start() + def test_that_will_load_user_file(self): # Setup presenter and mock view user_file_path = create_user_file(sample_user_file) @@ -176,17 +180,17 @@ class RunTabPresenterTest(unittest.TestCase): presenter.on_batch_file_load() # Assert - self.assertEqual(view.add_row.call_count, 2) + self.assertEqual(view.add_row.call_count, 8) if use_multi_period: expected_first_row = ['SANS2D00022024', '', 'SANS2D00022048', '', 'SANS2D00022048', '', '', '', '', '', '', - '', 'test_file', '', '1.0', ''] + '', 'test_file', '', '', '', '', '', ''] expected_second_row = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', - '1.0', ''] + '', '', '', '', ''] else: expected_first_row = ['SANS2D00022024', '', 'SANS2D00022048', '', 'SANS2D00022048', '', '', '', '', '', '', - '', 'test_file', '', '1.0', ''] - expected_second_row = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '1.0' - , ''] + '', 'test_file', '', '', '', '', '', ''] + expected_second_row = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '' + , '', '', '', ''] calls = [mock.call(expected_first_row), mock.call(expected_second_row)] view.add_row.assert_has_calls(calls) @@ -216,10 +220,11 @@ class RunTabPresenterTest(unittest.TestCase): presenter.on_batch_file_load() # Assert - self.assertEqual(view.add_row.call_count, 1) - self.assertEqual(view.show_period_columns.call_count, 1) + self.assertEqual(view.add_row.call_count, 4) + self.assertEqual(view.show_period_columns.call_count, 2) - expected_row = ['SANS2D00022024', '3', '', '', '', '', '', '', '', '', '', '', 'test_file', '', '1.0', ''] + expected_row = ['SANS2D00022024', '3', '', '', '', '', '', '', '', '', '', '', 'test_file', '', '', + '', '', '', ''] calls = [mock.call(expected_row)] view.add_row.assert_has_calls(calls) @@ -247,12 +252,14 @@ class RunTabPresenterTest(unittest.TestCase): batch_file_path, user_file_path, presenter, _ = self._get_files_and_mock_presenter(BATCH_FILE_TEST_CONTENT_2) presenter.on_user_file_load() presenter.on_batch_file_load() + presenter._table_model.update_thickness_from_file_information(1, self.get_file_information_mock()) + presenter._table_model.update_thickness_from_file_information(2, self.get_file_information_mock()) # Act states, errors = presenter.get_states(row_index=[0, 1]) # Assert - self.assertTrue(len(states) == 2) + self.assertEqual(len(states), 2) for _, state in states.items(): try: state.validate() @@ -296,12 +303,16 @@ class RunTabPresenterTest(unittest.TestCase): row_user_file_path = row_user_file_path) presenter.on_user_file_load() presenter.on_batch_file_load() + presenter._table_model.update_thickness_from_file_information(1, self.get_file_information_mock()) + presenter._table_model.update_thickness_from_file_information(2, self.get_file_information_mock()) + + # Act state = presenter.get_state_for_row(1) state0 = presenter.get_state_for_row(0) # Assert - self.assertTrue(state.convert_to_q.use_gravity is False) - self.assertTrue(state0.convert_to_q.use_gravity is True) + self.assertFalse(state.convert_to_q.use_gravity) + self.assertTrue(state0.convert_to_q.use_gravity) def test_that_can_get_state_for_index_if_index_exists(self): # Arrange @@ -309,6 +320,8 @@ class RunTabPresenterTest(unittest.TestCase): presenter.on_user_file_load() presenter.on_batch_file_load() + presenter._table_model.update_thickness_from_file_information(1, self.get_file_information_mock()) + presenter._table_model.update_thickness_from_file_information(2, self.get_file_information_mock()) # Act state = presenter.get_state_for_row(1) @@ -349,6 +362,8 @@ class RunTabPresenterTest(unittest.TestCase): presenter.on_user_file_load() presenter.on_batch_file_load() + presenter._table_model.update_thickness_from_file_information(1, self.get_file_information_mock()) + presenter._table_model.update_thickness_from_file_information(2, self.get_file_information_mock()) # Act presenter.on_mask_file_add() @@ -365,19 +380,25 @@ class RunTabPresenterTest(unittest.TestCase): def test_on_data_changed_calls_update_rows(self): batch_file_path, user_file_path, presenter, _ = self._get_files_and_mock_presenter(BATCH_FILE_TEST_CONTENT_1) presenter._masking_table_presenter = mock.MagicMock() + presenter._table_model.subscribe_to_model_changes(presenter._masking_table_presenter) presenter._beam_centre_presenter = mock.MagicMock() + presenter._table_model.subscribe_to_model_changes(presenter._beam_centre_presenter) row_entry = [''] * 16 presenter.on_row_inserted(0, row_entry) presenter.on_data_changed(0, 0, '12335', '00000') - presenter._masking_table_presenter.on_update_rows.assert_called_once_with() - presenter._beam_centre_presenter.on_update_rows.assert_called_once_with() + presenter._masking_table_presenter.on_update_rows.assert_called_with() + presenter._beam_centre_presenter.on_update_rows.assert_called_with() def test_table_model_is_initialised_upon_presenter_creation(self): presenter = RunTabPresenter(SANSFacility.ISIS) - - self.assertEqual(presenter._table_model, TableModel()) + expected_table_model = TableModel() + expected_table_model.subscribe_to_model_changes(presenter) + expected_table_model.subscribe_to_model_changes(presenter._masking_table_presenter) + expected_table_model.subscribe_to_model_changes(presenter._beam_centre_presenter) + self.maxDiff = None + self.assertEqual(presenter._table_model, expected_table_model) def test_on_insert_row_updates_table_model(self): presenter = RunTabPresenter(SANSFacility.ISIS) @@ -385,12 +406,15 @@ class RunTabPresenterTest(unittest.TestCase): row = ['74044', '', '74044', '', '74044', '', '74044', '', '74044', '', '74044', '', 'test_reduction' , 'user_file', '1.2', ''] index = 0 + expected_table_index_model = TableIndexModel(*row) + expected_table_index_model.id = 0 + expected_table_index_model.file_finding = True presenter.on_row_inserted(index, row) self.assertEqual(presenter._table_model.get_number_of_rows(), 1) model_row = presenter._table_model.get_table_entry(0) - self.assertEqual(model_row, TableIndexModel(*row)) + self.assertEqual(model_row, expected_table_index_model) def test_that_all_columns_shown_when_multi_period_is_true(self): presenter = RunTabPresenter(SANSFacility.ISIS) @@ -419,12 +443,15 @@ class RunTabPresenterTest(unittest.TestCase): row = 0 column = 2 value = '74040' + expected_table_index_model = TableIndexModel(*expected_row) + expected_table_index_model.id = 0 + expected_table_index_model.file_finding = True presenter.on_data_changed(row, column, value, '') self.assertEqual(presenter._table_model.get_number_of_rows(), 1) model_row = presenter._table_model.get_table_entry(0) - self.assertEqual(model_row, TableIndexModel(*expected_row)) + self.assertEqual(model_row, expected_table_index_model) def test_on_row_removed_removes_correct_row(self): presenter = RunTabPresenter(SANSFacility.ISIS) @@ -442,14 +469,21 @@ class RunTabPresenterTest(unittest.TestCase): presenter._table_model.add_table_entry(2, TableIndexModel(*row_2)) presenter._table_model.add_table_entry(3, TableIndexModel(*row_3)) rows = [0, 2] + expected_row_0 = TableIndexModel(*row_1) + expected_row_0.id = 1 + expected_row_0.file_finding = True + expected_row_1 = TableIndexModel(*row_3) + expected_row_1.id = 3 + expected_row_1.file_finding = True + presenter.on_rows_removed(rows) self.assertEqual(presenter._table_model.get_number_of_rows(), 2) model_row_0 = presenter._table_model.get_table_entry(0) - self.assertEqual(model_row_0, TableIndexModel(*row_1)) + self.assertEqual(model_row_0, expected_row_0) model_row_1 = presenter._table_model.get_table_entry(1) - self.assertEqual(model_row_1, TableIndexModel(*row_3)) + self.assertEqual(model_row_1, expected_row_1) def test_on_rows_removed_updates_view(self): presenter = RunTabPresenter(SANSFacility.ISIS) @@ -484,7 +518,7 @@ class RunTabPresenterTest(unittest.TestCase): table_entry_0 = presenter._table_model.get_table_entry(0) self.assertEqual(table_entry_0.output_name, 'test_file') self.assertEqual(table_entry_0.sample_scatter, 'SANS2D00022024') - self.assertEqual(table_entry_0.sample_thickness, 1.0) + self.assertEqual(table_entry_0.sample_thickness, '') table_entry_1 = presenter._table_model.get_table_entry(1) self.assertEqual(table_entry_1.output_name, 'test_file2') @@ -496,15 +530,16 @@ class RunTabPresenterTest(unittest.TestCase): for item, row in enumerate(parsed_data): presenter._add_row_to_table_model(row, item) expected_call_0 = ['SANS2D00022024', '', 'SANS2D00022048', '', 'SANS2D00022048', '', '', '', '', '', '', '', - 'test_file', '', '1.0', ''] - expected_call_1 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '1.0', ''] + 'test_file', '', '', '', '', '', ''] + expected_call_1 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '', '', + '', '', ''] presenter.update_view_from_table_model() - self.assertEqual(presenter._view.add_row.call_count, 2) + self.assertEqual(presenter._view.add_row.call_count, 5) - presenter._view.add_row.called_with(expected_call_0) - presenter._view.add_row.called_with(expected_call_1) + presenter._view.add_row.assert_any_call(expected_call_0) + presenter._view.add_row.assert_any_call(expected_call_1) def test_setup_instrument_specific_settings(self): presenter = RunTabPresenter(SANSFacility.ISIS) @@ -525,7 +560,7 @@ class RunTabPresenterTest(unittest.TestCase): view.get_selected_rows = mock.MagicMock(return_value=[0]) presenter.set_view(view) test_row = ['SANS2D00022024', '', 'SANS2D00022048', '', 'SANS2D00022048', '', '', '', '', '', '', '', - 'test_file', '', '1.0', ''] + 'test_file', '', '1.0', '', '', '', ''] presenter.on_row_inserted(0, test_row) @@ -539,8 +574,9 @@ class RunTabPresenterTest(unittest.TestCase): view.get_selected_rows = mock.MagicMock(return_value=[]) presenter.set_view(view) test_row_0 = ['SANS2D00022024', '', 'SANS2D00022048', '', 'SANS2D00022048', '', '', '', '', '', '', '', - 'test_file', '', '1.0', ''] - test_row_1 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '1.0', ''] + 'test_file', '', '1.0', '', '', '', ''] + test_row_1 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '1.0', '', + '', '', ''] presenter.on_row_inserted(0, test_row_0) presenter.on_row_inserted(1, test_row_1) presenter._clipboard = [test_row_0] @@ -556,8 +592,9 @@ class RunTabPresenterTest(unittest.TestCase): view.get_selected_rows = mock.MagicMock(return_value=[1]) presenter.set_view(view) test_row_0 = ['SANS2D00022024', '', 'SANS2D00022048', '', 'SANS2D00022048', '', '', '', '', '', '', '', - 'test_file', '', '1.0', ''] - test_row_1 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '1.0', ''] + 'test_file', '', '1.0', '', '', '', ''] + test_row_1 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '1.0', '', + '', '', ''] presenter.on_row_inserted(0, test_row_0) presenter.on_row_inserted(1, test_row_1) presenter.on_row_inserted(2, test_row_0) @@ -574,9 +611,11 @@ class RunTabPresenterTest(unittest.TestCase): view.get_selected_rows = mock.MagicMock(return_value=[0, 2]) presenter.set_view(view) test_row_0 = ['SANS2D00022024', '', 'SANS2D00022048', '', 'SANS2D00022048', '', '', '', '', '', '', '', - 'test_file', '', '1.0', ''] - test_row_1 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '1.0', ''] - test_row_2 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file3', '', '1.0', ''] + 'test_file', '', '1.0', '', '', '', ''] + test_row_1 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '1.0', '', + '', '', ''] + test_row_2 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file3', '', '1.0', '', + '', '', ''] presenter.on_row_inserted(0, test_row_0) presenter.on_row_inserted(1, test_row_1) presenter.on_row_inserted(2, test_row_2) @@ -603,7 +642,7 @@ class RunTabPresenterTest(unittest.TestCase): presenter.on_paste_rows_requested() - presenter.update_view_from_table_model.assert_called_once_with() + presenter.update_view_from_table_model.assert_called_with() def test_on_insert_row_adds_row_to_table_model_after_selected_row(self): presenter = RunTabPresenter(SANSFacility.ISIS) @@ -611,15 +650,16 @@ class RunTabPresenterTest(unittest.TestCase): view.get_selected_rows = mock.MagicMock(return_value=[0]) presenter.set_view(view) test_row_0 = ['SANS2D00022024', '', 'SANS2D00022048', '', 'SANS2D00022048', '', '', '', '', '', '', '', - 'test_file', '', '1.0', ''] - test_row_1 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '1.0', ''] + 'test_file', '', '1.0', '', '', '', ''] + test_row_1 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '1.0', '', + '', '', ''] presenter.on_row_inserted(0, test_row_0) presenter.on_row_inserted(1, test_row_1) presenter.on_insert_row() self.assertEqual(presenter._table_model.get_number_of_rows(), 3) - self.assertEqual(presenter._table_model.get_table_entry(1).to_list(), [''] * 16) + self.assertEqual(presenter._table_model.get_table_entry(1).to_list(), [''] * 19) self.assertEqual(presenter._table_model.get_table_entry(0).to_list(), test_row_0) self.assertEqual(presenter._table_model.get_table_entry(2).to_list(), test_row_1) @@ -629,8 +669,9 @@ class RunTabPresenterTest(unittest.TestCase): view.get_selected_rows = mock.MagicMock(return_value=[0]) presenter.set_view(view) test_row_0 = ['SANS2D00022024', '', 'SANS2D00022048', '', 'SANS2D00022048', '', '', '', '', '', '', '', - 'test_file', '', '1.0', ''] - test_row_1 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '1.0', ''] + 'test_file', '', '1.0', '', '', '', ''] + test_row_1 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '1.0', + '', '', '', ''] presenter.on_row_inserted(0, test_row_0) presenter.on_row_inserted(1, test_row_1) presenter.update_view_from_table_model = mock.MagicMock() @@ -645,8 +686,9 @@ class RunTabPresenterTest(unittest.TestCase): view.get_selected_rows = mock.MagicMock(return_value=[1, 2]) presenter.set_view(view) test_row_0 = ['SANS2D00022024', '', 'SANS2D00022048', '', 'SANS2D00022048', '', '', '', '', '', '', '', - 'test_file', '', '1.0', ''] + 'test_file', '', '1.0', '', '', '', ''] empty_row = TableModel.create_empty_row() + presenter.on_row_inserted(0, test_row_0) presenter.on_row_inserted(1, test_row_0) presenter.on_row_inserted(2, test_row_0) @@ -655,8 +697,10 @@ class RunTabPresenterTest(unittest.TestCase): self.assertEqual(presenter._table_model.get_number_of_rows(), 3) self.assertEqual(presenter._table_model.get_table_entry(0).to_list(), test_row_0) - self.assertEqual(presenter._table_model.get_table_entry(1), empty_row) - self.assertEqual(presenter._table_model.get_table_entry(2), empty_row) + empty_row.id = 3 + self.assertEqual(presenter._table_model.get_table_entry(1).__dict__, empty_row.__dict__) + empty_row.id = 4 + self.assertEqual(presenter._table_model.get_table_entry(2).__dict__, empty_row.__dict__) def test_on_erase_rows_updates_view(self): presenter = RunTabPresenter(SANSFacility.ISIS) @@ -664,7 +708,7 @@ class RunTabPresenterTest(unittest.TestCase): view.get_selected_rows = mock.MagicMock(return_value=[1, 2]) presenter.set_view(view) test_row_0 = ['SANS2D00022024', '', 'SANS2D00022048', '', 'SANS2D00022048', '', '', '', '', '', '', '', - 'test_file', '', '1.0', ''] + 'test_file', '', '1.0', '', '', '', ''] presenter.on_row_inserted(0, test_row_0) presenter.on_row_inserted(1, test_row_0) presenter.on_row_inserted(2, test_row_0) @@ -672,7 +716,15 @@ class RunTabPresenterTest(unittest.TestCase): presenter.on_erase_rows() - presenter.update_view_from_table_model.assert_called_once_with() + self.assertEqual(presenter._table_model._table_entries[0].to_list(), + ['SANS2D00022024', '', 'SANS2D00022048', '', 'SANS2D00022048', '', '', '', '', '', '', '', + 'test_file', '', '1.0', '', '', '', '']) + self.assertEqual(presenter._table_model._table_entries[1].to_list(), + ['', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '']) + self.assertEqual(presenter._table_model._table_entries[2].to_list(), + ['', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '']) def test_on_cut_rows_requested_updates_clipboard(self): presenter = RunTabPresenter(SANSFacility.ISIS) @@ -680,7 +732,7 @@ class RunTabPresenterTest(unittest.TestCase): view.get_selected_rows = mock.MagicMock(return_value=[0]) presenter.set_view(view) test_row = ['SANS2D00022024', '', 'SANS2D00022048', '', 'SANS2D00022048', '', '', '', '', '', '', '', - 'test_file', '', '1.0', ''] + 'test_file', '', '1.0', '', '', '', ''] presenter.on_row_inserted(0, test_row) @@ -694,8 +746,9 @@ class RunTabPresenterTest(unittest.TestCase): view.get_selected_rows = mock.MagicMock(return_value=[0]) presenter.set_view(view) test_row_0 = ['SANS2D00022024', '', 'SANS2D00022048', '', 'SANS2D00022048', '', '', '', '', '', '', '', - 'test_file', '', '1.0', ''] - test_row_1 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '1.0', ''] + 'test_file', '', '1.0', '', '', '', ''] + test_row_1 = ['SANS2D00022024', '', '', '', '', '', '', '', '', '', '', '', 'test_file2', '', '1.0', + '', '', '', ''] presenter.on_row_inserted(0, test_row_0) presenter.on_row_inserted(1, test_row_1) @@ -757,6 +810,9 @@ class RunTabPresenterTest(unittest.TestCase): if batch_file_path: remove_file(batch_file_path) + @staticmethod + def get_file_information_mock(): + return SANSFileInformationMock(instrument=SANSInstrument.SANS2D) if __name__ == '__main__': unittest.main() diff --git a/scripts/test/SANS/gui_logic/table_model_test.py b/scripts/test/SANS/gui_logic/table_model_test.py index 92f3eb1bbabadc61f7832f888a82eadd616bf140..f48de1999b33de9c30f06e4a8ba25543dc0840ed 100644 --- a/scripts/test/SANS/gui_logic/table_model_test.py +++ b/scripts/test/SANS/gui_logic/table_model_test.py @@ -10,9 +10,20 @@ import unittest from sans.gui_logic.models.table_model import (TableModel, TableIndexModel, OptionsColumnModel) from sans.gui_logic.models.basic_hint_strategy import BasicHintStrategy +from PyQt4.QtCore import QCoreApplication +from sans.common.enums import RowState +try: + from unittest import mock +except: + import mock class TableModelTest(unittest.TestCase): + def setUp(self): + self.thickness_patcher = mock.patch('sans.gui_logic.models.table_model.create_file_information') + self.addCleanup(self.thickness_patcher.stop) + self.thickness_patcher.start() + def test_user_file_can_be_set(self): self._do_test_file_setting(self._user_file_wrapper, "user_file") @@ -35,9 +46,9 @@ class TableModelTest(unittest.TestCase): self.assertTrue(returned_model.sample_scatter == '') def test_that_can_set_the_options_column_model(self): - table_index_model = TableIndexModel(0, "", "", "", "", "", "", + table_index_model = TableIndexModel('0', "", "", "", "", "", "", "", "", "", "", "", "", "", "", - "WavelengthMin=1, WavelengthMax=3, NotRegister2=1") + options_column_string="WavelengthMin=1, WavelengthMax=3, NotRegister2=1") options_column_model = table_index_model.options_column_model options = options_column_model.get_options() self.assertTrue(len(options) == 2) @@ -45,9 +56,9 @@ class TableModelTest(unittest.TestCase): self.assertTrue(options["WavelengthMax"] == 3.) def test_that_raises_for_missing_equal(self): - args = [0, "", "", "", "", "", "", "", "", "", "", "", "", "", "", - "WavelengthMin=1, WavelengthMax=3, NotRegister2"] - self.assertRaises(ValueError, TableIndexModel, *args) + args = [0, "", "", "", "", "", "", "", "", "", "", "", "", "", ""] + kwargs = {'options_column_string': "WavelengthMin=1, WavelengthMax=3, NotRegister2"} + self.assertRaises(ValueError, TableIndexModel, *args, **kwargs) def test_that_querying_nonexistent_row_index_raises_IndexError_exception(self): table_model = TableModel() @@ -56,7 +67,7 @@ class TableModelTest(unittest.TestCase): def test_that_can_retrieve_user_file_from_table_index_model(self): table_model = TableModel() - table_index_model = TableIndexModel(2, "", "", "", "", "", "", + table_index_model = TableIndexModel('2', "", "", "", "", "", "", "", "", "", "", "", "", "User_file_name") table_model.add_table_entry(2, table_index_model) user_file = table_model.get_row_user_file(0) @@ -81,10 +92,10 @@ class TableModelTest(unittest.TestCase): def test_get_number_of_rows_returns_number_of_entries(self): table_model = TableModel() - table_index_model = TableIndexModel(0, "", "", "", "", "", "", + table_index_model = TableIndexModel('0', "", "", "", "", "", "", "", "", "", "", "", "") table_model.add_table_entry(0, table_index_model) - table_index_model = TableIndexModel(1, "", "", "", "", "", "", + table_index_model = TableIndexModel('1', "", "", "", "", "", "", "", "", "", "", "", "") table_model.add_table_entry(1, table_index_model) @@ -94,13 +105,14 @@ class TableModelTest(unittest.TestCase): def test_when_table_is_cleared_is_left_with_one_empty_row(self): table_model = TableModel() - table_index_model = TableIndexModel(0, "", "", "", "", "", "", + table_index_model = TableIndexModel('0', "", "", "", "", "", "", "", "", "", "", "", "") table_model.add_table_entry(0, table_index_model) - table_index_model = TableIndexModel(1, "", "", "", "", "", "", + table_index_model = TableIndexModel('1', "", "", "", "", "", "", "", "", "", "", "", "") table_model.add_table_entry(1, table_index_model) empty_row = table_model.create_empty_row() + empty_row.id = 2 table_model.clear_table_entries() @@ -109,13 +121,14 @@ class TableModelTest(unittest.TestCase): def test_when_last_row_is_removed_table_is_left_with_one_empty_row(self): table_model = TableModel() - table_index_model = TableIndexModel(0, "", "", "", "", "", "", + table_index_model = TableIndexModel('0', "", "", "", "", "", "", "", "", "", "", "", "") table_model.add_table_entry(0, table_index_model) - table_index_model = TableIndexModel(1, "", "", "", "", "", "", + table_index_model = TableIndexModel('1', "", "", "", "", "", "", "", "", "", "", "", "") table_model.add_table_entry(1, table_index_model) empty_row = table_model.create_empty_row() + empty_row.id = 2 table_model.remove_table_entries([0, 1]) @@ -162,6 +175,41 @@ class TableModelTest(unittest.TestCase): table_model = TableModel() table_model.user_file = value +class TableModelThreadingTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.qApp = QCoreApplication(['test_app']) + + @mock.patch('sans.gui_logic.presenter.create_file_information.SANSFileInformationFactory') + def test_that_get_thickness_for_row_handles_errors_correctly(self, file_information_factory_mock): + # self.thickness_patcher.stop() + file_information_factory_instance = mock.MagicMock() + file_information_factory_instance.create_sans_file_information.side_effect = RuntimeError('File Error') + file_information_factory_mock.return_value = file_information_factory_instance + table_model = TableModel() + table_index_model = TableIndexModel("00000", "", "", "", "", "", "", + "", "", "", "", "", "") + table_model.add_table_entry(0, table_index_model) + + table_model.get_thickness_for_rows() + table_model.work_handler.wait_for_done() + self.qApp.processEvents() + + self.assertEqual(table_index_model.tool_tip, 'File Error') + self.assertEqual(table_index_model.row_state, RowState.Error) + + def test_that_get_thickness_for_rows_updates_table_correctly(self): + table_model = TableModel() + table_index_model = TableIndexModel("LOQ74044", "", "", "", "", "", "", + "", "", "", "", "", "") + table_model.add_table_entry(0, table_index_model) + + table_model.get_thickness_for_rows() + table_model.work_handler.wait_for_done() + self.qApp.processEvents() + + self.assertEqual(table_index_model.sample_thickness, 1.0) + if __name__ == '__main__': unittest.main() diff --git a/scripts/test/directtools/DirectToolsTest.py b/scripts/test/directtools/DirectToolsTest.py index 88c15ccff62983d2b217d25693b7206d1de5ead1..0819376662b118fc9c47023ee0ec5525acafdbed 100644 --- a/scripts/test/directtools/DirectToolsTest.py +++ b/scripts/test/directtools/DirectToolsTest.py @@ -70,12 +70,7 @@ class DirectTest(unittest.TestCase): def test_defaultrcParams(self): result = directtools.defaultrcParams() - self.assertTrue(isinstance(result, dict)) - self.assertEqual(len(result), 2) - self.assertTrue('legend.numpoints' in result) - self.assertEqual(result['legend.numpoints'], 1) - self.assertTrue('text.usetex' in result) - self.assertEqual(result['text.usetex'], True) + self.assertEqual(result, {'legend.numpoints': 1}) def test_dynamicsusceptibility(self): xs = numpy.array([-1, 0, 1]) @@ -331,7 +326,7 @@ class DirectTest(unittest.TestCase): kwargs = {'workspaces': ws} figure, axes = testhelpers.assertRaisesNothing(self, directtools.plotprofiles, **kwargs) self.assertEquals(axes.get_xlabel(), '') - self.assertEquals(axes.get_ylabel(), '$S(Q,E)$') + self.assertEquals(axes.get_ylabel(), r'$S(Q,E)$') numpy.testing.assert_equal(axes.get_lines()[0].get_data()[0], (xs[1:] + xs[:-1])/2) numpy.testing.assert_equal(axes.get_lines()[0].get_data()[1], ys) @@ -342,7 +337,7 @@ class DirectTest(unittest.TestCase): kwargs = {'workspaces': ws} figure, axes = testhelpers.assertRaisesNothing(self, directtools.plotprofiles, **kwargs) self.assertEquals(axes.get_xlabel(), 'Energy (meV)') - self.assertEquals(axes.get_ylabel(), '$S(Q,E)$') + self.assertEquals(axes.get_ylabel(), r'$S(Q,E)$') numpy.testing.assert_equal(axes.get_lines()[0].get_data()[0], (xs[1:] + xs[:-1])/2) numpy.testing.assert_equal(axes.get_lines()[0].get_data()[1], ys) @@ -352,7 +347,7 @@ class DirectTest(unittest.TestCase): ws = CreateWorkspace(DataX=xs, DataY=ys, NSpec=1, UnitX='MomentumTransfer', StoreInADS=False) kwargs = {'workspaces': ws} figure, axes = testhelpers.assertRaisesNothing(self, directtools.plotprofiles, **kwargs) - self.assertEquals(axes.get_xlabel(), '$Q$ (\\AA$^{-1}$)') + self.assertEquals(axes.get_xlabel(), u'$Q$ (\u00c5$^{-1}$)') self.assertEquals(axes.get_ylabel(), '$S(Q,E)$') numpy.testing.assert_equal(axes.get_lines()[0].get_data()[0], (xs[1:] + xs[:-1])/2) numpy.testing.assert_equal(axes.get_lines()[0].get_data()[1], ys) diff --git a/scripts/test/isis_powder/ISISPowderFocusCropTest.py b/scripts/test/isis_powder/ISISPowderFocusCropTest.py index f05ab6f1889fd09c1eb8e8b7983ea3d47fee6552..db87ab54c890d0d839479e225344b122e7b962db 100644 --- a/scripts/test/isis_powder/ISISPowderFocusCropTest.py +++ b/scripts/test/isis_powder/ISISPowderFocusCropTest.py @@ -15,7 +15,8 @@ class ISISPowderFocusCropTest(unittest.TestCase): x = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] y = [0, 0, 10, 30, 2000, 80, 50, 40, 30, 25, 30] test_ws = mantid.CreateWorkspace(DataX=x, DataY=y) - test_ws = focus._crop_spline_to_percent_of_max(test_ws, test_ws, test_ws) + # search entire range (0 to 100) for max + test_ws = focus._crop_spline_to_percent_of_max(test_ws, test_ws, test_ws, 0, 100) y_compare = [30, 2000, 80, 50, 40, 30, 25, 30] result = test_ws.readY(0) for compare, val in zip(y_compare, result): @@ -25,7 +26,8 @@ class ISISPowderFocusCropTest(unittest.TestCase): x = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] y = [50, 100, 300, 500, 2000, 80, 50, 0, 0, 0, 0] test_ws = mantid.CreateWorkspace(DataX=x, DataY=y) - test_ws = focus._crop_spline_to_percent_of_max(test_ws, test_ws, test_ws) + # search entire range (0 to 100) for max + test_ws = focus._crop_spline_to_percent_of_max(test_ws, test_ws, test_ws, 0, 100) y_compare = [50, 100, 300, 500, 2000, 80, 50] result = test_ws.readY(0) for compare, val in zip(y_compare, result): @@ -35,7 +37,8 @@ class ISISPowderFocusCropTest(unittest.TestCase): x = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] y = [0, 0, 10, 30, 2000, 80, 50, 0, 0, 0, 0] test_ws = mantid.CreateWorkspace(DataX=x, DataY=y) - test_ws = focus._crop_spline_to_percent_of_max(test_ws, test_ws, test_ws) + # search entire range (0 to 100) for max + test_ws = focus._crop_spline_to_percent_of_max(test_ws, test_ws, test_ws, 0, 100) y_compare = [30, 2000, 80, 50] result = test_ws.readY(0) for compare, val in zip(y_compare, result): @@ -45,12 +48,35 @@ class ISISPowderFocusCropTest(unittest.TestCase): x = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] y = [1, 5, 10, 30, 20, 80, 50, 40, 20, 10, 1] test_ws = mantid.CreateWorkspace(DataX=x, DataY=y) - test_ws = focus._crop_spline_to_percent_of_max(test_ws, test_ws, test_ws) + # search entire range (0 to 100) for max + test_ws = focus._crop_spline_to_percent_of_max(test_ws, test_ws, test_ws, 0, 100) y_compare = [1, 5, 10, 30, 20, 80, 50, 40, 20, 10, 1] result = test_ws.readY(0) for compare, val in zip(y_compare, result): self.assertEqual(compare, val) + def test_no_crop_subrange(self): + x = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + y = [1, 5, 10, 30, 20, 80, 50, 40, 20, 10, 10000] + test_ws = mantid.CreateWorkspace(DataX=x, DataY=y) + # search from 0 to 20 x for max + test_ws = focus._crop_spline_to_percent_of_max(test_ws, test_ws, test_ws, 0, 20) + y_compare = [1, 5, 10, 30, 20, 80, 50, 40, 20, 10, 10000] + result = test_ws.readY(0) + for compare, val in zip(y_compare, result): + self.assertEqual(compare, val) + + def test_crop_subrange(self): + x = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + y = [1, 50, 2000, 30, 20, 80, 50, 40, 20, 10, 1] + test_ws = mantid.CreateWorkspace(DataX=x, DataY=y) + # search from 0 to 30 x for max + test_ws = focus._crop_spline_to_percent_of_max(test_ws, test_ws, test_ws, 0, 30) + y_compare = [50, 2000, 30, 20, 80, 50, 40] + result = test_ws.readY(0) + for compare, val in zip(y_compare, result): + self.assertEqual(compare, val) + if __name__ == '__main__': unittest.main() diff --git a/tools/scripts/pyqt4_to_qtpy.py b/tools/scripts/pyqt4_to_qtpy.py new file mode 100755 index 0000000000000000000000000000000000000000..bafee0f6d3eff766f6f0078c708adaa9395fcf8f --- /dev/null +++ b/tools/scripts/pyqt4_to_qtpy.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +from __future__ import (absolute_import, division, print_function, unicode_literals) +import os +import sys + + +def parse_old_style_connect(qt_old_line, linenum): + step1 = qt_old_line.split('connect(')[1] + step1 = step1.replace(' ', '') + terms = step1.split(',') + if len(terms) != 3: + raise RuntimeError('This is not right! L{1} {0}'.format(qt_old_line, + linenum)) + + # leading whitespace + whitespace = ' ' * (len(qt_old_line) - len(qt_old_line.lstrip(' '))) + # widget name + widget_name = terms[0] + # event signal + event_signal = terms[1] + # method to handle event + hander_method = terms[2].replace('(', '').replace(')', '').strip() + + return whitespace, widget_name, event_signal, hander_method + + +def convert_signal_connect(cmd, linenum): + """ + Convert (very) old style signal connection to newer style + """ + try: + if cmd.strip().startswith('#'): + return cmd + if cmd.count('self.connect') != 1: + return cmd + except UnicodeDecodeError: + print('L{} of source file encountered UnicodeDecodeError - not changing line'.format(linenum)) + return cmd + + whitespace, widget_name, event_signal, handler_method = parse_old_style_connect(cmd, linenum) + if event_signal.count('accepted') > 0: + signal_call = 'accepted' + elif event_signal.count('activated') > 0: + signal_call = 'activated' + elif event_signal.count('clicked') > 0: + signal_call = 'clicked' + elif event_signal.count('currentIndexChanged') > 0: + signal_call = 'currentIndexChanged' + elif event_signal.count('indexChanged') > 0: + # must follow currentIndexChanged + signal_call = 'indexChanged' + elif event_signal.count('stateChanged') > 0: + signal_call = 'stateChanged' + elif event_signal.count('toggled') > 0: + signal_call = 'toggled' + elif event_signal.count('itemSelectionChanged') > 0: + signal_call = 'itemSelectionChanged' + elif event_signal.count('rejected') > 0: + signal_call = 'rejected' + elif event_signal.count('returnPressed') > 0: + signal_call = 'returnPressed' + elif event_signal.count('textChanged') > 0: + signal_call = 'textChanged' + elif event_signal.count('triggered') > 0: + signal_call = 'triggered' + elif event_signal.count('valueChanged') > 0: + signal_call = 'valueChanged' + else: + sys.stderr.write('L{1} signal {0} is not supported - skipping line\n'.format(event_signal, linenum)) + return cmd + + return '{0}{1}.{2}.connect({3})\n'.format(whitespace, widget_name, signal_call, handler_method) + +QT4_TO_QTPY_FIXES = {'QtCore.QEventLoop': ('qtpy.QtCore', 'QEventLoop'), + 'QtCore.QFileInfo': ('qtpy.QtCore', 'QFileInfo'), + 'QtCore.QRegExp': ('qtpy.QtCore', 'QRegExp'), + 'QtCore.QSettings': ('qtpy.QtCore', 'QSettings'), + 'QtGui.QAction': ('qtpy.QtWidgets', 'QAction'), + 'QtGui.QButtonGroup': ('qtpy.QtWidgets', 'QButtonGroup'), + 'QtGui.QCheckBox': ('qtpy.QtWidgets', 'QCheckBox'), + 'QtGui.QComboBox': ('qtpy.QtWidgets', 'QComboBox'), + 'QtGui.QDialog': ('qtpy.QtWidgets', 'QDialog'), + 'QtGui.QDoubleValidator': ('qtpy.QtGui', 'QDoubleValidator'), + 'QtGui.QFileDialog': ('qtpy.QtWidgets', 'QFileDialog'), + 'QtGui.QFrame': ('qtpy.QtWidgets', 'QFrame'), + 'QtGui.QGridLayout': ('qtpy.QtWidgets', 'QGridLayout'), + 'QtGui.QGroupBox': ('qtpy.QtWidgets', 'QGroupBox'), + 'QtGui.QHBoxLayout': ('qtpy.QtWidgets', 'QHBoxLayout'), + 'QtGui.QIntValidator': ('qtpy.QtGui', 'QIntValidator'), + 'QtGui.QMenu': ('qtpy.QtWidgets', 'QMenu'), + 'QtGui.QLabel': ('qtpy.QtWidgets', 'QLabel'), + 'QtGui.QLineEdit': ('qtpy.QtWidgets', 'QLineEdit'), + 'QtGui.QMainWindow': ('qtpy.QtWidgets', 'QMainWindow'), + 'QtGui.QMessageBox': ('qtpy.QtWidgets', 'QMessageBox'), + 'QtGui.QPushButton': ('qtpy.QtWidgets', 'QPushButton'), + 'QtGui.QRegExpValidator': ('qtpy.QtGui', 'QRegExpValidator'), + 'QtGui.QSizePolicy': ('qtpy.QtWidgets', 'QSizePolicy'), + 'QtGui.QSpacerItem': ('qtpy.QtWidgets', 'QSpacerItem'), + 'QtGui.QTabWidget': ('qtpy.QtWidgets', 'QTabWidget'), + 'QtGui.QTextEdit': ('qtpy.QtWidgets', 'QTextEdit'), + 'QtGui.QVBoxLayout': ('qtpy.QtWidgets', 'QVBoxLayout'), + 'QtGui.QWidget': ('qtpy.QtWidgets', 'QWidget'), + } + + +def convertToQtPy(command, linenum): + imports = dict() + + try: + for old_txt, (import_mod, new_txt) in QT4_TO_QTPY_FIXES.items(): + if old_txt in command: + items = imports.get(import_mod, set()) + items.add(new_txt) + imports[import_mod] = items + command = command.replace(old_txt, new_txt) + if ('QtCore' in command or 'QtGui' in command) and 'import' not in command: + sys.stderr.write('L{} Found unknown qt call: "{}"\n'.format(linenum, + command.strip())) + except UnicodeDecodeError: + pass # would have already been an issue + return command, imports + + +def read_and_convert_pyqt4_functions(filename): + # parse and fix file + lines = list() + imports = dict() + with open(filename, 'r') as handle: + for linenum, line in enumerate(handle): + # editors count from 1 + line = convert_signal_connect(line, linenum+1) + line, imports_for_line = convertToQtPy(line, linenum+1) + for key, values in imports_for_line.items(): + items = imports.get(key, set()) + for item in values: + items.add(item) + imports[key] = items + lines.append(line) + + if len(imports) > 0: + print('========== Change imports of PyQt4 to be ==========') + for key, values in imports.items(): + values = list(values) + values.sort() + values = ', '.join(values) + print('from {} import ({}) # noqa'.format(key, values)) + + # overwrite with new version since there weren't errors + with open(filename, 'w') as handle: + for line in lines: + handle.write(line) + return lines + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser(description='Convert old qt4 signals to qt5-style') + parser.add_argument('files', nargs='+', help='script to convert') + options = parser.parse_args() + + for filename in options.files: + filename = os.path.abspath(os.path.expanduser(filename)) + if not os.path.exists(filename): + parser.error('file "{}" does not exist'.format(filename)) + + read_and_convert_pyqt4_functions(filename)