diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6813d520d89e3de45578a2e634a5d274f6e312b1..e65326b23c8fd56ffc2d0dd6ec595e2ebe91a4c9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -202,6 +202,24 @@ endif()
 
 add_subdirectory ( scripts )
 
+# Docs requirements
+find_package ( Sphinx 1.2 )
+if ( SPHINX_FOUND )
+  # run python to see if the theme is installed
+  execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "import sphinx_bootstrap_theme"
+                    OUTPUT_VARIABLE SPHINX_BOOTSTRAP_THEME_OUT
+                    ERROR_VARIABLE SPHINX_BOOTSTRAP_THEME_ERR
+                    OUTPUT_STRIP_TRAILING_WHITESPACE
+                    ERROR_STRIP_TRAILING_WHITESPACE )
+  if (SPHINX_BOOTSTRAP_THEME_ERR)
+    message (ERROR " Did not find sphinx_bootstrap_theme")
+    message (STATUS "${PYTHON_EXECUTABLE} -c \"import sphinx_bootstrap_theme\"")
+    message (STATUS "${SPHINX_BOOTSTRAP_THEME_ERR}")
+    message (FATAL_ERROR " Install instructions at https://pypi.python.org/pypi/sphinx-bootstrap-theme/")
+  endif ()
+endif ()
+
+add_subdirectory ( dev-docs )
 if ( ENABLE_MANTIDPLOT )
   add_subdirectory ( docs )
 endif()
diff --git a/Framework/API/CMakeLists.txt b/Framework/API/CMakeLists.txt
index 94ed3d4ef037105f540199cd69cac2063d0a9572..5afd011c1e9098f940964d4f6c9d9d7cc0e8d749 100644
--- a/Framework/API/CMakeLists.txt
+++ b/Framework/API/CMakeLists.txt
@@ -497,7 +497,9 @@ set_target_properties ( API PROPERTIES OUTPUT_NAME MantidAPI
                                        COMPILE_DEFINITIONS IN_MANTID_API )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( API PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+  set_target_properties(API PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(API PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 # Add to the 'Framework' group in VS
diff --git a/Framework/API/src/MatrixWorkspace.cpp b/Framework/API/src/MatrixWorkspace.cpp
index ff42242b017107bcb9afcc04082a1891ce2d4d15..12a2ba68ac45b9e2ac0a1882c6106994b2106654 100644
--- a/Framework/API/src/MatrixWorkspace.cpp
+++ b/Framework/API/src/MatrixWorkspace.cpp
@@ -21,6 +21,7 @@
 #include "MantidKernel/TimeSeriesProperty.h"
 #include "MantidKernel/make_unique.h"
 #include "MantidKernel/VectorHelper.h"
+#include "MantidParallel/Collectives.h"
 #include "MantidParallel/Communicator.h"
 #include "MantidTypes/SpectrumDefinition.h"
 
@@ -654,10 +655,6 @@ double MatrixWorkspace::getXMax() const {
 }
 
 void MatrixWorkspace::getXMinMax(double &xmin, double &xmax) const {
-  if (m_indexInfo->size() != m_indexInfo->globalSize())
-    throw std::runtime_error(
-        "MatrixWorkspace: Parallel support for XMin and XMax not implemented.");
-
   // set to crazy values to start
   xmin = std::numeric_limits<double>::max();
   xmax = -1.0 * xmin;
@@ -676,6 +673,14 @@ void MatrixWorkspace::getXMinMax(double &xmin, double &xmax) const {
         xmax = xback;
     }
   }
+  if (m_indexInfo->size() != m_indexInfo->globalSize()) {
+    auto &comm = m_indexInfo->communicator();
+    std::vector<double> extrema(comm.size());
+    Parallel::all_gather(comm, xmin, extrema);
+    xmin = *std::min_element(extrema.begin(), extrema.end());
+    Parallel::all_gather(comm, xmax, extrema);
+    xmax = *std::max_element(extrema.begin(), extrema.end());
+  }
 }
 
 /** Integrate all the spectra in the matrix workspace within the range given.
diff --git a/Framework/API/test/FunctionAttributeTest.h b/Framework/API/test/FunctionAttributeTest.h
index 5795e901eeb6042a57a70afd7183bab68d9180a1..588770f5f7d2e1137c137adf2dc52870aade6d0e 100644
--- a/Framework/API/test/FunctionAttributeTest.h
+++ b/Framework/API/test/FunctionAttributeTest.h
@@ -11,7 +11,7 @@
 using namespace Mantid;
 using namespace Mantid::API;
 
-namespace {
+namespace detail {
 class IFT_Funct : public ParamFunction, public IFunction1D {
 public:
   IFT_Funct() {
@@ -34,13 +34,12 @@ public:
 };
 
 DECLARE_FUNCTION(IFT_Funct)
-
-} // namespace
+} // namespace detail
 
 class FunctionAttributeTest : public CxxTest::TestSuite {
 public:
   void test_double_attribute() {
-    IFT_Funct f;
+    detail::IFT_Funct f;
     IFunction::Attribute att = f.getAttribute("DAttr");
 
     TS_ASSERT_EQUALS(att.asDouble(), 0.0);
@@ -67,7 +66,7 @@ public:
   }
 
   void test_int_attribute() {
-    IFT_Funct f;
+    detail::IFT_Funct f;
     IFunction::Attribute att = f.getAttribute("IAttr");
 
     TS_ASSERT_EQUALS(att.asInt(), 0);
@@ -89,7 +88,7 @@ public:
   }
 
   void test_bool_attribute() {
-    IFT_Funct f;
+    detail::IFT_Funct f;
     IFunction::Attribute att = f.getAttribute("BAttr");
 
     TS_ASSERT_EQUALS(att.asBool(), false);
@@ -117,7 +116,7 @@ public:
   }
 
   void test_string_attribute() {
-    IFT_Funct f;
+    detail::IFT_Funct f;
     IFunction::Attribute att = f.getAttribute("SAttr");
 
     TS_ASSERT_EQUALS(att.asString(), "");
@@ -139,7 +138,7 @@ public:
   }
 
   void test_quoted_string_attribute() {
-    IFT_Funct f;
+    detail::IFT_Funct f;
     IFunction::Attribute att = f.getAttribute("SQAttr");
 
     TS_ASSERT_EQUALS(att.asString(), "\"\"");
@@ -161,7 +160,7 @@ public:
   }
 
   void test_vector_attribute() {
-    IFT_Funct f;
+    detail::IFT_Funct f;
     IFunction::Attribute att = f.getAttribute("VAttr");
 
     TS_ASSERT_EQUALS(att.type(), "std::vector<double>");
@@ -256,7 +255,7 @@ public:
   }
 
   void test_empty_string_attributes_do_not_show_by_asString() {
-    IFT_Funct f;
+    detail::IFT_Funct f;
     TS_ASSERT_EQUALS(
         f.asString(),
         "name=IFT_Funct,BAttr=false,DAttr=0,IAttr=0,VAttr=(),VAttr1=(1,2,3)");
diff --git a/Framework/API/test/MultiPeriodGroupWorkerTest.h b/Framework/API/test/MultiPeriodGroupWorkerTest.h
index 6869712fb2e77506be7e3926d6c7f750935aa2c1..88635589562ce3aea3a5ff2df3f65e1934a4b9a0 100644
--- a/Framework/API/test/MultiPeriodGroupWorkerTest.h
+++ b/Framework/API/test/MultiPeriodGroupWorkerTest.h
@@ -12,7 +12,6 @@
 using Mantid::API::MultiPeriodGroupWorker;
 using namespace Mantid::API;
 
-namespace {
 class TestAlgorithm : public Algorithm {
 public:
   TestAlgorithm() {}
@@ -32,7 +31,6 @@ public:
   ~TestAlgorithm() override {}
 };
 DECLARE_ALGORITHM(TestAlgorithm)
-}
 
 class MultiPeriodGroupWorkerTest : public CxxTest::TestSuite,
                                    public MultiPeriodGroupTestBase {
diff --git a/Framework/API/test/MultipleFilePropertyTest.h b/Framework/API/test/MultipleFilePropertyTest.h
index d374d704f0a0a1c43b3db3e4155edb94342b23da..797b8cb32db42561afcc3fa592bccc120c09ff8a 100644
--- a/Framework/API/test/MultipleFilePropertyTest.h
+++ b/Framework/API/test/MultipleFilePropertyTest.h
@@ -397,6 +397,37 @@ public:
     TS_ASSERT_EQUALS(fileNames[6][2], dummyFile("TSC00004.raw"));
   }
 
+  void test_multipleFiles_shortForm_addRanges() {
+    MultipleFileProperty p("Filename");
+    p.setValue("TSC1-2+4-5.raw");
+    std::vector<std::vector<std::string>> fileNames = p();
+
+    TS_ASSERT_EQUALS(fileNames[0][0], dummyFile("TSC00001.raw"));
+    TS_ASSERT_EQUALS(fileNames[0][1], dummyFile("TSC00002.raw"));
+    TS_ASSERT_EQUALS(fileNames[0][2], dummyFile("TSC00004.raw"));
+    TS_ASSERT_EQUALS(fileNames[0][3], dummyFile("TSC00005.raw"));
+  }
+
+  void test_multipleFiles_shortForm_addSingleToRange() {
+    MultipleFileProperty p("Filename");
+    p.setValue("TSC2+4-5.raw");
+    std::vector<std::vector<std::string>> fileNames = p();
+
+    TS_ASSERT_EQUALS(fileNames[0][0], dummyFile("TSC00002.raw"));
+    TS_ASSERT_EQUALS(fileNames[0][1], dummyFile("TSC00004.raw"));
+    TS_ASSERT_EQUALS(fileNames[0][2], dummyFile("TSC00005.raw"));
+  }
+
+  void test_multipleFiles_shortForm_rangeToSingle() {
+    MultipleFileProperty p("Filename");
+    p.setValue("TSC1-2+5.raw");
+    std::vector<std::vector<std::string>> fileNames = p();
+
+    TS_ASSERT_EQUALS(fileNames[0][0], dummyFile("TSC00001.raw"));
+    TS_ASSERT_EQUALS(fileNames[0][1], dummyFile("TSC00002.raw"));
+    TS_ASSERT_EQUALS(fileNames[0][2], dummyFile("TSC00005.raw"));
+  }
+
   void test_multipleFiles_longForm_commaList() {
     MultipleFileProperty p("Filename");
     p.setValue("TSC1.raw,TSC2.raw,TSC3.raw,TSC4.raw,TSC5.raw");
diff --git a/Framework/Algorithms/CMakeLists.txt b/Framework/Algorithms/CMakeLists.txt
index 39fcebb6676aaa9c84544e4d9c50bb80cbac5bc9..5ecfac48d9482a8f4e31436084f61b53f4eaf60c 100644
--- a/Framework/Algorithms/CMakeLists.txt
+++ b/Framework/Algorithms/CMakeLists.txt
@@ -220,6 +220,7 @@ set ( SRC_FILES
 	src/PointByPointVCorrection.cpp
 	src/PoissonErrors.cpp
 	src/PolarizationCorrection.cpp
+	src/PolarizationEfficiencyCor.cpp
 	src/PolynomialCorrection.cpp
 	src/Power.cpp
 	src/PowerLawCorrection.cpp
@@ -556,6 +557,7 @@ set ( INC_FILES
 	inc/MantidAlgorithms/PointByPointVCorrection.h
 	inc/MantidAlgorithms/PoissonErrors.h
 	inc/MantidAlgorithms/PolarizationCorrection.h
+	inc/MantidAlgorithms/PolarizationEfficiencyCor.h
 	inc/MantidAlgorithms/PolynomialCorrection.h
 	inc/MantidAlgorithms/Power.h
 	inc/MantidAlgorithms/PowerLawCorrection.h
@@ -894,6 +896,7 @@ set ( TEST_FILES
 	PointByPointVCorrectionTest.h
 	PoissonErrorsTest.h
 	PolarizationCorrectionTest.h
+	PolarizationEfficiencyCorTest.h
 	PolynomialCorrectionTest.h
 	PowerLawCorrectionTest.h
 	PowerTest.h
@@ -943,6 +946,7 @@ set ( TEST_FILES
 	SmoothDataTest.h
 	SmoothNeighboursTest.h
 	SofQWCentreTest.h
+	SofQWCutTest.h
 	SofQWNormalisedPolygonTest.h
 	SofQWPolygonTest.h
 	SofQWTest.h
@@ -1004,7 +1008,9 @@ set_target_properties ( Algorithms PROPERTIES OUTPUT_NAME MantidAlgorithms
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( Algorithms PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
+  set_target_properties(Algorithms PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(Algorithms PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 # Add to the 'Framework' group in VS
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/AlignDetectors.h b/Framework/Algorithms/inc/MantidAlgorithms/AlignDetectors.h
index de48ddb74647f7caebbacea08caf0a03b07447b2..6d5dd1fb37c9a8fd5a1ecf8dfae60517a5243065 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/AlignDetectors.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/AlignDetectors.h
@@ -72,6 +72,11 @@ public:
   /// Cross-check properties with each other @see IAlgorithm::validateInputs
   std::map<std::string, std::string> validateInputs() override;
 
+protected:
+  Parallel::ExecutionMode getParallelExecutionMode(
+      const std::map<std::string, Parallel::StorageMode> &storageModes)
+      const override;
+
 private:
   // Implement abstract Algorithm methods
   void init() override;
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/CalculateDIFC.h b/Framework/Algorithms/inc/MantidAlgorithms/CalculateDIFC.h
index 9c5e9129ba7f17cda15d6a9bb358ff3170687954..257cce3dfbfeb9a3902d5070fd1ec04aa2dfeaac 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/CalculateDIFC.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/CalculateDIFC.h
@@ -34,13 +34,18 @@ namespace Algorithms {
 */
 class DLLExport CalculateDIFC : public API::Algorithm {
 public:
+  /// Algorithms name for identification. @see Algorithm::name
   const std::string name() const override;
+  /// Algorithm's version for identification. @see Algorithm::version
   int version() const override;
   const std::string category() const override;
+  /// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
   const std::string summary() const override;
 
 private:
   void init() override;
+  /// Cross-check properties with each other @see IAlgorithm::validateInputs
+  std::map<std::string, std::string> validateInputs() override;
   void exec() override;
 };
 
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/ConjoinXRuns.h b/Framework/Algorithms/inc/MantidAlgorithms/ConjoinXRuns.h
index 07899a20dcd6953e52045727f50e6a7ab1abef54..d19c6cd8d8f2bc96b97474eb9f01cd36fa89bfe6 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/ConjoinXRuns.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/ConjoinXRuns.h
@@ -61,6 +61,8 @@ private:
   std::list<API::MatrixWorkspace_sptr> m_inputWS;
   /// Output workspace
   API::MatrixWorkspace_sptr m_outWS;
+  /// X-axis cache if sample log is given
+  std::map<std::string, std::vector<double>> m_axisCache;
 };
 
 } // namespace Algorithms
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/ConvertDiffCal.h b/Framework/Algorithms/inc/MantidAlgorithms/ConvertDiffCal.h
index 8c4a70b770820bdef44eae290d7f34b96b01982b..9efc415141be4b49c3b8547f75c5223e07820d8e 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/ConvertDiffCal.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/ConvertDiffCal.h
@@ -2,7 +2,7 @@
 #define MANTID_ALGORITHMS_CONVERTDIFFCAL_H_
 
 #include "MantidKernel/System.h"
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/ParallelAlgorithm.h"
 namespace Mantid {
 namespace Algorithms {
 
@@ -29,7 +29,7 @@ namespace Algorithms {
   File change history is stored at: <https://github.com/mantidproject/mantid>
   Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
-class DLLExport ConvertDiffCal : public API::Algorithm {
+class DLLExport ConvertDiffCal : public API::ParallelAlgorithm {
 public:
   const std::string name() const override;
   int version() const override;
@@ -44,4 +44,4 @@ private:
 } // namespace Algorithms
 } // namespace Mantid
 
-#endif /* MANTID_ALGORITHMS_CONVERTDIFFCAL_H_ */
\ No newline at end of file
+#endif /* MANTID_ALGORITHMS_CONVERTDIFFCAL_H_ */
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/DiffractionFocussing2.h b/Framework/Algorithms/inc/MantidAlgorithms/DiffractionFocussing2.h
index 8b4bab6882c13c1fe6d7bbb2546a72281fea63c9..f480d4d18789afa9059b78c63a086e48ac0142d6 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/DiffractionFocussing2.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/DiffractionFocussing2.h
@@ -121,10 +121,6 @@ private:
   /// Shared pointer to the event workspace
   DataObjects::EventWorkspace_const_sptr m_eventW;
 
-  // This map does not need to be ordered, just a lookup for udet
-  /// typedef for the storage of the UDET-group mapping
-  typedef std::map<detid_t, int> udet2groupmap;
-
   // This map needs to be ordered to process the groups in order.
   /// typedef for the storage of each group's X vector
   typedef std::map<int, boost::shared_ptr<MantidVec>> group2vectormap;
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/FFTSmooth2.h b/Framework/Algorithms/inc/MantidAlgorithms/FFTSmooth2.h
index f21bb5b5b9809e752bbe2d0cba1b75a720d83d9f..5706a7006eb243e93b7ee7df0b61b4646c86b22f 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/FFTSmooth2.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/FFTSmooth2.h
@@ -1,10 +1,7 @@
 #ifndef MANTID_ALGORITHM_FFTSMOOTH2_H_
 #define MANTID_ALGORITHM_FFTSMOOTH2_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/ParallelAlgorithm.h"
 #include "MantidAPI/MatrixWorkspace_fwd.h"
 
 namespace Mantid {
@@ -35,7 +32,7 @@ namespace Algorithms {
     File change history is stored at: <https://github.com/mantidproject/mantid>
     Code Documentation is available at: <http://doxygen.mantidproject.org>
  */
-class DLLExport FFTSmooth2 : public API::Algorithm {
+class DLLExport FFTSmooth2 : public API::ParallelAlgorithm {
 public:
   /// Algorithm's name for identification overriding a virtual method
   const std::string name() const override { return "FFTSmooth"; }
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/FindPeakBackground.h b/Framework/Algorithms/inc/MantidAlgorithms/FindPeakBackground.h
index a92f02e4c58f032b0aba7f7bb177a4d029329ee9..d2737dc9b9912c4300da78081506f77d8a2f595b 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/FindPeakBackground.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/FindPeakBackground.h
@@ -1,7 +1,7 @@
 #ifndef MANTID_ALGORITHMS_FindPeakBackground_H_
 #define MANTID_ALGORITHMS_FindPeakBackground_H_
 
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/ParallelAlgorithm.h"
 #include "MantidAPI/ITableWorkspace.h"
 #include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/cow_ptr.h"
@@ -38,7 +38,7 @@ namespace Algorithms {
   File change history is stored at: <https://github.com/mantidproject/mantid>
   Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
-class DLLExport FindPeakBackground : public API::Algorithm {
+class DLLExport FindPeakBackground : public API::ParallelAlgorithm {
 public:
   /// Algorithm's name for identification overriding a virtual method
   const std::string name() const override { return "FindPeakBackground"; }
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/FindPeaks.h b/Framework/Algorithms/inc/MantidAlgorithms/FindPeaks.h
index 636af33e7c6daba7d1697411550256012be0d97c..7cdaf8d08bb3ac3487f964e3a49bd302c51e002b 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/FindPeaks.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/FindPeaks.h
@@ -1,12 +1,8 @@
 #ifndef MANTID_ALGORITHMS_FINDPEAKS_H_
 #define MANTID_ALGORITHMS_FINDPEAKS_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/ParallelAlgorithm.h"
 #include "MantidKernel/System.h"
-// #include "MantidAPI/IFunction.h"
 #include "MantidAPI/IBackgroundFunction.h"
 #include "MantidAPI/IPeakFunction.h"
 #include "MantidDataObjects/TableWorkspace.h"
@@ -67,7 +63,7 @@ namespace Algorithms {
     Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
 
-class DLLExport FindPeaks : public API::Algorithm {
+class DLLExport FindPeaks : public API::ParallelAlgorithm {
 public:
   /// Constructor
   FindPeaks();
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/GeneratePythonScript.h b/Framework/Algorithms/inc/MantidAlgorithms/GeneratePythonScript.h
index 296abcea1386a6993999afe4b2acdeaccd423a0d..90db547f68428134283aef4b81e233d45fe064e3 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/GeneratePythonScript.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/GeneratePythonScript.h
@@ -2,7 +2,7 @@
 #define MANTID_ALGORITHMS_GENERATEPYTHONSCRIPT_H_
 
 #include "MantidKernel/System.h"
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/SerialAlgorithm.h"
 
 namespace Mantid {
 namespace Algorithms {
@@ -42,7 +42,7 @@ namespace Algorithms {
   File change history is stored at: <https://github.com/mantidproject/mantid>
   Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
-class DLLExport GeneratePythonScript : public API::Algorithm {
+class DLLExport GeneratePythonScript : public API::SerialAlgorithm {
 public:
   /// Algorithm's name for identification
   const std::string name() const override { return "GeneratePythonScript"; };
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/MultipleScatteringCylinderAbsorption.h b/Framework/Algorithms/inc/MantidAlgorithms/MultipleScatteringCylinderAbsorption.h
index 68a30583a3615e34d013f6c9e16e683c3dca44c8..ab2190f5bf9fa46fadddf2d3a6e49d054f002728 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/MultipleScatteringCylinderAbsorption.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/MultipleScatteringCylinderAbsorption.h
@@ -1,10 +1,7 @@
 #ifndef MANTID_ALGORITHM_MULTIPLE_SCATTERING_ABSORPTION_H_
 #define MANTID_ALGORITHM_MULTIPLE_SCATTERING_ABSORPTION_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/DistributedAlgorithm.h"
 #include <vector>
 
 namespace Mantid {
@@ -44,7 +41,8 @@ namespace Algorithms {
                   <https://github.com/mantidproject/mantid>
     Code Documentation is available at: <http://doxygen.mantidproject.org>
  */
-class DLLExport MultipleScatteringCylinderAbsorption : public API::Algorithm {
+class DLLExport MultipleScatteringCylinderAbsorption
+    : public API::DistributedAlgorithm {
 public:
   /// Algorithm's name for identification overriding a virtual method
   const std::string name() const override;
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/NormaliseByCurrent.h b/Framework/Algorithms/inc/MantidAlgorithms/NormaliseByCurrent.h
index 6bb98e6ed6d0ed2c423856b5d5103322d91a7619..c41872d84f016b308ff7653a6b127eee2d2dbf39 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/NormaliseByCurrent.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/NormaliseByCurrent.h
@@ -1,10 +1,7 @@
 #ifndef MANTID_ALGORITHMS_NORMALISEBYCURRENT_H_
 #define MANTID_ALGORITHMS_NORMALISEBYCURRENT_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/DistributedAlgorithm.h"
 #include <boost/shared_ptr.hpp>
 namespace Mantid {
 namespace API {
@@ -50,7 +47,7 @@ namespace Algorithms {
     File change history is stored at: <https://github.com/mantidproject/mantid>
     Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
-class DLLExport NormaliseByCurrent : public API::Algorithm {
+class DLLExport NormaliseByCurrent : public API::DistributedAlgorithm {
 public:
   /// Algorithm's name for identification overriding a virtual method
   const std::string name() const override { return "NormaliseByCurrent"; }
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/PDDetermineCharacterizations.h b/Framework/Algorithms/inc/MantidAlgorithms/PDDetermineCharacterizations.h
index 50102fe4a80510dbbda11ad29ae79f1f7190d061..860285ea1a8e6d7cf08892b194b4f7366b46c22a 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/PDDetermineCharacterizations.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/PDDetermineCharacterizations.h
@@ -2,7 +2,7 @@
 #define MANTID_ALGORITHMS_PDDETERMINECHARACTERIZATIONS_H_
 
 #include "MantidKernel/System.h"
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/DistributedAlgorithm.h"
 #include "MantidAPI/ITableWorkspace_fwd.h"
 
 namespace Mantid {
@@ -43,7 +43,8 @@ namespace Algorithms {
   File change history is stored at: <https://github.com/mantidproject/mantid>
   Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
-class DLLExport PDDetermineCharacterizations : public API::Algorithm {
+class DLLExport PDDetermineCharacterizations
+    : public API::DistributedAlgorithm {
 public:
   const std::string name() const override;
   int version() const override;
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/PolarizationEfficiencyCor.h b/Framework/Algorithms/inc/MantidAlgorithms/PolarizationEfficiencyCor.h
new file mode 100644
index 0000000000000000000000000000000000000000..53ed383886519c6642b7644a94f9188e6b000d3a
--- /dev/null
+++ b/Framework/Algorithms/inc/MantidAlgorithms/PolarizationEfficiencyCor.h
@@ -0,0 +1,97 @@
+#ifndef MANTID_ALGORITHMS_POLARIZATIONEFFICIENCYCOR_H_
+#define MANTID_ALGORITHMS_POLARIZATIONEFFICIENCYCOR_H_
+
+#include "MantidAlgorithms/DllConfig.h"
+#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/WorkspaceGroup_fwd.h"
+
+namespace Mantid {
+namespace API {
+class ISpectrum;
+}
+
+namespace Algorithms {
+
+/** PolarizationEfficiencyCor : This algorithm corrects for non-ideal
+  component efficiencies in polarized neutron analysis. It is based on
+  [A. R. Wildes (2006) Neutron News, 17:2, 17-25,
+  DOI: 10.1080/10448630600668738]
+
+  Copyright &copy; 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>
+*/
+class MANTID_ALGORITHMS_DLL PolarizationEfficiencyCor : 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:
+  /// A convenience set of workspaces corresponding flipper configurations.
+  struct WorkspaceMap {
+    API::MatrixWorkspace_sptr mmWS{nullptr};
+    API::MatrixWorkspace_sptr mpWS{nullptr};
+    API::MatrixWorkspace_sptr pmWS{nullptr};
+    API::MatrixWorkspace_sptr ppWS{nullptr};
+    size_t size() const noexcept;
+  };
+
+  /// A convenience set of efficiency factors.
+  struct EfficiencyMap {
+    const API::ISpectrum *P1{nullptr};
+    const API::ISpectrum *P2{nullptr};
+    const API::ISpectrum *F1{nullptr};
+    const API::ISpectrum *F2{nullptr};
+  };
+
+  void init() override;
+  void exec() override;
+  std::map<std::string, std::string> validateInputs() override;
+  void checkConsistentNumberHistograms(const WorkspaceMap &inputs);
+  void checkConsistentX(const WorkspaceMap &inputs,
+                        const EfficiencyMap &efficiencies);
+  EfficiencyMap efficiencyFactors();
+  WorkspaceMap directBeamCorrections(const WorkspaceMap &inputs,
+                                     const EfficiencyMap &efficiencies);
+  WorkspaceMap analyzerlessCorrections(const WorkspaceMap &inputs,
+                                       const EfficiencyMap &efficiencies);
+  WorkspaceMap twoInputCorrections(const WorkspaceMap &inputs,
+                                   const EfficiencyMap &efficiencies);
+  WorkspaceMap threeInputCorrections(const WorkspaceMap &inputs,
+                                     const EfficiencyMap &efficiencies);
+  WorkspaceMap fullCorrections(const WorkspaceMap &inputs,
+                               const EfficiencyMap &efficiencies);
+  API::WorkspaceGroup_sptr groupOutput(const WorkspaceMap &outputs);
+  WorkspaceMap mapInputsToDirections(const std::vector<std::string> &flippers);
+  void threeInputsSolve01(WorkspaceMap &inputs,
+                          const EfficiencyMap &efficiencies);
+  void threeInputsSolve10(WorkspaceMap &inputs,
+                          const EfficiencyMap &efficiencies);
+  void twoInputsSolve01And10(WorkspaceMap &fullInputs,
+                             const WorkspaceMap &inputs,
+                             const EfficiencyMap &efficiencies);
+};
+
+} // namespace Algorithms
+} // namespace Mantid
+
+#endif /* MANTID_ALGORITHMS_POLARIZATIONEFFICIENCYCOR_H_ */
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/RealFFT.h b/Framework/Algorithms/inc/MantidAlgorithms/RealFFT.h
index 69ad61a00e07ac90396e58ba3d09caea0af5b1a3..bba0209f62036629b7956d220a3fce703d3e484a 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/RealFFT.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/RealFFT.h
@@ -1,10 +1,7 @@
 #ifndef MANTID_ALGORITHM_REALFFT_H_
 #define MANTID_ALGORITHM_REALFFT_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/ParallelAlgorithm.h"
 #include "MantidAPI/Workspace_fwd.h"
 #include "MantidAlgorithms/FFT.h"
 
@@ -36,7 +33,7 @@ namespace Algorithms {
     File change history is stored at: <https://github.com/mantidproject/mantid>
     Code Documentation is available at: <http://doxygen.mantidproject.org>
  */
-class DLLExport RealFFT : public API::Algorithm {
+class DLLExport RealFFT : public API::ParallelAlgorithm {
 public:
   /// Algorithm's name for identification overriding a virtual method
   const std::string name() const override { return "RealFFT"; }
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/RemoveLowResTOF.h b/Framework/Algorithms/inc/MantidAlgorithms/RemoveLowResTOF.h
index 6cd9c74847f29dbab236824f27ddca2117b8bd64..98c2d35754d63803dc8f6d7794eae94300727b18 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/RemoveLowResTOF.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/RemoveLowResTOF.h
@@ -1,18 +1,16 @@
 #ifndef REMOVELOWRESTOF_H_
 #define REMOVELOWRESTOF_H_
 
-// includes
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/DistributedAlgorithm.h"
 #include "MantidDataObjects/EventWorkspace.h"
 #include "MantidGeometry/IComponent.h"
 
 namespace Mantid {
 
 namespace Algorithms {
-class DLLExport RemoveLowResTOF : public API::Algorithm {
+class DLLExport RemoveLowResTOF : public API::DistributedAlgorithm {
 public:
   RemoveLowResTOF();
-  ~RemoveLowResTOF() override;
   const std::string name() const override;
   int version() const override;
   const std::string category() const override;
@@ -31,7 +29,6 @@ private:
   /// Pointer to the input event workspace
   DataObjects::EventWorkspace_const_sptr m_inputEvWS;
   double calcTofMin(const std::size_t, const API::SpectrumInfo &spectrumInfo);
-  void runMaskDetectors();
   void getTminData(const bool);
   /// The reference value for DIFC to filter with
   double m_DIFCref;
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/RenameWorkspace.h b/Framework/Algorithms/inc/MantidAlgorithms/RenameWorkspace.h
index 2f6d4fa95e5fece4686cdf7261e8667d36c392a9..fcde2879ca7e806c156052801f926bd8ff3b8bf5 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/RenameWorkspace.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/RenameWorkspace.h
@@ -1,10 +1,7 @@
 #ifndef MANTID_ALGORITHMS_RENAMEWORKSPACE_H_
 #define MANTID_ALGORITHMS_RENAMEWORKSPACE_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/DistributedAlgorithm.h"
 
 namespace Mantid {
 namespace Algorithms {
@@ -41,7 +38,7 @@ namespace Algorithms {
 
     Code Documentation is available at: <http://doxygen.mantidproject.org>
  */
-class DLLExport RenameWorkspace : public API::Algorithm {
+class DLLExport RenameWorkspace : public API::DistributedAlgorithm {
 public:
   /// Algorithm's name for identification overriding a virtual method
   const std::string name() const override { return "RenameWorkspace"; }
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SetUncertainties.h b/Framework/Algorithms/inc/MantidAlgorithms/SetUncertainties.h
index 24a3d17912a135ff4d2e36da3c73c01b358cba9e..5488a90453d853c66d979861c8530f03240613fa 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/SetUncertainties.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/SetUncertainties.h
@@ -1,10 +1,7 @@
 #ifndef MANTID_ALGORITHMS_SETUNCERTAINTIES_H_
 #define MANTID_ALGORITHMS_SETUNCERTAINTIES_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/ParallelAlgorithm.h"
 
 namespace Mantid {
 namespace Algorithms {
@@ -39,7 +36,7 @@ namespace Algorithms {
  File change history is stored at: <https://github.com/mantidproject/mantid>
  Code Documentation is available at: <http://doxygen.mantidproject.org>
  */
-class DLLExport SetUncertainties : public API::Algorithm {
+class DLLExport SetUncertainties : public API::ParallelAlgorithm {
 public:
   /// Algorithm's name
   const std::string name() const override;
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SofQW.h b/Framework/Algorithms/inc/MantidAlgorithms/SofQW.h
index 2530589abaebcc68be7824c78c07b9d562a8b66a..bd8137d3fb271c6c602e7c918808f68d75a0d84e 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/SofQW.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/SofQW.h
@@ -62,8 +62,9 @@ public:
   /// Create the output workspace
   static API::MatrixWorkspace_sptr
   setUpOutputWorkspace(API::MatrixWorkspace_const_sptr inputWorkspace,
-                       const std::vector<double> &binParams,
-                       std::vector<double> &newAxis);
+                       const std::vector<double> &qbinParams,
+                       std::vector<double> &qAxis,
+                       const std::vector<double> &ebinParams);
   /// Create the input properties on the given algorithm object
   static void createCommonInputProperties(API::Algorithm &alg);
   /// Energy to K constant
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SofQWCentre.h b/Framework/Algorithms/inc/MantidAlgorithms/SofQWCentre.h
index f1cbeacb7d34a5ed4d2961d0aa0d674438a40b75..f850e0d0329099d4d79859534889cccd6c6b1e08 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/SofQWCentre.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/SofQWCentre.h
@@ -68,7 +68,8 @@ public:
   static API::MatrixWorkspace_sptr
   setUpOutputWorkspace(API::MatrixWorkspace_const_sptr inputWorkspace,
                        const std::vector<double> &binParams,
-                       std::vector<double> &newAxis);
+                       std::vector<double> &newAxis,
+                       const std::vector<double> &ebinParams);
   /// Convert the workspace to a distribution
   void makeDistribution(API::MatrixWorkspace_sptr outputWS,
                         const std::vector<double> qAxis);
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SofQWNormalisedPolygon.h b/Framework/Algorithms/inc/MantidAlgorithms/SofQWNormalisedPolygon.h
index 89ec0c5bf9440507ac54f4e7c2190875366d8586..e3a4a37bb9a2cf7b74136e77c18b71ba3d722683 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/SofQWNormalisedPolygon.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/SofQWNormalisedPolygon.h
@@ -94,8 +94,9 @@ private:
   /// Create the output workspace
   DataObjects::RebinnedOutput_sptr
   setUpOutputWorkspace(const API::MatrixWorkspace &inputWorkspace,
-                       const std::vector<double> &binParams,
-                       std::vector<double> &newAxis);
+                       const std::vector<double> &qbinParams,
+                       std::vector<double> &qAxis,
+                       const std::vector<double> &ebinParams);
 
   SofQCommon m_EmodeProperties;
   /// Output Q axis
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/StripPeaks.h b/Framework/Algorithms/inc/MantidAlgorithms/StripPeaks.h
index 858a4f3e5bfc9f09cff64f4c76e54e31a699d76a..aea54a092d9a594203e67038b0910e8d009f29c4 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/StripPeaks.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/StripPeaks.h
@@ -1,10 +1,7 @@
 #ifndef MANTID_ALGORITHMS_STRIPPEAKS_H_
 #define MANTID_ALGORITHMS_STRIPPEAKS_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/ParallelAlgorithm.h"
 #include "MantidAPI/ITableWorkspace_fwd.h"
 
 namespace Mantid {
@@ -53,10 +50,8 @@ namespace Algorithms {
     File change history is stored at: <https://github.com/mantidproject/mantid>
     Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
-class DLLExport StripPeaks : public API::Algorithm {
+class DLLExport StripPeaks : public API::ParallelAlgorithm {
 public:
-  /// (Empty) Constructor
-  StripPeaks();
   /// Algorithm's name
   const std::string name() const override { return "StripPeaks"; }
   /// Summary of algorithms purpose
@@ -82,7 +77,7 @@ private:
   API::ITableWorkspace_sptr findPeaks(API::MatrixWorkspace_sptr WS);
   API::MatrixWorkspace_sptr removePeaks(API::MatrixWorkspace_const_sptr input,
                                         API::ITableWorkspace_sptr peakslist);
-  double m_maxChiSq;
+  double m_maxChiSq{0.0};
 };
 
 } // namespace Algorithms
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/StripVanadiumPeaks2.h b/Framework/Algorithms/inc/MantidAlgorithms/StripVanadiumPeaks2.h
index 42252fa1b190b2a6eec8632277199244230d1f46..da5ac35ef604e883add0d998da14deaa12591e09 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/StripVanadiumPeaks2.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/StripVanadiumPeaks2.h
@@ -2,7 +2,7 @@
 #define MANTID_ALGORITHMS_STRIPVANADIUMPEAKS2_H_
 
 #include "MantidKernel/System.h"
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/ParallelAlgorithm.h"
 
 namespace Mantid {
 namespace Algorithms {
@@ -33,7 +33,7 @@ namespace Algorithms {
   File change history is stored at: <https://github.com/mantidproject/mantid>
   Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
-class DLLExport StripVanadiumPeaks2 : public API::Algorithm {
+class DLLExport StripVanadiumPeaks2 : public API::ParallelAlgorithm {
 public:
   /// Algorithm's name for identification overriding a virtual method
   const std::string name() const override { return "StripVanadiumPeaks"; }
diff --git a/Framework/Algorithms/src/AlignDetectors.cpp b/Framework/Algorithms/src/AlignDetectors.cpp
index 96d3731959288402f6cb14fb659f9ac7705a4966..d36670cb4709824fe18421e758097dbedd84491f 100644
--- a/Framework/Algorithms/src/AlignDetectors.cpp
+++ b/Framework/Algorithms/src/AlignDetectors.cpp
@@ -318,5 +318,16 @@ void AlignDetectors::align(const ConversionFactors &converter,
   outputWS.clearMRU();
 }
 
+Parallel::ExecutionMode AlignDetectors::getParallelExecutionMode(
+    const std::map<std::string, Parallel::StorageMode> &storageModes) const {
+  using namespace Parallel;
+  const auto inputMode = storageModes.at("InputWorkspace");
+  const auto &calibrationMode = storageModes.find("CalibrationWorkspace");
+  if (calibrationMode != storageModes.end())
+    if (calibrationMode->second != StorageMode::Cloned)
+      return ExecutionMode::Invalid;
+  return getCorrespondingExecutionMode(inputMode);
+}
+
 } // namespace Algorithms
 } // namespace Mantid
diff --git a/Framework/Algorithms/src/CalculateDIFC.cpp b/Framework/Algorithms/src/CalculateDIFC.cpp
index 99b76c3c5373cfd73f72352ceb9e4908cbb4f5a5..205b266aadc64a86b89a59237c544bbd7abb04f6 100644
--- a/Framework/Algorithms/src/CalculateDIFC.cpp
+++ b/Framework/Algorithms/src/CalculateDIFC.cpp
@@ -1,4 +1,5 @@
 #include "MantidAlgorithms/CalculateDIFC.h"
+#include "MantidAPI/ITableWorkspace.h"
 #include "MantidDataObjects/SpecialWorkspace2D.h"
 #include "MantidGeometry/IDetector.h"
 
@@ -6,39 +7,54 @@ namespace Mantid {
 
 namespace {
 
-void calculate(API::Progress &progress, API::MatrixWorkspace_sptr &outputWs,
-               DataObjects::OffsetsWorkspace_sptr &offsetsWS,
-               const Geometry::DetectorInfo &detectorInfo) {
-  DataObjects::SpecialWorkspace2D_sptr localWS =
-      boost::dynamic_pointer_cast<DataObjects::SpecialWorkspace2D>(outputWs);
-
+// calculate DIFC using the geometry and
+void calculateFromOffset(API::Progress &progress,
+                         DataObjects::SpecialWorkspace2D &outputWs,
+                         const DataObjects::OffsetsWorkspace *const offsetsWS,
+                         const Geometry::DetectorInfo &detectorInfo) {
   const auto &detectorIDs = detectorInfo.detectorIDs();
-
+  const bool haveOffset = (offsetsWS != nullptr);
   const double l1 = detectorInfo.l1();
-  // Now go through all
+
   for (size_t i = 0; i < detectorInfo.size(); ++i) {
     if ((!detectorInfo.isMasked(i)) && (!detectorInfo.isMonitor(i))) {
-      double offset = 0.;
-      if (offsetsWS)
-        offset = offsetsWS->getValue(detectorIDs[i], 0.);
-
-      double difc = Geometry::Conversion::tofToDSpacingFactor(
-          l1, detectorInfo.l2(i), detectorInfo.twoTheta(i), offset);
-      difc = 1. / difc; // tofToDSpacingFactor gives 1/DIFC
-      localWS->setValue(detectorIDs[i], difc);
+      // offset=0 means that geometry is correct
+      const double offset =
+          (haveOffset) ? offsetsWS->getValue(detectorIDs[i], 0.) : 0.;
+
+      // tofToDSpacingFactor gives 1/DIFC
+      double difc =
+          1. / Geometry::Conversion::tofToDSpacingFactor(
+                   l1, detectorInfo.l2(i), detectorInfo.twoTheta(i), offset);
+      outputWs.setValue(detectorIDs[i], difc);
     }
 
     progress.report("Calculate DIFC");
   }
 }
+
+// look through the columns of detid and difc and put it into the
+// SpecialWorkspace2D
+void calculateFromTable(API::Progress &progress,
+                        DataObjects::SpecialWorkspace2D &outputWs,
+                        const API::ITableWorkspace &calibWs) {
+  API::ConstColumnVector<double> difcCol = calibWs.getVector("difc");
+  API::ConstColumnVector<int> detIDs = calibWs.getVector("detid");
+
+  const size_t numRows = detIDs.size();
+  for (size_t i = 0; i < numRows; ++i) {
+    outputWs.setValue(detIDs[i], difcCol[i]);
+    progress.report("Calculate DIFC");
+  }
 }
+} // namespace
 
 namespace Algorithms {
 
 using Mantid::API::MatrixWorkspace;
 using Mantid::API::WorkspaceProperty;
 using Mantid::DataObjects::OffsetsWorkspace;
-using Mantid::DataObjects::OffsetsWorkspace_sptr;
+using Mantid::DataObjects::OffsetsWorkspace_const_sptr;
 using Mantid::DataObjects::SpecialWorkspace2D;
 using Mantid::DataObjects::SpecialWorkspace2D_sptr;
 using Mantid::Geometry::Instrument_const_sptr;
@@ -75,12 +91,34 @@ void CalculateDIFC::init() {
   declareProperty(Kernel::make_unique<WorkspaceProperty<MatrixWorkspace>>(
                       "OutputWorkspace", "", Direction::Output),
                   "Workspace containing DIFC for each pixel");
-  declareProperty(
-      Kernel::make_unique<WorkspaceProperty<OffsetsWorkspace>>(
-          "OffsetsWorkspace", "", Direction::Input,
-          Mantid::API::PropertyMode::Optional),
-      "Optional: A OffsetsWorkspace containing the calibration offsets. Either "
-      "this or CalibrationFile needs to be specified.");
+  declareProperty(Kernel::make_unique<WorkspaceProperty<API::ITableWorkspace>>(
+                      "CalibrationWorkspace", "", Direction::Input,
+                      Mantid::API::PropertyMode::Optional),
+                  "Optional: A OffsetsWorkspace containing the calibration "
+                  "offsets.  This cannot be specified with an "
+                  "OffsetsWorkspace.");
+  declareProperty(Kernel::make_unique<WorkspaceProperty<OffsetsWorkspace>>(
+                      "OffsetsWorkspace", "", Direction::Input,
+                      Mantid::API::PropertyMode::Optional),
+                  "Optional: A OffsetsWorkspace containing the calibration "
+                  "offsets. This cannot be specified with a "
+                  "CalibrationWorkspace.");
+}
+
+std::map<std::string, std::string> CalculateDIFC::validateInputs() {
+  std::map<std::string, std::string> result;
+
+  OffsetsWorkspace_const_sptr offsetsWS = getProperty("OffsetsWorkspace");
+  API::ITableWorkspace_const_sptr calibrationWS =
+      getProperty("CalibrationWorkspace");
+
+  if ((bool(offsetsWS)) && (bool(calibrationWS))) {
+    std::string msg = "Only specify calibration one way";
+    result["OffsetsWorkspace"] = msg;
+    result["CalibrationWorkspace"] = msg;
+  }
+
+  return result;
 }
 
 //----------------------------------------------------------------------------------------------
@@ -88,8 +126,9 @@ void CalculateDIFC::init() {
  */
 void CalculateDIFC::exec() {
 
-  DataObjects::OffsetsWorkspace_sptr offsetsWs =
+  DataObjects::OffsetsWorkspace_const_sptr offsetsWs =
       getProperty("OffsetsWorkspace");
+  API::ITableWorkspace_const_sptr calibWs = getProperty("CalibrationWorkspace");
   API::MatrixWorkspace_sptr inputWs = getProperty("InputWorkspace");
   API::MatrixWorkspace_sptr outputWs = getProperty("OutputWorkspace");
 
@@ -100,10 +139,19 @@ void CalculateDIFC::exec() {
     outputWs->setTitle("DIFC workspace");
   }
 
-  const auto &detectorInfo = inputWs->detectorInfo();
+  // convert to actual type being used
+  DataObjects::SpecialWorkspace2D_sptr outputSpecialWs =
+      boost::dynamic_pointer_cast<DataObjects::SpecialWorkspace2D>(outputWs);
 
-  API::Progress progress(this, 0.0, 1.0, detectorInfo.size());
-  calculate(progress, outputWs, offsetsWs, detectorInfo);
+  API::Progress progress(this, 0.0, 1.0, inputWs->getNumberHistograms());
+  if (bool(calibWs)) {
+    calculateFromTable(progress, *outputSpecialWs, *calibWs);
+  } else {
+    // this method handles calculating from instrument geometry as well
+    const auto &detectorInfo = inputWs->detectorInfo();
+    calculateFromOffset(progress, *outputSpecialWs, offsetsWs.get(),
+                        detectorInfo);
+  }
 
   setProperty("OutputWorkspace", outputWs);
 }
diff --git a/Framework/Algorithms/src/ConjoinXRuns.cpp b/Framework/Algorithms/src/ConjoinXRuns.cpp
index 7d25bc957887fdbfbda6764afc557aec4e592467..2fea96c2810fa1c8868ab1ea3d041d4dcbb73628 100644
--- a/Framework/Algorithms/src/ConjoinXRuns.cpp
+++ b/Framework/Algorithms/src/ConjoinXRuns.cpp
@@ -120,6 +120,13 @@ std::map<std::string, std::string> ConjoinXRuns::validateInputs() {
       issues[INPUT_WORKSPACE_PROPERTY] +=
           "Workspace " + ws->getName() + " is not a point-data\n";
     } else {
+      try {
+        ws->blocksize();
+      } catch (std::length_error) {
+        issues[INPUT_WORKSPACE_PROPERTY] +=
+            "Workspace " + ws->getName() +
+            " has different number of points per histogram\n";
+      }
       m_inputWS.push_back(ws);
     }
   }
@@ -214,33 +221,28 @@ std::vector<double> ConjoinXRuns::getXAxis(MatrixWorkspace_sptr ws) const {
 
   std::vector<double> axis;
   axis.reserve(ws->blocksize());
-
-  if (m_logEntry.empty()) {
-    // return the actual x-axis of the first spectrum
-    axis = ws->x(0).rawData();
+  auto &run = ws->run();
+  // try time series first
+  TimeSeriesProperty<double> *timeSeriesDouble(nullptr);
+  timeSeriesDouble =
+      dynamic_cast<TimeSeriesProperty<double> *>(run.getLogData(m_logEntry));
+  if (timeSeriesDouble) {
+    // try double series
+    axis = timeSeriesDouble->filteredValuesAsVector();
   } else {
-    auto &run = ws->run();
-    // try time series first
-    TimeSeriesProperty<double> *timeSeriesDouble(nullptr);
-    timeSeriesDouble =
-        dynamic_cast<TimeSeriesProperty<double> *>(run.getLogData(m_logEntry));
-    if (timeSeriesDouble) {
-      // try double series
-      axis = timeSeriesDouble->filteredValuesAsVector();
+    // try int series next
+    TimeSeriesProperty<int> *timeSeriesInt(nullptr);
+    timeSeriesInt =
+        dynamic_cast<TimeSeriesProperty<int> *>(run.getLogData(m_logEntry));
+    if (timeSeriesInt) {
+      std::vector<int> intAxis = timeSeriesInt->filteredValuesAsVector();
+      axis = std::vector<double>(intAxis.begin(), intAxis.end());
     } else {
-      // try int series next
-      TimeSeriesProperty<int> *timeSeriesInt(nullptr);
-      timeSeriesInt =
-          dynamic_cast<TimeSeriesProperty<int> *>(run.getLogData(m_logEntry));
-      if (timeSeriesInt) {
-        std::vector<int> intAxis = timeSeriesInt->filteredValuesAsVector();
-        axis = std::vector<double>(intAxis.begin(), intAxis.end());
-      } else {
-        // then scalar
-        axis.push_back(run.getPropertyAsSingleValue(m_logEntry));
-      }
+      // then scalar
+      axis.push_back(run.getPropertyAsSingleValue(m_logEntry));
     }
   }
+
   return axis;
 }
 
@@ -271,19 +273,29 @@ void ConjoinXRuns::fillHistory() {
 void ConjoinXRuns::joinSpectrum(int64_t wsIndex) {
   std::vector<double> spectrum;
   std::vector<double> errors;
+  std::vector<double> axis;
   spectrum.reserve(m_outWS->blocksize());
   errors.reserve(m_outWS->blocksize());
+  axis.reserve(m_outWS->blocksize());
   size_t index = static_cast<size_t>(wsIndex);
 
   for (const auto &input : m_inputWS) {
     auto y = input->y(index).rawData();
     auto e = input->e(index).rawData();
+    std::vector<double> x;
+    if (m_logEntry.empty()) {
+      x = input->x(index).rawData();
+    } else {
+      x = m_axisCache[input->getName()];
+    }
     spectrum.insert(spectrum.end(), y.begin(), y.end());
     errors.insert(errors.end(), e.begin(), e.end());
+    axis.insert(axis.end(), x.begin(), x.end());
   }
 
   m_outWS->mutableY(index) = spectrum;
   m_outWS->mutableE(index) = errors;
+  m_outWS->mutableX(index) = axis;
 }
 
 //----------------------------------------------------------------------------------------------
@@ -319,10 +331,6 @@ void ConjoinXRuns::exec() {
   }
 
   auto first = m_inputWS.front();
-  std::vector<double> axisFirst = getXAxis(first);
-  std::vector<double> xAxis;
-  xAxis.insert(xAxis.end(), axisFirst.begin(), axisFirst.end());
-
   SampleLogsBehaviour sampleLogsBehaviour = SampleLogsBehaviour(
       *first, g_log, sampleLogsSum, sampleLogsTimeSeries, sampleLogsList,
       sampleLogsWarn, sampleLogsWarnTolerances, sampleLogsFail,
@@ -337,14 +345,14 @@ void ConjoinXRuns::exec() {
   // to be skipped, to compute the size of the output respectively.
   MatrixWorkspace_uptr temp = first->clone();
 
-  // First sequentially merge the sample logs and build the x-axis
+  size_t outBlockSize = (*it)->blocksize();
+  // First sequentially merge the sample logs
   for (++it; it != m_inputWS.end(); ++it) {
     // attempt to merge the sample logs
     try {
       sampleLogsBehaviour.mergeSampleLogs(**it, *temp);
       sampleLogsBehaviour.setUpdatedSampleLogs(*temp);
-      std::vector<double> axisIt = getXAxis(*it);
-      xAxis.insert(xAxis.end(), axisIt.begin(), axisIt.end());
+      outBlockSize += (*it)->blocksize();
     } catch (std::invalid_argument &e) {
       if (sampleLogsFailBehaviour == SKIP_BEHAVIOUR) {
         g_log.error() << "Could not join workspace: " << (*it)->getName()
@@ -366,8 +374,13 @@ void ConjoinXRuns::exec() {
     // the x-axis might need to be changed
   }
 
+  if (!m_logEntry.empty()) {
+    for (const auto &ws : m_inputWS) {
+      m_axisCache[ws->getName()] = getXAxis(ws);
+    }
+  }
+
   // now get the size of the output
-  size_t outBlockSize = xAxis.size();
   size_t numSpec = first->getNumberHistograms();
 
   m_outWS = WorkspaceFactory::Instance().create(first, numSpec, outBlockSize,
@@ -382,7 +395,6 @@ void ConjoinXRuns::exec() {
   PARALLEL_FOR_IF(threadSafe(*m_outWS))
   for (int64_t index = 0; index < static_cast<int64_t>(numSpec); ++index) {
     PARALLEL_START_INTERUPT_REGION
-    m_outWS->mutableX(static_cast<size_t>(index)) = xAxis;
     joinSpectrum(index);
     m_progress->report();
     PARALLEL_END_INTERUPT_REGION
@@ -399,6 +411,8 @@ void ConjoinXRuns::exec() {
   }
 
   setProperty("OutputWorkspace", m_outWS);
+  m_inputWS.clear();
+  m_axisCache.clear();
 }
 } // namespace Algorithms
 } // namespace Mantid
diff --git a/Framework/Algorithms/src/DiffractionFocussing2.cpp b/Framework/Algorithms/src/DiffractionFocussing2.cpp
index 875954fbee9fa68cd2a12920b9a98e9431644b36..3924340f985afc8302f547d153d09db3bb731ea1 100644
--- a/Framework/Algorithms/src/DiffractionFocussing2.cpp
+++ b/Framework/Algorithms/src/DiffractionFocussing2.cpp
@@ -164,15 +164,14 @@ void DiffractionFocussing2::exec() {
     throw std::runtime_error("No points found in the data range.");
   }
   API::MatrixWorkspace_sptr out = API::WorkspaceFactory::Instance().create(
-      m_matrixInputW, nGroups, nPoints + 1, nPoints);
+      m_matrixInputW, m_validGroups.size(), nPoints + 1, nPoints);
   // Caching containers that are either only read from or unused. Initialize
   // them once.
   // Helgrind will show a race-condition but the data is completely unused so it
   // is irrelevant
   MantidVec weights_default(1, 1.0), emptyVec(1, 0.0), EOutDummy(nPoints);
 
-  std::unique_ptr<Progress> prog = make_unique<API::Progress>(
-      this, 0.2, 1.0, static_cast<int>(totalHistProcess) + nGroups);
+  Progress prog(this, 0.2, 1.0, static_cast<int>(totalHistProcess) + nGroups);
 
   PARALLEL_FOR_IF(Kernel::threadSafe(*m_matrixInputW, *out))
   for (int outWorkspaceIndex = 0;
@@ -182,16 +181,15 @@ void DiffractionFocussing2::exec() {
     int group = static_cast<int>(m_validGroups[outWorkspaceIndex]);
 
     // Get the group
-    auto it = group2xvector.find(group);
-    auto dif = std::distance(group2xvector.begin(), it);
-    auto &Xout = it->second;
+    auto &Xout = group2xvector.at(group);
 
     // Assign the new X axis only once (i.e when this group is encountered the
     // first time)
-    out->setBinEdges(static_cast<int64_t>(dif), Xout);
+    out->setBinEdges(outWorkspaceIndex, Xout);
 
     // This is the output spectrum
     auto &outSpec = out->getSpectrum(outWorkspaceIndex);
+    outSpec.setSpectrumNo(group);
 
     // Get the references to Y and E output and rebin
     // TODO can only be changed once rebin implemented in HistogramData
@@ -213,6 +211,7 @@ void DiffractionFocussing2::exec() {
       auto &Xin = inSpec.x();
       auto &Yin = inSpec.y();
       auto &Ein = inSpec.e();
+      outSpec.addDetectorIDs(inSpec.getDetectorIDs());
 
       try {
         // TODO This should be implemented in Histogram as rebin
@@ -281,7 +280,7 @@ void DiffractionFocussing2::exec() {
         VectorHelper::rebin(limits, weights_default, emptyVec, Xout.rawData(),
                             groupWgt, EOutDummy, true, true);
       }
-      prog->report();
+      prog.report();
     } // end of loop for input spectra
 
     // Calculate the bin widths
@@ -306,20 +305,18 @@ void DiffractionFocussing2::exec() {
     std::transform(Eout.begin(), Eout.end(), groupWgt.begin(), Eout.begin(),
                    std::divides<double>());
     // Now multiply by the number of spectra in the group
-    std::transform(Yout.begin(), Yout.end(), Yout.begin(),
-                   std::bind2nd(std::multiplies<double>(), groupSize));
-    std::transform(Eout.begin(), Eout.end(), Eout.begin(),
-                   std::bind2nd(std::multiplies<double>(), groupSize));
-
-    prog->report();
+    std::for_each(Yout.begin(), Yout.end(), [groupSize](double &val) {
+      val *= static_cast<double>(groupSize);
+    });
+    std::for_each(Eout.begin(), Eout.end(), [groupSize](double &val) {
+      val *= static_cast<double>(groupSize);
+    });
+
+    prog.report();
     PARALLEL_END_INTERUPT_REGION
   } // end of loop for groups
   PARALLEL_CHECK_INTERUPT_REGION
 
-  out->setIndexInfo(Indexing::group(m_matrixInputW->indexInfo(),
-                                    std::move(m_validGroups),
-                                    std::move(m_wsIndices)));
-
   setProperty("OutputWorkspace", out);
 
   this->cleanup();
@@ -634,7 +631,8 @@ void DiffractionFocussing2::determineRebinParameters() {
  */
 size_t DiffractionFocussing2::setupGroupToWSIndices() {
   // set up the mapping of group to input workspace index
-  this->m_wsIndices.reserve(this->nGroups + 1);
+  std::vector<std::vector<std::size_t>> wsIndices;
+  wsIndices.reserve(this->nGroups + 1);
   size_t nHist_st = static_cast<size_t>(nHist);
   for (size_t wi = 0; wi < nHist_st; wi++) {
     // wi is the workspace index (of the input)
@@ -643,28 +641,24 @@ size_t DiffractionFocussing2::setupGroupToWSIndices() {
       continue;
 
     // resize the ws_indices if it is not big enough
-    if (this->m_wsIndices.size() < static_cast<size_t>(group + 1)) {
-      this->m_wsIndices.resize(group + 1);
+    if (wsIndices.size() < static_cast<size_t>(group + 1)) {
+      wsIndices.resize(group + 1);
     }
 
     // Also record a list of workspace indices
-    this->m_wsIndices[group].push_back(wi);
+    wsIndices[group].push_back(wi);
   }
 
   // initialize a vector of the valid group numbers
-  this->m_validGroups.reserve(nGroups);
   size_t totalHistProcess = 0;
-  for (size_t i = 0; i < this->m_wsIndices.size(); i++) {
-    if (!(this->m_wsIndices[i].empty())) {
-      this->m_validGroups.push_back(static_cast<int>(i));
-      totalHistProcess += this->m_wsIndices[i].size();
-    }
+  for (const auto &item : group2xvector) {
+    const auto group = item.first;
+    m_validGroups.push_back(group);
+    totalHistProcess += wsIndices[group].size();
   }
 
-  m_wsIndices.erase(
-      std::remove_if(m_wsIndices.begin(), m_wsIndices.end(),
-                     [](const std::vector<size_t> &v) { return v.empty(); }),
-      m_wsIndices.end());
+  for (const auto &group : m_validGroups)
+    m_wsIndices.push_back(std::move(wsIndices[static_cast<int>(group)]));
 
   return totalHistProcess;
 }
diff --git a/Framework/Algorithms/src/FFTSmooth2.cpp b/Framework/Algorithms/src/FFTSmooth2.cpp
index 75c356171f22a40f30c3b11fdf710f9847b14ab4..bc71a5c60f1cfd3e3fea15a51061b8469d033ada 100644
--- a/Framework/Algorithms/src/FFTSmooth2.cpp
+++ b/Framework/Algorithms/src/FFTSmooth2.cpp
@@ -1,6 +1,3 @@
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
 #include "MantidAlgorithms/FFTSmooth2.h"
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/TextAxis.h"
diff --git a/Framework/Algorithms/src/FindPeaks.cpp b/Framework/Algorithms/src/FindPeaks.cpp
index 928402b9f6f9974705e56e3f867401a367485168..4fea557494d455caf578d19ebc4ffebf68722376 100644
--- a/Framework/Algorithms/src/FindPeaks.cpp
+++ b/Framework/Algorithms/src/FindPeaks.cpp
@@ -41,7 +41,7 @@ DECLARE_ALGORITHM(FindPeaks)
 /** Constructor
   */
 FindPeaks::FindPeaks()
-    : API::Algorithm(), m_peakParameterNames(), m_bkgdParameterNames(),
+    : API::ParallelAlgorithm(), m_peakParameterNames(), m_bkgdParameterNames(),
       m_bkgdOrder(0), m_outPeakTableWS(), m_dataWS(), m_inputPeakFWHM(0),
       m_highBackground(false), m_rawPeaksTable(false), m_numTableParams(0),
       m_centreIndex(1) /* for Gaussian */, m_peakFuncType(""),
diff --git a/Framework/Algorithms/src/LineProfile.cpp b/Framework/Algorithms/src/LineProfile.cpp
index 4dd9b0471ecf233bb0c480b5c1f7732f34d92241..0410bebd3bdae698f9e9ed21989e2f5b622df727 100644
--- a/Framework/Algorithms/src/LineProfile.cpp
+++ b/Framework/Algorithms/src/LineProfile.cpp
@@ -297,7 +297,7 @@ void LineProfile::init() {
                       PropertyNames::INPUT_WORKSPACE, "", Direction::Input,
                       inputWorkspaceValidator),
                   "An input workspace.");
-  declareProperty(Kernel::make_unique<WorkspaceProperty<Workspace2D>>(
+  declareProperty(Kernel::make_unique<WorkspaceProperty<MatrixWorkspace>>(
                       PropertyNames::OUTPUT_WORKSPACE, "", Direction::Output),
                   "A single histogram workspace containing the profile.");
   declareProperty(PropertyNames::CENTRE, EMPTY_DBL(), mandatoryDouble,
diff --git a/Framework/Algorithms/src/PolarizationEfficiencyCor.cpp b/Framework/Algorithms/src/PolarizationEfficiencyCor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..553dcd3ab9573e8e92ee3d3fe285e4610c53fd5e
--- /dev/null
+++ b/Framework/Algorithms/src/PolarizationEfficiencyCor.cpp
@@ -0,0 +1,1032 @@
+#include "MantidAlgorithms/PolarizationEfficiencyCor.h"
+
+#include "MantidAPI/ADSValidator.h"
+#include "MantidAPI/Axis.h"
+#include "MantidAPI/MatrixWorkspace.h"
+#include "MantidAPI/WorkspaceGroup.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+#include "MantidKernel/ArrayProperty.h"
+#include "MantidKernel/ListValidator.h"
+#include "MantidKernel/StringTokenizer.h"
+
+#include <Eigen/Dense>
+#include <boost/math/special_functions/pow.hpp>
+
+namespace {
+/// Property names.
+namespace Prop {
+static const std::string FLIPPERS{"Flippers"};
+static const std::string EFFICIENCIES{"Efficiencies"};
+static const std::string INPUT_WS{"InputWorkspaces"};
+static const std::string OUTPUT_WS{"OutputWorkspace"};
+} // namespace Prop
+
+/// Flipper configurations.
+namespace Flippers {
+static const std::string Off{"0"};
+static const std::string OffOff{"00"};
+static const std::string OffOn{"01"};
+static const std::string On{"1"};
+static const std::string OnOff{"10"};
+static const std::string OnOn{"11"};
+} // namespace Flippers
+
+/**
+ * Parse a flipper configuration string.
+ * @param setupString a configuration string
+ * @return a vector of individual configurations
+ */
+std::vector<std::string> parseFlipperSetup(const std::string &setupString) {
+  using Mantid::Kernel::StringTokenizer;
+  StringTokenizer tokens{setupString, ",", StringTokenizer::TOK_TRIM};
+  return std::vector<std::string>{tokens.begin(), tokens.end()};
+}
+
+/**
+ * Throw if given ws is nullptr.
+ * @param ws a workspace to check
+ * @param tag a flipper configuration for the error message
+ */
+void checkInputExists(const Mantid::API::MatrixWorkspace_sptr &ws,
+                      const std::string &tag) {
+  if (!ws) {
+    throw std::runtime_error("A workspace designated as " + tag +
+                             " is missing in inputs.");
+  }
+}
+
+/**
+ * Calculate the corrected intensities and error estimates.
+ * @param corrected an output vector for R00, R01, R10 and R11
+ * @param errors an output vector for the error estimates
+ * @param ppy intensity I00
+ * @param ppyE error of ppy
+ * @param pmy intensity I01
+ * @param pmyE error of pmy
+ * @param mpy intensity I10
+ * @param mpyE error of mpy
+ * @param mmy intensity I11
+ * @param mmyE error of mmy
+ * @param f1 polarizer efficiency
+ * @param f1E error of f1
+ * @param f2 analyzer efficiency
+ * @param f2E error of f2
+ * @param p1 polarizer flipper efficiency
+ * @param p1E error of p1
+ * @param p2 analyzer flipper efficiency
+ * @param p2E error of p2
+ */
+void fourInputsCorrectedAndErrors(
+    Eigen::Vector4d &corrected, Eigen::Vector4d &errors, const double ppy,
+    const double ppyE, const double pmy, const double pmyE, const double mpy,
+    const double mpyE, const double mmy, const double mmyE, const double f1,
+    const double f1E, const double f2, const double f2E, const double p1,
+    const double p1E, const double p2, const double p2E) {
+  using namespace boost::math;
+  // Note that f1 and f2 correspond to 1-F1 and 1-F2 in [Wildes, 1999].
+  // These are inverted forms of the efficiency matrices.
+  const auto diag1 = 1. / f1;
+  const auto off1 = (f1 - 1.) / f1;
+  Eigen::Matrix4d F1m;
+  F1m << 1., 0., 0., 0., 0., 1., 0., 0., off1, 0., diag1, 0., 0., off1, 0.,
+      diag1;
+  const auto diag2 = 1. / f2;
+  const auto off2 = (f2 - 1.) / f2;
+  Eigen::Matrix4d F2m;
+  F2m << 1., 0., 0., 0., off2, diag2, 0., 0., 0., 0., 1., 0., 0., 0., off2,
+      diag2;
+  const auto diag3 = (p1 - 1.) / (2. * p1 - 1.);
+  const auto off3 = p1 / (2. * p1 - 1);
+  Eigen::Matrix4d P1m;
+  P1m << diag3, 0, off3, 0, 0, diag3, 0, off3, off3, 0, diag3, 0, 0, off3, 0,
+      diag3;
+  const auto diag4 = (p2 - 1.) / (2. * p2 - 1.);
+  const auto off4 = p2 / (2. * p2 - 1.);
+  Eigen::Matrix4d P2m;
+  P2m << diag4, off4, 0., 0., off4, diag4, 0., 0., 0., 0., diag4, off4, 0., 0.,
+      off4, diag4;
+  const Eigen::Vector4d intensities(ppy, pmy, mpy, mmy);
+  const auto FProduct = F2m * F1m;
+  const auto PProduct = P2m * P1m;
+  const auto PFProduct = PProduct * FProduct;
+  corrected = PFProduct * intensities;
+  // The error matrices here are element-wise algebraic derivatives of
+  // the matrices above, multiplied by the error.
+  const auto elemE1 = -1. / pow<2>(f1) * f1E;
+  Eigen::Matrix4d F1Em;
+  F1Em << 0., 0., 0., 0., 0., 0., 0., 0., -elemE1, 0., elemE1, 0., 0., -elemE1,
+      0., elemE1;
+  const auto elemE2 = -1. / pow<2>(f2) * f2E;
+  Eigen::Matrix4d F2Em;
+  F2Em << 0., 0., 0., 0., -elemE2, elemE2, 0., 0., 0., 0., 0., 0., 0., 0.,
+      -elemE2, elemE2;
+  const auto elemE3 = 1. / pow<2>(2. * p1 - 1.) * p1E;
+  Eigen::Matrix4d P1Em;
+  P1Em << elemE3, 0., -elemE3, 0., 0., elemE3, 0., -elemE3, -elemE3, 0., elemE3,
+      0., 0., -elemE3, 0., elemE3;
+  const auto elemE4 = 1. / pow<2>(2. * p2 - 1.) * p2E;
+  Eigen::Matrix4d P2Em;
+  P2Em << elemE4, -elemE4, 0., 0., -elemE4, elemE4, 0., 0., 0., 0., elemE4,
+      -elemE4, 0., 0., -elemE4, elemE4;
+  const Eigen::Vector4d yErrors(ppyE, pmyE, mpyE, mmyE);
+  const auto e1 = (P2Em * P1m * FProduct * intensities).array();
+  const auto e2 = (P2m * P1Em * FProduct * intensities).array();
+  const auto e3 = (PProduct * F2Em * F1m * intensities).array();
+  const auto e4 = (PProduct * F2m * F1Em * intensities).array();
+  const auto sqPFProduct = (PFProduct.array() * PFProduct.array()).matrix();
+  const auto sqErrors = (yErrors.array() * yErrors.array()).matrix();
+  const auto e5 = (sqPFProduct * sqErrors).array();
+  errors = (e1 * e1 + e2 * e2 + e3 * e3 + e4 * e4 + e5).sqrt();
+}
+
+/**
+ * Estimate errors for I01 in the two inputs case.
+ * @param i00 intensity of 00 flipper configuration
+ * @param e00 error of i00
+ * @param i11 intensity of 11 flipper configuration
+ * @param e11 error of i11
+ * @param p1 polarizer efficiency
+ * @param p1E error of p1
+ * @param p2 analyzer efficiency
+ * @param p2E error of p2
+ * @param f1 polarizer flipper efficiency
+ * @param f1E error of f1
+ * @param f2 analyzer flipper efficiency
+ * @param f2E error of f2
+ * @return the error estimate
+ */
+double twoInputsErrorEstimate01(const double i00, const double e00,
+                                const double i11, const double e11,
+                                const double p1, const double p1E,
+                                const double p2, const double p2E,
+                                const double f1, const double f1E,
+                                const double f2, const double f2E) {
+  using namespace boost::math;
+  // Derivatives of the equation which solves the I01 intensities
+  // with respect to i00, i11, f1, etc.
+  const auto pmdi00 =
+      -((f1 * (-1. + 2. * p1) *
+         (-f2 * pow<2>(1. - 2. * p2) + pow<2>(f2) * pow<2>(1. - 2. * p2) +
+          (-1. + p2) * p2)) /
+        (f2 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) +
+         f1 * (-1. + 2. * p1) *
+             ((1. - p2) * p2 + f2 * (-1. + p1 + p2) * (-1. + 2. * p2))));
+  const auto pmdi11 =
+      (f2 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2)) /
+      (f2 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) +
+       f1 * (-1. + 2. * p1) *
+           ((1. - p2) * p2 + f2 * (-1. + p1 + p2) * (-1. + 2. * p2)));
+  const auto pmdf1 =
+      -(((-1. + 2. * p1) *
+         ((1. - p2) * p2 + f2 * (-1. + p1 + p2) * (-1. + 2. * p2)) *
+         (f2 * i11 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) -
+          f1 * i00 * (-1. + 2. * p1) *
+              (-f2 * pow<2>(1. - 2. * p2) + pow<2>(f2) * pow<2>(1. - 2. * p2) +
+               (-1. + p2) * p2))) /
+        pow<2>(f2 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) +
+               f1 * (-1. + 2. * p1) *
+                   ((1. - p2) * p2 + f2 * (-1. + p1 + p2) * (-1. + 2. * p2)))) -
+      (i00 * (-1. + 2. * p1) *
+       (-f2 * pow<2>(1. - 2. * p2) + pow<2>(f2) * pow<2>(1. - 2. * p2) +
+        (-1. + p2) * p2)) /
+          (f2 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) +
+           f1 * (-1. + 2. * p1) *
+               ((1. - p2) * p2 + f2 * (-1. + p1 + p2) * (-1. + 2. * p2)));
+  const auto pmdf2 =
+      -(((f1 * (-1. + 2. * p1) * (-1. + p1 + p2) * (-1 + 2 * p2) +
+          p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2)) *
+         (f2 * i11 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) -
+          f1 * i00 * (-1. + 2. * p1) *
+              (-f2 * pow<2>(1. - 2. * p2) + pow<2>(f2) * pow<2>(1. - 2. * p2) +
+               (-1. + p2) * p2))) /
+        pow<2>(f2 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) +
+               f1 * (-1. + 2. * p1) *
+                   ((1. - p2) * p2 + f2 * (-1. + p1 + p2) * (-1. + 2. * p2)))) +
+      (-f1 * i00 * (-1. + 2. * p1) *
+           (-pow<2>(1. - 2. * p2) + 2 * f2 * pow<2>(1. - 2. * p2)) +
+       i11 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2)) /
+          (f2 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) +
+           f1 * (-1. + 2. * p1) *
+               ((1. - p2) * p2 + f2 * (-1. + p1 + p2) * (-1. + 2. * p2)));
+  const auto pmdp1 =
+      -(((f2 * i11 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) -
+          f1 * i00 * (-1. + 2. * p1) *
+              (-f2 * pow<2>(1. - 2. * p2) + pow<2>(f2) * pow<2>(1. - 2. * p2) +
+               (-1. + p2) * p2)) *
+         (f2 * p1 * (1. - 2. * p2) +
+          f1 * f2 * (-1. + 2. * p1) * (-1. + 2. * p2) +
+          f2 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) +
+          2. * f1 *
+              ((1. - p2) * p2 + f2 * (-1. + p1 + p2) * (-1. + 2. * p2)))) /
+        pow<2>(f2 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) +
+               f1 * (-1. + 2. * p1) *
+                   ((1. - p2) * p2 + f2 * (-1. + p1 + p2) * (-1. + 2. * p2)))) +
+      (f2 * i11 * p1 * (1. - 2. * p2) +
+       f2 * i11 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) -
+       2. * f1 * i00 * (-f2 * pow<2>(1. - 2. * p2) +
+                        pow<2>(f2) * pow<2>(1. - 2. * p2) + (-1. + p2) * p2)) /
+          (f2 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) +
+           f1 * (-1. + 2. * p1) *
+               ((1. - p2) * p2 + f2 * (-1. + p1 + p2) * (-1. + 2. * p2)));
+  const auto pmdp2 =
+      -(((f2 * i11 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) -
+          f1 * i00 * (-1. + 2. * p1) *
+              (-f2 * pow<2>(1. - 2. * p2) + pow<2>(f2) * pow<2>(1. - 2. * p2) +
+               (-1. + p2) * p2)) *
+         (f2 * (2. - 2. * p1) * p1 +
+          f1 * (-1. + 2. * p1) * (1. - 2. * p2 + 2. * f2 * (-1. + p1 + p2) +
+                                  f2 * (-1. + 2. * p2)))) /
+        pow<2>(f2 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) +
+               f1 * (-1. + 2. * p1) *
+                   ((1. - p2) * p2 + f2 * (-1. + p1 + p2) * (-1. + 2. * p2)))) +
+      (f2 * i11 * (2. - 2. * p1) * p1 -
+       f1 * i00 * (-1. + 2. * p1) *
+           (-1. + 4. * f2 * (1. - 2. * p2) - 4. * pow<2>(f2) * (1. - 2. * p2) +
+            2. * p2)) /
+          (f2 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) +
+           f1 * (-1. + 2. * p1) *
+               ((1. - p2) * p2 + f2 * (-1. + p1 + p2) * (-1. + 2. * p2)));
+  // Estimate the error components using linearized extrapolation,
+  // sum in squares.
+  const auto e01_I00 = pow<2>(pmdi00 * e00);
+  const auto e01_I11 = pow<2>(pmdi11 * e11);
+  const auto e01_F1 = pow<2>(pmdf1 * f1E);
+  const auto e01_F2 = pow<2>(pmdf2 * f2E);
+  const auto e01_P1 = pow<2>(pmdp1 * p1E);
+  const auto e01_P2 = pow<2>(pmdp2 * p2E);
+  return std::sqrt(e01_I00 + e01_I11 + e01_F1 + e01_F2 + e01_P1 + e01_P2);
+}
+
+/**
+ * Estimate errors for I10 in the two inputs case.
+ * @param i00 intensity of 00 flipper configuration
+ * @param e00 error of i00
+ * @param i11 intensity of 11 flipper configuration
+ * @param e11 error of i11
+ * @param p1 polarizer efficiency
+ * @param p1E error of p1
+ * @param p2 analyzer efficiency
+ * @param p2E error of p2
+ * @param f1 polarizer flipper efficiency
+ * @param f1E error of f1
+ * @param f2 analyzer flipper efficiency
+ * @param f2E error of f2
+ * @return the error estimate
+ */
+double twoInputsErrorEstimate10(const double i00, const double e00,
+                                const double i11, const double e11,
+                                const double p1, const double p1E,
+                                const double p2, const double p2E,
+                                const double f1, const double f1E,
+                                const double f2, const double f2E) {
+  using namespace boost::math;
+  // Derivatives of the equation which solves the I10 intensities
+  // with respect to i00, i11, f1, etc.
+  const auto a = -1. + p1 + 2. * p2 - 2. * p1 * p2;
+  const auto b = -1. + 2. * p1;
+  const auto c = -1. + 2. * p2;
+  const auto d = -1. + p2;
+  const auto mpdi00 = (-pow<2>(f1) * f2 * pow<2>(b) * c +
+                       f1 * f2 * pow<2>(b) * c + f2 * p1 * a) /
+                      (f2 * p1 * a + f1 * b * (-d * p2 + f2 * (p1 + d) * c));
+  const auto mpdi11 = -((f1 * b * d * p2) /
+                        (f2 * p1 * a + f1 * b * (-d * p2 + f2 * (p1 + d) * c)));
+  const auto mpdf1 =
+      -(((-1. + 2. * p1) *
+         ((1. - p2) * p2 + f2 * (-1. + p1 + p2) * (-1. + 2. * p2)) *
+         (-pow<2>(f1) * f2 * i00 * pow<2>(1. - 2. * p1) * (-1. + 2. * p2) +
+          f2 * i00 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) +
+          f1 * (-1. + 2. * p1) *
+              (-i11 * (-1. + p2) * p2 +
+               f2 * i00 * (-1. + 2. * p1) * (-1. + 2. * p2)))) /
+        pow<2>(f2 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) +
+               f1 * (-1. + 2. * p1) *
+                   ((1. - p2) * p2 + f2 * (-1. + p1 + p2) * (-1. + 2. * p2)))) +
+      (-2. * f1 * f2 * i00 * pow<2>(1. - 2. * p1) * (-1. + 2. * p2) +
+       (-1. + 2. * p1) * (-i11 * (-1. + p2) * p2 +
+                          f2 * i00 * (-1. + 2. * p1) * (-1. + 2. * p2))) /
+          (f2 * p1 * (-1. + p1 + 2. * p2 - 2. * p1 * p2) +
+           f1 * (-1. + 2. * p1) *
+               ((1. - p2) * p2 + f2 * (-1. + p1 + p2) * (-1. + 2. * p2)));
+  const auto mpdf2 =
+      -(((f1 * b * (p1 + d) * c + p1 * a) *
+         (-pow<2>(f1) * f2 * i00 * pow<2>(b) * c + f2 * i00 * p1 * a +
+          f1 * b * (-i11 * d * p2 + f2 * i00 * b * c))) /
+        pow<2>(f2 * p1 * a + f1 * b * (-d * p2 + f2 * (p1 + d) * c))) +
+      (-pow<2>(f1) * i00 * pow<2>(b) * c + f1 * i00 * pow<2>(b) * c +
+       i00 * p1 * a) /
+          (f2 * p1 * a + f1 * b * (-d * p2 + f2 * (p1 + d) * c));
+  const auto mpdp1 =
+      -(((-pow<2>(f1) * f2 * i00 * pow<2>(b) * c + f2 * i00 * p1 * a +
+          f1 * b * (-i11 * d * p2 + f2 * i00 * b * c)) *
+         (f2 * p1 * -c + f1 * f2 * b * c + f2 * a +
+          2. * f1 * (-d * p2 + f2 * (p1 + d) * c))) /
+        pow<2>(f2 * p1 * a + f1 * b * (-d * p2 + f2 * (p1 + d) * c))) +
+      (f2 * i00 * p1 * -c + 4. * pow<2>(f1) * f2 * i00 * -b * c +
+       2. * f1 * f2 * i00 * b * c + f2 * i00 * a +
+       2. * f1 * (-i11 * d * p2 + f2 * i00 * b * c)) /
+          (f2 * p1 * a + f1 * b * (-d * p2 + f2 * (p1 + d) * c));
+  const auto mpdp2 =
+      -(((f2 * (2. - 2. * p1) * p1 +
+          f1 * b * (1. - 2. * p2 + 2. * f2 * (p1 + d) + f2 * c)) *
+         (-pow<2>(f1) * f2 * i00 * pow<2>(b) * c + f2 * i00 * p1 * a +
+          f1 * b * (-i11 * d * p2 + f2 * i00 * b * c))) /
+        pow<2>(f2 * p1 * a + f1 * b * (-d * p2 + f2 * (p1 + d) * c))) +
+      (-2. * pow<2>(f1) * f2 * i00 * pow<2>(b) +
+       f2 * i00 * (2. - 2. * p1) * p1 +
+       f1 * b * (2. * f2 * i00 * b - i11 * d - i11 * p2)) /
+          (f2 * p1 * a + f1 * b * (-d * p2 + f2 * (p1 + d) * c));
+  // Estimate the error components using linearized extrapolation,
+  // sum in squares.
+  const auto e10_I00 = pow<2>(mpdi00 * e00);
+  const auto e10_I11 = pow<2>(mpdi11 * e11);
+  const auto e10_F1 = pow<2>(mpdf1 * f1E);
+  const auto e10_F2 = pow<2>(mpdf2 * f2E);
+  const auto e10_P1 = pow<2>(mpdp1 * p1E);
+  const auto e10_P2 = pow<2>(mpdp2 * p2E);
+  return std::sqrt(e10_I00 + e10_I11 + e10_F1 + e10_F2 + e10_P1 + e10_P2);
+}
+} // namespace
+
+namespace Mantid {
+namespace Algorithms {
+
+// Register the algorithm into the AlgorithmFactory
+DECLARE_ALGORITHM(PolarizationEfficiencyCor)
+
+//----------------------------------------------------------------------------------------------
+
+/// Algorithms name for identification. @see Algorithm::name
+const std::string PolarizationEfficiencyCor::name() const {
+  return "PolarizationEfficiencyCor";
+}
+
+/// Algorithm's version for identification. @see Algorithm::version
+int PolarizationEfficiencyCor::version() const { return 1; }
+
+/// Algorithm's category for identification. @see Algorithm::category
+const std::string PolarizationEfficiencyCor::category() const {
+  return "Reflectometry";
+}
+
+/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
+const std::string PolarizationEfficiencyCor::summary() const {
+  return "Corrects a group of polarization analysis workspaces for polarizer "
+         "and analyzer efficiencies.";
+}
+
+/**
+ * Count the non-nullptr workspaces
+ * @return the count on non-nullptr workspaces.
+ */
+size_t PolarizationEfficiencyCor::WorkspaceMap::size() const noexcept {
+  return (mmWS ? 1 : 0) + (mpWS ? 1 : 0) + (pmWS ? 1 : 0) + (ppWS ? 1 : 0);
+}
+
+//----------------------------------------------------------------------------------------------
+/** Initialize the algorithm's properties.
+ */
+void PolarizationEfficiencyCor::init() {
+  declareProperty(Kernel::make_unique<Kernel::ArrayProperty<std::string>>(
+                      Prop::INPUT_WS, "",
+                      boost::make_shared<API::ADSValidator>(),
+                      Kernel::Direction::Input),
+                  "A list of workspaces to be corrected corresponding to the "
+                  "flipper configurations.");
+  declareProperty(
+      Kernel::make_unique<API::WorkspaceProperty<API::WorkspaceGroup>>(
+          Prop::OUTPUT_WS, "", Kernel::Direction::Output),
+      "A group of polarization efficiency corrected workspaces.");
+  const std::string full = Flippers::OffOff + ", " + Flippers::OffOn + ", " +
+                           Flippers::OnOff + ", " + Flippers::OnOn;
+  const std::string missing01 =
+      Flippers::OffOff + ", " + Flippers::OnOff + ", " + Flippers::OnOn;
+  const std::string missing10 =
+      Flippers::OffOff + ", " + Flippers::OffOn + ", " + Flippers::OnOn;
+  const std::string missing0110 = Flippers::OffOff + ", " + Flippers::OnOn;
+  const std::string noAnalyzer = Flippers::Off + ", " + Flippers::On;
+  const std::string directBeam = Flippers::Off;
+  const std::vector<std::string> setups{
+      {full, missing01, missing10, missing0110, noAnalyzer, directBeam}};
+  declareProperty(
+      Prop::FLIPPERS, full,
+      boost::make_shared<Kernel::ListValidator<std::string>>(setups),
+      "Flipper configurations of the input workspaces.");
+  declareProperty(
+      Kernel::make_unique<API::WorkspaceProperty<API::MatrixWorkspace>>(
+          Prop::EFFICIENCIES, "", Kernel::Direction::Input),
+      "A workspace containing the efficiency factors P1, P2, F1 and F2 as "
+      "histograms");
+}
+
+//----------------------------------------------------------------------------------------------
+/** Execute the algorithm.
+ */
+void PolarizationEfficiencyCor::exec() {
+  const std::string flipperProperty = getProperty(Prop::FLIPPERS);
+  const auto flippers = parseFlipperSetup(flipperProperty);
+  const bool analyzer = flippers.front() != "0" && flippers.back() != "1";
+  const auto inputs = mapInputsToDirections(flippers);
+  checkConsistentNumberHistograms(inputs);
+  const EfficiencyMap efficiencies = efficiencyFactors();
+  checkConsistentX(inputs, efficiencies);
+  WorkspaceMap outputs;
+  switch (inputs.size()) {
+  case 1:
+    outputs = directBeamCorrections(inputs, efficiencies);
+    break;
+  case 2:
+    if (analyzer) {
+      outputs = twoInputCorrections(inputs, efficiencies);
+    } else {
+      outputs = analyzerlessCorrections(inputs, efficiencies);
+    }
+    break;
+  case 3:
+    outputs = threeInputCorrections(inputs, efficiencies);
+    break;
+  case 4:
+    outputs = fullCorrections(inputs, efficiencies);
+  }
+  setProperty(Prop::OUTPUT_WS, groupOutput(outputs));
+}
+
+/**
+ * Validate the algorithm's input properties.
+ * @return a map from property names to discovered issues
+ */
+std::map<std::string, std::string> PolarizationEfficiencyCor::validateInputs() {
+  std::map<std::string, std::string> issues;
+  API::MatrixWorkspace_const_sptr factorWS = getProperty(Prop::EFFICIENCIES);
+  const auto &factorAxis = factorWS->getAxis(1);
+  if (!factorAxis) {
+    issues[Prop::EFFICIENCIES] = "The workspace is missing a vertical axis.";
+  } else if (!factorAxis->isText()) {
+    issues[Prop::EFFICIENCIES] =
+        "The vertical axis in the workspace is not text axis.";
+  } else if (factorWS->getNumberHistograms() < 4) {
+    issues[Prop::EFFICIENCIES] =
+        "The workspace should contain at least 4 histograms.";
+  } else {
+    std::vector<std::string> tags{{"P1", "P2", "F1", "F2"}};
+    for (size_t i = 0; i != factorAxis->length(); ++i) {
+      const auto label = factorAxis->label(i);
+      auto found = std::find(tags.begin(), tags.end(), label);
+      if (found != tags.cend()) {
+        std::swap(tags.back(), *found);
+        tags.pop_back();
+      }
+    }
+    if (!tags.empty()) {
+      issues[Prop::EFFICIENCIES] = "A histogram labeled " + tags.front() +
+                                   " is missing from the workspace.";
+    }
+  }
+  const std::vector<std::string> inputs = getProperty(Prop::INPUT_WS);
+  const std::string flipperProperty = getProperty(Prop::FLIPPERS);
+  const auto flippers = parseFlipperSetup(flipperProperty);
+  if (inputs.size() != flippers.size()) {
+    issues[Prop::FLIPPERS] = "The number of flipper configurations does not "
+                             "match the number of input workspaces";
+  }
+  return issues;
+}
+
+/**
+ * Check that all workspaces in inputs have the same number of histograms.
+ * @param inputs a set of workspaces to check
+ */
+void PolarizationEfficiencyCor::checkConsistentNumberHistograms(
+    const WorkspaceMap &inputs) {
+  size_t nHist{0};
+  bool nHistValid{false};
+  // A local helper function to check the number of histograms.
+  auto checkNHist = [&nHist, &nHistValid](const API::MatrixWorkspace_sptr &ws,
+                                          const std::string &tag) {
+    if (nHistValid) {
+      if (nHist != ws->getNumberHistograms()) {
+        throw std::runtime_error("Number of histograms mismatch in " + tag);
+      }
+    } else {
+      nHist = ws->getNumberHistograms();
+      nHistValid = true;
+    }
+  };
+  if (inputs.mmWS) {
+    checkNHist(inputs.mmWS, Flippers::OffOff);
+  }
+  if (inputs.mpWS) {
+    checkNHist(inputs.mpWS, Flippers::OffOn);
+  }
+  if (inputs.pmWS) {
+    checkNHist(inputs.pmWS, Flippers::OnOff);
+  }
+  if (inputs.ppWS) {
+    checkNHist(inputs.ppWS, Flippers::OnOn);
+  }
+}
+
+/**
+ * Check that all workspaces and efficicencies have the same X data.
+ * @param inputs a set of workspaces to check
+ * @param efficiencies efficiencies to check
+ */
+void PolarizationEfficiencyCor::checkConsistentX(
+    const WorkspaceMap &inputs, const EfficiencyMap &efficiencies) {
+  // Compare everything to F1 efficiency.
+  const auto &F1x = efficiencies.F1->x();
+  // A local helper function to check a HistogramX against F1.
+  auto checkX =
+      [&F1x](const HistogramData::HistogramX &x, const std::string &tag) {
+        if (x.size() != F1x.size()) {
+          throw std::runtime_error(
+              "Mismatch of histogram lengths between F1 and " + tag + '.');
+        }
+        for (size_t i = 0; i != x.size(); ++i) {
+          if (x[i] != F1x[i]) {
+            throw std::runtime_error("Mismatch of X data between F1 and " +
+                                     tag + '.');
+          }
+        }
+      };
+  const auto &F2x = efficiencies.F2->x();
+  checkX(F2x, "F2");
+  const auto &P1x = efficiencies.P1->x();
+  checkX(P1x, "P1");
+  const auto &P2x = efficiencies.P2->x();
+  checkX(P2x, "P2");
+  // A local helper function to check an input workspace against F1.
+  auto checkWS = [&F1x, &checkX](const API::MatrixWorkspace_sptr &ws,
+                                 const std::string &tag) {
+    const auto nHist = ws->getNumberHistograms();
+    for (size_t i = 0; i != nHist; ++i) {
+      checkX(ws->x(i), tag);
+    }
+  };
+  if (inputs.mmWS) {
+    checkWS(inputs.mmWS, Flippers::OffOff);
+  }
+  if (inputs.mpWS) {
+    checkWS(inputs.mpWS, Flippers::OffOn);
+  }
+  if (inputs.pmWS) {
+    checkWS(inputs.pmWS, Flippers::OnOff);
+  }
+  if (inputs.ppWS) {
+    checkWS(inputs.ppWS, Flippers::OnOn);
+  }
+}
+
+/**
+ * Make a workspace group out of the given set of workspaces.
+ * The workspaces will be published in the ADS, their names appended by
+ * appropriate suffices.
+ * @param outputs a set of workspaces to group
+ * @return a group workspace
+ */
+API::WorkspaceGroup_sptr
+PolarizationEfficiencyCor::groupOutput(const WorkspaceMap &outputs) {
+  const std::string outWSName = getProperty(Prop::OUTPUT_WS);
+  std::vector<std::string> names;
+  if (outputs.mmWS) {
+    names.emplace_back(outWSName + "_--");
+    API::AnalysisDataService::Instance().addOrReplace(names.back(),
+                                                      outputs.mmWS);
+  }
+  if (outputs.mpWS) {
+    names.emplace_back(outWSName + "_-+");
+    API::AnalysisDataService::Instance().addOrReplace(names.back(),
+                                                      outputs.mpWS);
+  }
+  if (outputs.pmWS) {
+    names.emplace_back(outWSName + "_+-");
+    API::AnalysisDataService::Instance().addOrReplace(names.back(),
+                                                      outputs.pmWS);
+  }
+  if (outputs.ppWS) {
+    names.emplace_back(outWSName + "_++");
+    API::AnalysisDataService::Instance().addOrReplace(names.back(),
+                                                      outputs.ppWS);
+  }
+  auto group = createChildAlgorithm("GroupWorkspaces");
+  group->initialize();
+  group->setProperty("InputWorkspaces", names);
+  group->setProperty("OutputWorkspace", outWSName);
+  group->execute();
+  API::WorkspaceGroup_sptr outWS = group->getProperty("OutputWorkspace");
+  return outWS;
+}
+
+/**
+ * Make a convenience access object to the efficiency factors.
+ * @return an EfficiencyMap object
+ */
+PolarizationEfficiencyCor::EfficiencyMap
+PolarizationEfficiencyCor::efficiencyFactors() {
+  EfficiencyMap e;
+  API::MatrixWorkspace_const_sptr factorWS = getProperty(Prop::EFFICIENCIES);
+  const auto &vertAxis = factorWS->getAxis(1);
+  for (size_t i = 0; i != vertAxis->length(); ++i) {
+    const auto label = vertAxis->label(i);
+    if (label == "P1") {
+      e.P1 = &factorWS->getSpectrum(i);
+    } else if (label == "P2") {
+      e.P2 = &factorWS->getSpectrum(i);
+    } else if (label == "F1") {
+      e.F1 = &factorWS->getSpectrum(i);
+    } else if (label == "F2") {
+      e.F2 = &factorWS->getSpectrum(i);
+    }
+    // Ignore other histograms such as 'Phi' in ILL's efficiency ws.
+  }
+  return e;
+}
+
+/**
+ * Correct a direct beam measurement for non-ideal instrument effects.
+ * Only the non-analyzer, polarizer flipper off case is considered here.
+ * @param inputs a set of workspaces to correct
+ * @param efficiencies a set of efficiency factors
+ * @return set of corrected workspaces
+ */
+PolarizationEfficiencyCor::WorkspaceMap
+PolarizationEfficiencyCor::directBeamCorrections(
+    const WorkspaceMap &inputs, const EfficiencyMap &efficiencies) {
+  using namespace boost::math;
+  checkInputExists(inputs.ppWS, Flippers::Off);
+  WorkspaceMap outputs;
+  outputs.ppWS = DataObjects::create<DataObjects::Workspace2D>(*inputs.ppWS);
+  const size_t nHisto = inputs.ppWS->getNumberHistograms();
+  for (size_t wsIndex = 0; wsIndex != nHisto; ++wsIndex) {
+    const auto &ppY = inputs.ppWS->y(wsIndex);
+    const auto &ppE = inputs.ppWS->e(wsIndex);
+    auto &ppYOut = outputs.ppWS->mutableY(wsIndex);
+    auto &ppEOut = outputs.ppWS->mutableE(wsIndex);
+    for (size_t binIndex = 0; binIndex < ppY.size(); ++binIndex) {
+      const auto P1 = efficiencies.P1->y()[binIndex];
+      const auto P2 = efficiencies.P2->y()[binIndex];
+      const double f = 1. - P1 - P2 + 2. * P1 * P2;
+      ppYOut[binIndex] = ppY[binIndex] / f;
+      const auto P1E = efficiencies.P1->e()[binIndex];
+      const auto P2E = efficiencies.P2->e()[binIndex];
+      const auto e1 = pow<2>(P1E * (2. * P1 - 1.) / pow<2>(f) * ppY[binIndex]);
+      const auto e2 = pow<2>(P2E * (2. * P2 - 1.) / pow<2>(f) * ppY[binIndex]);
+      const auto e3 = pow<2>(ppE[binIndex] / f);
+      const auto errorSum = std::sqrt(e1 + e2 + e3);
+      ppEOut[binIndex] = errorSum;
+    }
+  }
+  return outputs;
+}
+
+/**
+ * Correct for non-ideal instrument effects.
+ * Deals with the case when the data was taken without the analyzer:
+ * only the polarizer flipper is used.
+ * @param inputs a set of workspaces to correct
+ * @param efficiencies a set of efficiency factors
+ * @return a set of corrected workspaces
+ */
+PolarizationEfficiencyCor::WorkspaceMap
+PolarizationEfficiencyCor::analyzerlessCorrections(
+    const WorkspaceMap &inputs, const EfficiencyMap &efficiencies) {
+  using namespace boost::math;
+  checkInputExists(inputs.mmWS, Flippers::On);
+  checkInputExists(inputs.ppWS, Flippers::Off);
+  WorkspaceMap outputs;
+  outputs.mmWS = DataObjects::create<DataObjects::Workspace2D>(*inputs.mmWS);
+  outputs.ppWS = DataObjects::create<DataObjects::Workspace2D>(*inputs.ppWS);
+  const size_t nHisto = inputs.mmWS->getNumberHistograms();
+  for (size_t wsIndex = 0; wsIndex != nHisto; ++wsIndex) {
+    const auto &mmY = inputs.mmWS->y(wsIndex);
+    const auto &mmE = inputs.mmWS->e(wsIndex);
+    const auto &ppY = inputs.ppWS->y(wsIndex);
+    const auto &ppE = inputs.ppWS->e(wsIndex);
+    auto &mmYOut = outputs.mmWS->mutableY(wsIndex);
+    auto &mmEOut = outputs.mmWS->mutableE(wsIndex);
+    auto &ppYOut = outputs.ppWS->mutableY(wsIndex);
+    auto &ppEOut = outputs.ppWS->mutableE(wsIndex);
+    for (size_t binIndex = 0; binIndex < mmY.size(); ++binIndex) {
+      const auto F1 = efficiencies.F1->y()[binIndex];
+      const auto P1 = efficiencies.P1->y()[binIndex];
+      Eigen::Matrix2d F1m;
+      F1m << 1., 0., (F1 - 1.) / F1, 1. / F1;
+      const double divisor = (2. * P1 - 1.);
+      const double diag = (P1 - 1.) / divisor;
+      const double off = P1 / divisor;
+      Eigen::Matrix2d P1m;
+      P1m << diag, off, off, diag;
+      const Eigen::Vector2d intensities(ppY[binIndex], mmY[binIndex]);
+      const auto PFProduct = P1m * F1m;
+      const auto corrected = PFProduct * intensities;
+      ppYOut[binIndex] = corrected[0];
+      mmYOut[binIndex] = corrected[1];
+      const auto F1E = efficiencies.F1->e()[binIndex];
+      const auto P1E = efficiencies.P1->e()[binIndex];
+      const auto elemE1 = -1. / pow<2>(F1) * F1E;
+      Eigen::Matrix2d F1Em;
+      F1Em << 0., 0., -elemE1, elemE1;
+      const auto elemE2 = 1. / pow<2>(divisor) * P1E;
+      Eigen::Matrix2d P1Em;
+      P1Em << elemE2, -elemE2, -elemE2, elemE2;
+      const Eigen::Vector2d errors(ppE[binIndex], mmE[binIndex]);
+      const auto e1 = (P1Em * F1m * intensities).array();
+      const auto e2 = (P1m * F1Em * intensities).array();
+      const auto sqPFProduct = (PFProduct.array() * PFProduct.array()).matrix();
+      const auto sqErrors = (errors.array() * errors.array()).matrix();
+      const auto e3 = (sqPFProduct * sqErrors).array();
+      const auto errorSum = (e1 * e1 + e2 * e2 + e3).sqrt();
+      ppEOut[binIndex] = errorSum[0];
+      mmEOut[binIndex] = errorSum[1];
+    }
+  }
+  return outputs;
+}
+
+/**
+ * Correct for non-ideal instrument effects.
+ * Only 00 and 11 flipper configurations need to be provided;
+ * the missing 01 and 10 data is solved from the assumption that
+ * in the corrected data, R01 = R10 = 0.
+ * @param inputs a set of workspaces to correct
+ * @param efficiencies a set of efficiency factors
+ * @return a set of corrected workspaces
+ */
+PolarizationEfficiencyCor::WorkspaceMap
+PolarizationEfficiencyCor::twoInputCorrections(
+    const WorkspaceMap &inputs, const EfficiencyMap &efficiencies) {
+  using namespace boost::math;
+  checkInputExists(inputs.mmWS, Flippers::OnOn);
+  checkInputExists(inputs.ppWS, Flippers::OffOff);
+  WorkspaceMap fullInputs = inputs;
+  fullInputs.mpWS = DataObjects::create<DataObjects::Workspace2D>(*inputs.mmWS);
+  fullInputs.pmWS = DataObjects::create<DataObjects::Workspace2D>(*inputs.ppWS);
+  twoInputsSolve01And10(fullInputs, inputs, efficiencies);
+  return fullCorrections(fullInputs, efficiencies);
+}
+
+/**
+ * Correct for non-ideal instrument effects.
+ * Needs the 00 and 11 flipper configurations as well as either 01 or 10.
+ * The missing intensity (01 or 10) is solved from the assumption
+ * that the corrected R01 = R10.
+ * @param inputs a set of workspaces to correct
+ * @param efficiencies a set of efficiency factors
+ * @return a set of corrected workspaces
+ */
+PolarizationEfficiencyCor::WorkspaceMap
+PolarizationEfficiencyCor::threeInputCorrections(
+    const WorkspaceMap &inputs, const EfficiencyMap &efficiencies) {
+  WorkspaceMap fullInputs = inputs;
+  checkInputExists(inputs.mmWS, Flippers::OnOn);
+  checkInputExists(inputs.ppWS, Flippers::OffOff);
+  if (!inputs.mpWS) {
+    checkInputExists(inputs.pmWS, Flippers::OffOn);
+    threeInputsSolve10(fullInputs, efficiencies);
+  } else {
+    checkInputExists(inputs.mpWS, Flippers::OnOff);
+    threeInputsSolve01(fullInputs, efficiencies);
+  }
+  return fullCorrections(fullInputs, efficiencies);
+}
+
+/**
+ * Correct for non-ideal instrument effects.
+ * Perform full polarization corrections. All flipper configurations
+ * (00, 01, 10 and 11) are needed for this.
+ * @param inputs a set of workspaces to correct
+ * @param efficiencies a set of efficiency factors
+ * @return a set of corrected workspaces
+ */
+PolarizationEfficiencyCor::WorkspaceMap
+PolarizationEfficiencyCor::fullCorrections(const WorkspaceMap &inputs,
+                                           const EfficiencyMap &efficiencies) {
+  using namespace boost::math;
+  checkInputExists(inputs.mmWS, Flippers::OnOn);
+  checkInputExists(inputs.mpWS, Flippers::OnOff);
+  checkInputExists(inputs.pmWS, Flippers::OffOn);
+  checkInputExists(inputs.ppWS, Flippers::OffOff);
+  WorkspaceMap outputs;
+  outputs.mmWS = DataObjects::create<DataObjects::Workspace2D>(*inputs.mmWS);
+  outputs.mpWS = DataObjects::create<DataObjects::Workspace2D>(*inputs.mpWS);
+  outputs.pmWS = DataObjects::create<DataObjects::Workspace2D>(*inputs.pmWS);
+  outputs.ppWS = DataObjects::create<DataObjects::Workspace2D>(*inputs.ppWS);
+  const auto F1 = efficiencies.F1->y();
+  const auto F1E = efficiencies.F1->e();
+  const auto F2 = efficiencies.F2->y();
+  const auto F2E = efficiencies.F2->e();
+  const auto P1 = efficiencies.P1->y();
+  const auto P1E = efficiencies.P1->e();
+  const auto P2 = efficiencies.P2->y();
+  const auto P2E = efficiencies.P2->e();
+  const size_t nHisto = inputs.mmWS->getNumberHistograms();
+  for (size_t wsIndex = 0; wsIndex != nHisto; ++wsIndex) {
+    const auto &mmY = inputs.mmWS->y(wsIndex);
+    const auto &mmE = inputs.mmWS->e(wsIndex);
+    const auto &mpY = inputs.mpWS->y(wsIndex);
+    const auto &mpE = inputs.mpWS->e(wsIndex);
+    const auto &pmY = inputs.pmWS->y(wsIndex);
+    const auto &pmE = inputs.pmWS->e(wsIndex);
+    const auto &ppY = inputs.ppWS->y(wsIndex);
+    const auto &ppE = inputs.ppWS->e(wsIndex);
+    auto &mmYOut = outputs.mmWS->mutableY(wsIndex);
+    auto &mmEOut = outputs.mmWS->mutableE(wsIndex);
+    auto &mpYOut = outputs.mpWS->mutableY(wsIndex);
+    auto &mpEOut = outputs.mpWS->mutableE(wsIndex);
+    auto &pmYOut = outputs.pmWS->mutableY(wsIndex);
+    auto &pmEOut = outputs.pmWS->mutableE(wsIndex);
+    auto &ppYOut = outputs.ppWS->mutableY(wsIndex);
+    auto &ppEOut = outputs.ppWS->mutableE(wsIndex);
+    for (size_t binIndex = 0; binIndex < mmY.size(); ++binIndex) {
+      Eigen::Vector4d corrected;
+      Eigen::Vector4d errors;
+      fourInputsCorrectedAndErrors(corrected, errors, ppY[binIndex],
+                                   ppE[binIndex], pmY[binIndex], pmE[binIndex],
+                                   mpY[binIndex], mpE[binIndex], mmY[binIndex],
+                                   mmE[binIndex], F1[binIndex], F1E[binIndex],
+                                   F2[binIndex], F2E[binIndex], P1[binIndex],
+                                   P1E[binIndex], P2[binIndex], P2E[binIndex]);
+      ppYOut[binIndex] = corrected[0];
+      pmYOut[binIndex] = corrected[1];
+      mpYOut[binIndex] = corrected[2];
+      mmYOut[binIndex] = corrected[3];
+      ppEOut[binIndex] = errors[0];
+      pmEOut[binIndex] = errors[1];
+      mpEOut[binIndex] = errors[2];
+      mmEOut[binIndex] = errors[3];
+    }
+  }
+  return outputs;
+}
+
+/**
+ * Make a set of workspaces to correct from input properties.
+ * @param flippers a vector of flipper configurations
+ * @return a set of workspaces to correct
+ */
+PolarizationEfficiencyCor::WorkspaceMap
+PolarizationEfficiencyCor::mapInputsToDirections(
+    const std::vector<std::string> &flippers) {
+  const std::vector<std::string> inputNames = getProperty(Prop::INPUT_WS);
+  WorkspaceMap inputs;
+  for (size_t i = 0; i < flippers.size(); ++i) {
+    auto ws =
+        (API::AnalysisDataService::Instance().retrieveWS<API::MatrixWorkspace>(
+            inputNames[i]));
+    if (!ws) {
+      throw std::runtime_error(
+          "One of the input workspaces doesn't seem to be a MatrixWorkspace.");
+    }
+    const auto &f = flippers[i];
+    if (f == Flippers::OnOn || f == Flippers::On) {
+      inputs.mmWS = ws;
+    } else if (f == Flippers::OnOff) {
+      inputs.mpWS = ws;
+    } else if (f == Flippers::OffOn) {
+      inputs.pmWS = ws;
+    } else if (f == Flippers::OffOff || f == Flippers::Off) {
+      inputs.ppWS = ws;
+    } else {
+      throw std::runtime_error(std::string{"Unknown entry in "} +
+                               Prop::FLIPPERS);
+    }
+  }
+  return inputs;
+}
+
+/**
+ * Solve in-place the 01 flipper configuration from the assumption that
+ * for the corrected intensities, R01 = R10.
+ * @param inputs a set of input workspaces
+ * @param efficiencies a set of efficiency factors
+ */
+void PolarizationEfficiencyCor::threeInputsSolve01(
+    WorkspaceMap &inputs, const EfficiencyMap &efficiencies) {
+  using namespace Mantid::DataObjects;
+  inputs.pmWS = create<Workspace2D>(*inputs.mpWS);
+  const auto &F1 = efficiencies.F1->y();
+  const auto &F2 = efficiencies.F2->y();
+  const auto &P1 = efficiencies.P1->y();
+  const auto &P2 = efficiencies.P2->y();
+  const auto nHisto = inputs.pmWS->getNumberHistograms();
+  for (size_t wsIndex = 0; wsIndex != nHisto; ++wsIndex) {
+    const auto &I00 = inputs.ppWS->y(wsIndex);
+    auto &I01 = inputs.pmWS->mutableY(wsIndex);
+    const auto &I10 = inputs.mpWS->y(wsIndex);
+    const auto &I11 = inputs.mmWS->y(wsIndex);
+    for (size_t binIndex = 0; binIndex != I00.size(); ++binIndex) {
+      const auto f1 = F1[binIndex];
+      const auto f2 = F2[binIndex];
+      const auto p1 = P1[binIndex];
+      const auto p2 = P2[binIndex];
+      const auto i00 = I00[binIndex];
+      const auto i10 = I10[binIndex];
+      const auto i11 = I11[binIndex];
+      I01[binIndex] =
+          (f1 * i00 * (-1. + 2. * p1) - (i00 - i10 + i11) * (p1 - p2) -
+           f2 * (i00 - i10) * (-1. + 2. * p2)) /
+          (-p1 + f1 * (-1. + 2. * p1) + p2);
+      // The errors are left to zero.
+    }
+  }
+}
+
+/**
+ * Solve in-place the 10 flipper configuration from the assumption that
+ * for the corrected intensities R01 = R10.
+ * @param inputs a set of input workspaces
+ * @param efficiencies a set of efficiency factors
+ */
+void PolarizationEfficiencyCor::threeInputsSolve10(
+    WorkspaceMap &inputs, const EfficiencyMap &efficiencies) {
+  inputs.mpWS = DataObjects::create<DataObjects::Workspace2D>(*inputs.pmWS);
+  const auto &F1 = efficiencies.F1->y();
+  const auto &F2 = efficiencies.F2->y();
+  const auto &P1 = efficiencies.P1->y();
+  const auto &P2 = efficiencies.P2->y();
+  const auto nHisto = inputs.mpWS->getNumberHistograms();
+  for (size_t wsIndex = 0; wsIndex != nHisto; ++wsIndex) {
+    const auto &I00 = inputs.ppWS->y(wsIndex);
+    const auto &I01 = inputs.pmWS->y(wsIndex);
+    auto &I10 = inputs.mpWS->mutableY(wsIndex);
+    const auto &I11 = inputs.mmWS->y(wsIndex);
+    for (size_t binIndex = 0; binIndex != I00.size(); ++binIndex) {
+      const auto f1 = F1[binIndex];
+      const auto f2 = F2[binIndex];
+      const auto p1 = P1[binIndex];
+      const auto p2 = P2[binIndex];
+      const auto i00 = I00[binIndex];
+      const auto i01 = I01[binIndex];
+      const auto i11 = I11[binIndex];
+      I10[binIndex] =
+          (-f1 * (i00 - i01) * (-1. + 2. * p1) + (i00 - i01 + i11) * (p1 - p2) +
+           f2 * i00 * (-1. + 2. * p2)) /
+          (p1 - p2 + f2 * (-1. + 2. * p2));
+      // The errors are left to zero.
+    }
+  }
+}
+
+/**
+ * Solve in-place the 01 and 10 flipper configurations from the assumption that
+ * for the corrected intensities R01 = R10 = 0.
+ * @param fullInputs a set of output workspaces
+ * @param inputs a set of input workspaces
+ * @param efficiencies a set of efficiency factors
+ */
+void PolarizationEfficiencyCor::twoInputsSolve01And10(
+    WorkspaceMap &fullInputs, const WorkspaceMap &inputs,
+    const EfficiencyMap &efficiencies) {
+  using namespace boost::math;
+  const auto &F1 = efficiencies.F1->y();
+  const auto &F1E = efficiencies.F1->e();
+  const auto &F2 = efficiencies.F2->y();
+  const auto &F2E = efficiencies.F2->e();
+  const auto &P1 = efficiencies.P1->y();
+  const auto &P1E = efficiencies.P1->e();
+  const auto &P2 = efficiencies.P2->y();
+  const auto &P2E = efficiencies.P2->e();
+  const auto nHisto = inputs.mmWS->getNumberHistograms();
+  for (size_t wsIndex = 0; wsIndex != nHisto; ++wsIndex) {
+    const auto &I00 = inputs.ppWS->y(wsIndex);
+    const auto &E00 = inputs.ppWS->e(wsIndex);
+    const auto &I11 = inputs.mmWS->y(wsIndex);
+    const auto &E11 = inputs.mmWS->e(wsIndex);
+    auto &I01 = fullInputs.pmWS->mutableY(wsIndex);
+    auto &E01 = fullInputs.pmWS->mutableE(wsIndex);
+    auto &I10 = fullInputs.mpWS->mutableY(wsIndex);
+    auto &E10 = fullInputs.mpWS->mutableE(wsIndex);
+    for (size_t binIndex = 0; binIndex != I00.size(); ++binIndex) {
+      const auto i00 = I00[binIndex];
+      const auto i11 = I11[binIndex];
+      const auto f1 = F1[binIndex];
+      const auto f2 = F2[binIndex];
+      const auto p1 = P1[binIndex];
+      const auto p2 = P2[binIndex];
+      const auto a = -1. + p1 + 2. * p2 - 2. * p1 * p2;
+      const auto b = -1. + 2. * p1;
+      const auto c = -1. + 2. * p2;
+      const auto d = -1. + p2;
+      // Case: 01
+      const auto divisor = f2 * p1 * a + f1 * b * (-d * p2 + f2 * (p1 + d) * c);
+      I01[binIndex] =
+          (f2 * i11 * p1 * a -
+           f1 * i00 * b * (-f2 * pow<2>(c) + pow<2>(f2 * c) + d * p2)) /
+          divisor;
+      E01[binIndex] = twoInputsErrorEstimate01(
+          i00, E00[binIndex], i11, E11[binIndex], p1, P1E[binIndex], p2,
+          P2E[binIndex], f1, F1E[binIndex], f2, F2E[binIndex]);
+      // Case: 10
+      I10[binIndex] =
+          (-pow<2>(f1) * f2 * i00 * pow<2>(b) * c + f2 * i00 * p1 * a +
+           f1 * b * (-i11 * d * p2 + f2 * i00 * b * c)) /
+          divisor;
+      E10[binIndex] = twoInputsErrorEstimate10(
+          i00, E00[binIndex], i11, E11[binIndex], p1, P1E[binIndex], p2,
+          P2E[binIndex], f1, F1E[binIndex], f2, F2E[binIndex]);
+    }
+  }
+}
+} // namespace Algorithms
+} // namespace Mantid
diff --git a/Framework/Algorithms/src/RealFFT.cpp b/Framework/Algorithms/src/RealFFT.cpp
index 134202698ac21b4292c1029b5222d0797063879a..4c8d099ca5f9c651223820a16236832e40745501 100644
--- a/Framework/Algorithms/src/RealFFT.cpp
+++ b/Framework/Algorithms/src/RealFFT.cpp
@@ -1,6 +1,3 @@
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
 #include "MantidAlgorithms/RealFFT.h"
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/TextAxis.h"
diff --git a/Framework/Algorithms/src/RemoveLowResTOF.cpp b/Framework/Algorithms/src/RemoveLowResTOF.cpp
index 94c54d97319da803d28780ad9af2c6f89ecc43de..016fa5f7e08c9ff21d9405945f226a5cbef19923 100644
--- a/Framework/Algorithms/src/RemoveLowResTOF.cpp
+++ b/Framework/Algorithms/src/RemoveLowResTOF.cpp
@@ -3,7 +3,6 @@
 #include "MantidAPI/InstrumentValidator.h"
 #include "MantidAPI/RawCountValidator.h"
 #include "MantidAPI/SpectrumInfo.h"
-#include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAPI/WorkspaceUnitValidator.h"
 #include "MantidGeometry/IComponent.h"
 #include "MantidGeometry/Instrument.h"
@@ -33,9 +32,6 @@ RemoveLowResTOF::RemoveLowResTOF()
     : m_inputWS(), m_inputEvWS(), m_DIFCref(0.), m_K(0.), m_Tmin(0.),
       m_wavelengthMin(0.), m_numberOfSpectra(0), m_outputLowResTOF(false) {}
 
-/// Destructor
-RemoveLowResTOF::~RemoveLowResTOF() {}
-
 /// Algorithm's name for identification overriding a virtual method
 const string RemoveLowResTOF::name() const { return "RemoveLowResTOF"; }
 
@@ -145,8 +141,6 @@ void RemoveLowResTOF::exec() {
     }
     m_progress->report();
   }
-
-  this->runMaskDetectors();
 }
 
 /** Remove low resolution TOF from an EventWorkspace
@@ -237,7 +231,6 @@ void RemoveLowResTOF::execEvent(const SpectrumInfo &spectrumInfo) {
   g_log.debug() << "TOF range is now " << outW->getTofMin() << " to "
                 << outW->getTofMax() << " microseconds\n";
   outW->clearMRU();
-  this->runMaskDetectors();
 }
 
 double RemoveLowResTOF::calcTofMin(const std::size_t workspaceIndex,
@@ -302,16 +295,5 @@ void RemoveLowResTOF::getTminData(const bool isEvent) {
     throw std::runtime_error("Cannot have minimum time less than zero");
 }
 
-void RemoveLowResTOF::runMaskDetectors() {
-  IAlgorithm_sptr alg = createChildAlgorithm("MaskDetectors");
-  alg->setProperty<MatrixWorkspace_sptr>("Workspace",
-                                         this->getProperty("OutputWorkspace"));
-  alg->setProperty<MatrixWorkspace_sptr>("MaskedWorkspace",
-                                         this->getProperty("InputWorkspace"));
-  if (!alg->execute())
-    throw std::runtime_error(
-        "MaskDetectors Child Algorithm has not executed successfully");
-}
-
 } // namespace Algorithm
 } // namespace Mantid
diff --git a/Framework/Algorithms/src/SofQW.cpp b/Framework/Algorithms/src/SofQW.cpp
index e2f8eb19e72fdeffed3e90f316bc6efc0d6f61be..00f65adb60c1f8cb4dd0f525ce293c96516083fd 100644
--- a/Framework/Algorithms/src/SofQW.cpp
+++ b/Framework/Algorithms/src/SofQW.cpp
@@ -109,6 +109,11 @@ void SofQW::createCommonInputProperties(API::Algorithm &alg) {
                       "If true, all NaN values in the output workspace are "
                       "replaced using the ReplaceSpecialValues algorithm.",
                       Direction::Input);
+  alg.declareProperty(
+      make_unique<ArrayProperty<double>>(
+          "EAxisBinning", boost::make_shared<RebinParamsValidator>(true)),
+      "The bin parameters to use for the E axis (optional, in the format "
+      "used by the :ref:`algm-Rebin` algorithm).");
 }
 
 void SofQW::exec() {
@@ -138,28 +143,36 @@ void SofQW::exec() {
 /** Creates the output workspace, setting the axes according to the input
  * binning parameters
  *  @param[in]  inputWorkspace The input workspace
- *  @param[in]  binParams The bin parameters from the user
- *  @param[out] newAxis        The 'vertical' axis defined by the given
- * parameters
+ *  @param[in]  qbinParams The q-bin parameters from the user
+ *  @param[out] qAxis The 'vertical' (q) axis defined by the given parameters
+ *  @param[out] ebinParams The 'horizontal' (energy) axis parameters (optional)
  *  @return A pointer to the newly-created workspace
  */
 API::MatrixWorkspace_sptr
 SofQW::setUpOutputWorkspace(API::MatrixWorkspace_const_sptr inputWorkspace,
-                            const std::vector<double> &binParams,
-                            std::vector<double> &newAxis) {
+                            const std::vector<double> &qbinParams,
+                            std::vector<double> &qAxis,
+                            const std::vector<double> &ebinParams) {
   // Create vector to hold the new X axis values
-  HistogramData::BinEdges xAxis(inputWorkspace->refX(0));
-  const int xLength = static_cast<int>(xAxis.size());
+  HistogramData::BinEdges xAxis(0);
+  int xLength;
+  if (ebinParams.empty()) {
+    xAxis = inputWorkspace->refX(0);
+    xLength = static_cast<int>(xAxis.size());
+  } else {
+    xLength = static_cast<int>(VectorHelper::createAxisFromRebinParams(
+        ebinParams, xAxis.mutableRawData()));
+  }
   // Create a vector to temporarily hold the vertical ('y') axis and populate
   // that
   const int yLength = static_cast<int>(
-      VectorHelper::createAxisFromRebinParams(binParams, newAxis));
+      VectorHelper::createAxisFromRebinParams(qbinParams, qAxis));
 
   // Create the output workspace
   MatrixWorkspace_sptr outputWorkspace = WorkspaceFactory::Instance().create(
       inputWorkspace, yLength - 1, xLength, xLength - 1);
   // Create a numeric axis to replace the default vertical one
-  Axis *const verticalAxis = new BinEdgeAxis(newAxis);
+  Axis *const verticalAxis = new BinEdgeAxis(qAxis);
   outputWorkspace->replaceAxis(1, verticalAxis);
 
   // Now set the axis values
diff --git a/Framework/Algorithms/src/SofQWCentre.cpp b/Framework/Algorithms/src/SofQWCentre.cpp
index 72e9eceab89d125751fd0c6bcc6347a4748dc01b..089e8911eb2d0d1e6b1eb82ee671cc40ddf4b0a6 100644
--- a/Framework/Algorithms/src/SofQWCentre.cpp
+++ b/Framework/Algorithms/src/SofQWCentre.cpp
@@ -1,4 +1,5 @@
 #include "MantidAlgorithms/SofQWCentre.h"
+#include "MantidAlgorithms/SofQW.h"
 #include "MantidDataObjects/Histogram1D.h"
 #include "MantidAPI/BinEdgeAxis.h"
 #include "MantidAPI/CommonBinsValidator.h"
@@ -45,48 +46,7 @@ using namespace API;
 /**
  * Create the input properties
  */
-void SofQWCentre::init() { createInputProperties(*this); }
-
-/**
- * Create the given algorithm's input properties
- * @param alg An algorithm object
- */
-void SofQWCentre::createInputProperties(API::Algorithm &alg) {
-  auto wsValidator = boost::make_shared<CompositeValidator>();
-  wsValidator->add<WorkspaceUnitValidator>("DeltaE");
-  wsValidator->add<SpectraAxisValidator>();
-  wsValidator->add<CommonBinsValidator>();
-  wsValidator->add<HistogramValidator>();
-  wsValidator->add<InstrumentValidator>();
-  alg.declareProperty(make_unique<WorkspaceProperty<>>(
-                          "InputWorkspace", "", Direction::Input, wsValidator),
-                      "Reduced data in units of energy transfer DeltaE.\nThe "
-                      "workspace must contain histogram data and have common "
-                      "bins across all spectra.");
-  alg.declareProperty(make_unique<WorkspaceProperty<>>("OutputWorkspace", "",
-                                                       Direction::Output),
-                      "The name to use for the q-omega workspace.");
-  alg.declareProperty(
-      make_unique<ArrayProperty<double>>(
-          "QAxisBinning", boost::make_shared<RebinParamsValidator>()),
-      "The bin parameters to use for the q axis (in the format used by the "
-      ":ref:`algm-Rebin` algorithm).");
-
-  std::vector<std::string> propOptions{"Direct", "Indirect"};
-  alg.declareProperty("EMode", "",
-                      boost::make_shared<StringListValidator>(propOptions),
-                      "The energy transfer analysis mode (Direct/Indirect)");
-  auto mustBePositive = boost::make_shared<BoundedValidator<double>>();
-  mustBePositive->setLower(0.0);
-  alg.declareProperty("EFixed", 0.0, mustBePositive,
-                      "The value of fixed energy: :math:`E_i` (EMode=Direct) "
-                      "or :math:`E_f` (EMode=Indirect) (meV).\nMust be set "
-                      "here if not available in the instrument definition.");
-  alg.declareProperty("ReplaceNaNs", false,
-                      "If true, replaces all NaNs in output workspace with "
-                      "zeroes.",
-                      Direction::Input);
-}
+void SofQWCentre::init() { SofQW::createCommonInputProperties(*this); }
 
 void SofQWCentre::exec() {
   using namespace Geometry;
@@ -102,9 +62,11 @@ void SofQWCentre::exec() {
   }
 
   std::vector<double> verticalAxis;
-  MatrixWorkspace_sptr outputWorkspace = setUpOutputWorkspace(
-      inputWorkspace, getProperty("QAxisBinning"), verticalAxis);
+  MatrixWorkspace_sptr outputWorkspace =
+      SofQW::setUpOutputWorkspace(inputWorkspace, getProperty("QAxisBinning"),
+                                  verticalAxis, getProperty("EAxisBinning"));
   setProperty("OutputWorkspace", outputWorkspace);
+  const auto &xAxis = outputWorkspace->binEdges(0).rawData();
 
   // Holds the spectrum-detector mapping
   std::vector<specnum_t> specNumberMapping;
@@ -158,6 +120,9 @@ void SofQWCentre::exec() {
             (detectorInfo.position(idet) - detectorInfo.samplePosition());
         scatterDir.normalize();
         for (size_t j = 0; j < numBins; ++j) {
+          if (X[j] < xAxis.front() || X[j + 1] > xAxis.back())
+            continue;
+
           const double deltaE = 0.5 * (X[j] + X[j + 1]);
           // Compute ki and kf wave vectors and therefore q = ki - kf
           double ei(0.0), ef(0.0);
@@ -204,6 +169,10 @@ void SofQWCentre::exec() {
           const MantidVec::difference_type qIndex =
               std::upper_bound(verticalAxis.begin(), verticalAxis.end(), q) -
               verticalAxis.begin() - 1;
+          // Find which e bin this point lies in
+          const MantidVec::difference_type eIndex =
+              std::upper_bound(xAxis.begin(), xAxis.end(), deltaE) -
+              xAxis.begin() - 1;
 
           // Add this spectra-detector pair to the mapping
           specNumberMapping.push_back(
@@ -212,10 +181,10 @@ void SofQWCentre::exec() {
 
           // And add the data and it's error to that bin, taking into account
           // the number of detectors contributing to this bin
-          outputWorkspace->mutableY(qIndex)[j] += Y[j] / numDets_d;
+          outputWorkspace->mutableY(qIndex)[eIndex] += Y[j] / numDets_d;
           // Standard error on the average
-          outputWorkspace->mutableE(qIndex)[j] =
-              sqrt((pow(outputWorkspace->e(qIndex)[j], 2) + pow(E[j], 2)) /
+          outputWorkspace->mutableE(qIndex)[eIndex] =
+              sqrt((pow(outputWorkspace->e(qIndex)[eIndex], 2) + pow(E[j], 2)) /
                    numDets_d);
         }
       } catch (std::out_of_range &) {
@@ -249,50 +218,6 @@ void SofQWCentre::exec() {
   }
 }
 
-/** Creates the output workspace, setting the axes according to the input
- * binning parameters
- *  @param[in]  inputWorkspace The input workspace
- *  @param[in]  binParams The bin parameters from the user
- *  @param[out] newAxis        The 'vertical' axis defined by the given
- * parameters
- *  @return A pointer to the newly-created workspace
- */
-API::MatrixWorkspace_sptr SofQWCentre::setUpOutputWorkspace(
-    API::MatrixWorkspace_const_sptr inputWorkspace,
-    const std::vector<double> &binParams, std::vector<double> &newAxis) {
-  // Create vector to hold the new X axis values
-  HistogramData::BinEdges xAxis(inputWorkspace->sharedX(0));
-  const int xLength = static_cast<int>(xAxis.size());
-  // Create a vector to temporarily hold the vertical ('y') axis and populate
-  // that
-  const int yLength = static_cast<int>(
-      VectorHelper::createAxisFromRebinParams(binParams, newAxis));
-
-  // Create the output workspace
-  MatrixWorkspace_sptr outputWorkspace = WorkspaceFactory::Instance().create(
-      inputWorkspace, yLength - 1, xLength, xLength - 1);
-  // Create a numeric axis to replace the default vertical one
-  Axis *const verticalAxis = new BinEdgeAxis(newAxis);
-  outputWorkspace->replaceAxis(1, verticalAxis);
-
-  // Now set the axis values
-  for (int i = 0; i < yLength - 1; ++i) {
-    outputWorkspace->setBinEdges(i, xAxis);
-  }
-
-  // Set the axis units
-  verticalAxis->unit() = UnitFactory::Instance().create("MomentumTransfer");
-  verticalAxis->title() = "|Q|";
-
-  // Set the X axis title (for conversion to MD)
-  outputWorkspace->getAxis(0)->title() = "Energy transfer";
-
-  outputWorkspace->setYUnit("");
-  outputWorkspace->setYUnitLabel("Intensity");
-
-  return outputWorkspace;
-}
-
 /** Divide each bin by the width of its q bin.
  *  @param outputWS :: The output workspace
  *  @param qAxis ::    A vector of the q bin boundaries
diff --git a/Framework/Algorithms/src/SofQWNormalisedPolygon.cpp b/Framework/Algorithms/src/SofQWNormalisedPolygon.cpp
index b056e9157926cd3f17dacda1410130ee11490e95..dc14b32c0d64f6d0753bcd581fc7395da5026177 100644
--- a/Framework/Algorithms/src/SofQWNormalisedPolygon.cpp
+++ b/Framework/Algorithms/src/SofQWNormalisedPolygon.cpp
@@ -73,7 +73,8 @@ void SofQWNormalisedPolygon::exec() {
   }
 
   RebinnedOutput_sptr outputWS =
-      this->setUpOutputWorkspace(*inputWS, getProperty("QAxisBinning"), m_Qout);
+      this->setUpOutputWorkspace(*inputWS, getProperty("QAxisBinning"), m_Qout,
+                                 getProperty("EAxisBinning"));
   g_log.debug() << "Workspace type: " << outputWS->id() << '\n';
   setProperty("OutputWorkspace", outputWS);
   const size_t nEnergyBins = inputWS->blocksize();
@@ -398,25 +399,37 @@ void SofQWNormalisedPolygon::initAngularCachesPSD(
 /** Creates the output workspace, setting the axes according to the input
  * binning parameters
  *  @param[in]  inputWorkspace The input workspace
- *  @param[in]  binParams The bin parameters from the user
- *  @param[out] newAxis        The 'vertical' axis defined by the given
- * parameters
+ *  @param[in]  qbinParams The q-bin parameters from the user
+ *  @param[out] qAxis The 'vertical' (q) axis defined by the given parameters
+ *  @param[out] ebinParams The 'horizontal' (energy) axis parameters (optional)
  *  @return A pointer to the newly-created workspace
  */
 RebinnedOutput_sptr SofQWNormalisedPolygon::setUpOutputWorkspace(
     const API::MatrixWorkspace &inputWorkspace,
-    const std::vector<double> &binParams, std::vector<double> &newAxis) {
+    const std::vector<double> &qbinParams, std::vector<double> &qAxis,
+    const std::vector<double> &ebinParams) {
+  using Kernel::VectorHelper::createAxisFromRebinParams;
+
+  HistogramData::BinEdges xAxis(0);
+  // Create vector to hold the new X axis values
+  if (ebinParams.empty()) {
+    xAxis = inputWorkspace.binEdges(0);
+  } else {
+    static_cast<void>(
+        createAxisFromRebinParams(ebinParams, xAxis.mutableRawData()));
+  }
+
   // Create a vector to temporarily hold the vertical ('y') axis and populate
   // that
   const int yLength = static_cast<int>(
-      VectorHelper::createAxisFromRebinParams(binParams, newAxis));
+      VectorHelper::createAxisFromRebinParams(qbinParams, qAxis));
 
   // Create output workspace, bin edges are same as in inputWorkspace index 0
-  auto outputWorkspace = create<RebinnedOutput>(inputWorkspace, yLength - 1,
-                                                inputWorkspace.binEdges(0));
+  auto outputWorkspace =
+      create<RebinnedOutput>(inputWorkspace, yLength - 1, xAxis);
 
   // Create a binned numeric axis to replace the default vertical one
-  Axis *const verticalAxis = new BinEdgeAxis(newAxis);
+  Axis *const verticalAxis = new BinEdgeAxis(qAxis);
   outputWorkspace->replaceAxis(1, verticalAxis);
 
   // Set the axis units
diff --git a/Framework/Algorithms/src/SofQWPolygon.cpp b/Framework/Algorithms/src/SofQWPolygon.cpp
index b5b4fa28464c5e6871fb8b5d914036d27e76cdb0..71ca8d777d8552cc6ec79db3e6ebe6e20b93c557 100644
--- a/Framework/Algorithms/src/SofQWPolygon.cpp
+++ b/Framework/Algorithms/src/SofQWPolygon.cpp
@@ -40,7 +40,8 @@ void SofQWPolygon::exec() {
   }
 
   MatrixWorkspace_sptr outputWS =
-      SofQW::setUpOutputWorkspace(inputWS, getProperty("QAxisBinning"), m_Qout);
+      SofQW::setUpOutputWorkspace(inputWS, getProperty("QAxisBinning"), m_Qout,
+                                  getProperty("EAxisBinning"));
   setProperty("OutputWorkspace", outputWS);
   const size_t nenergyBins = inputWS->blocksize();
 
diff --git a/Framework/Algorithms/src/StripPeaks.cpp b/Framework/Algorithms/src/StripPeaks.cpp
index ba15c3be06cd0f55113cc851149336bd941f10cb..7b320428b8987b745228b80c871d3317235ad7d7 100644
--- a/Framework/Algorithms/src/StripPeaks.cpp
+++ b/Framework/Algorithms/src/StripPeaks.cpp
@@ -18,8 +18,6 @@ DECLARE_ALGORITHM(StripPeaks)
 using namespace Kernel;
 using namespace API;
 
-StripPeaks::StripPeaks() : API::Algorithm(), m_maxChiSq(0.) {}
-
 void StripPeaks::init() {
   declareProperty(
       make_unique<WorkspaceProperty<>>("InputWorkspace", "", Direction::Input),
diff --git a/Framework/Algorithms/test/CalculateDIFCTest.h b/Framework/Algorithms/test/CalculateDIFCTest.h
index d682a124cdfbb1f2c73904cabb73ba2acc4912da..a706ff7dad7a682335202fc5b909ee570d875970 100644
--- a/Framework/Algorithms/test/CalculateDIFCTest.h
+++ b/Framework/Algorithms/test/CalculateDIFCTest.h
@@ -3,8 +3,10 @@
 
 #include <cxxtest/TestSuite.h>
 
-#include "MantidAlgorithms/CalculateDIFC.h"
 #include "MantidAPI/SpectrumInfo.h"
+#include "MantidAPI/TableRow.h"
+#include "MantidAlgorithms/CalculateDIFC.h"
+#include "MantidDataObjects/TableWorkspace.h"
 #include "MantidTestHelpers/WorkspaceCreationHelper.h"
 
 using Mantid::Algorithms::CalculateDIFC;
@@ -45,8 +47,7 @@ public:
     TS_ASSERT_THROWS_NOTHING(alg.execute(););
     TS_ASSERT(alg.isExecuted());
 
-    // Retrieve the workspace from data service. TODO: Change to your desired
-    // type
+    // Retrieve the workspace from data service.
     MatrixWorkspace_sptr ws;
     TS_ASSERT_THROWS_NOTHING(
         ws = boost::dynamic_pointer_cast<MatrixWorkspace>(
@@ -90,6 +91,49 @@ public:
 
     runTest(inputWS, offsetsWS, outWSName);
   }
+
+  void test_withDiffCal() {
+    auto inputWS = WorkspaceCreationHelper::create2DWorkspaceWithFullInstrument(
+        NUM_SPEC, 1);
+    std::string outWSName("CalculateDIFCTest_withCalib_OutputWS");
+
+    ITableWorkspace_sptr calibWksp =
+        boost::make_shared<Mantid::DataObjects::TableWorkspace>();
+    calibWksp->addColumn("int", "detid");
+    calibWksp->addColumn("double", "difc");
+    for (size_t i = 0; i < NUM_SPEC; ++i) {
+      Mantid::API::TableRow newrow = calibWksp->appendRow();
+      newrow << static_cast<int>(i + 1);
+      newrow << 12345.;
+    }
+
+    CalculateDIFC alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspace", inputWS));
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setProperty("CalibrationWorkspace", calibWksp));
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", outWSName));
+    TS_ASSERT_THROWS_NOTHING(alg.execute(););
+    TS_ASSERT(alg.isExecuted());
+
+    MatrixWorkspace_sptr ws;
+    TS_ASSERT_THROWS_NOTHING(
+        ws = boost::dynamic_pointer_cast<MatrixWorkspace>(
+            AnalysisDataService::Instance().retrieve(outWSName)));
+    TS_ASSERT(ws);
+    if (!ws)
+      return;
+
+    // should only be NUM_SPEC
+    for (size_t i = 0; i < NUM_SPEC; ++i) {
+      TS_ASSERT_DELTA(ws->readY(i)[0], 12345., 1.);
+    }
+
+    // Remove workspace from the data service.
+    AnalysisDataService::Instance().remove(outWSName);
+  }
 };
 
 #endif /* MANTID_ALGORITHMS_CALCULATEDIFCTEST_H_ */
diff --git a/Framework/Algorithms/test/ConjoinXRunsTest.h b/Framework/Algorithms/test/ConjoinXRunsTest.h
index 6e45b12d1da39e83e73bed5bc89941e43b3efef2..8133133d9614ad865bb76317ee54976b307c5e5a 100644
--- a/Framework/Algorithms/test/ConjoinXRunsTest.h
+++ b/Framework/Algorithms/test/ConjoinXRunsTest.h
@@ -16,6 +16,7 @@ using Mantid::Algorithms::AddSampleLog;
 using Mantid::Algorithms::AddTimeSeriesLog;
 using namespace Mantid::API;
 using namespace WorkspaceCreationHelper;
+using namespace Mantid::HistogramData;
 
 class ConjoinXRunsTest : public CxxTest::TestSuite {
 public:
@@ -29,18 +30,24 @@ public:
     MatrixWorkspace_sptr ws2 = create2DWorkspace154(5, 2); // 2 points
     MatrixWorkspace_sptr ws3 = create2DWorkspace123(5, 1); // 1 point
     MatrixWorkspace_sptr ws4 = create2DWorkspace154(5, 1); // 1 point
+    MatrixWorkspace_sptr ws5 = create2DWorkspace123(5, 3); // 3 points
+    MatrixWorkspace_sptr ws6 = create2DWorkspace123(5, 3); // 3 points
 
     ws1->getAxis(0)->setUnit("TOF");
     ws2->getAxis(0)->setUnit("TOF");
     ws3->getAxis(0)->setUnit("TOF");
     ws4->getAxis(0)->setUnit("TOF");
+    ws5->getAxis(0)->setUnit("TOF");
+    ws6->getAxis(0)->setUnit("TOF");
 
     storeWS("ws1", ws1);
     storeWS("ws2", ws2);
     storeWS("ws3", ws3);
     storeWS("ws4", ws4);
+    storeWS("ws5", ws5);
+    storeWS("ws6", ws6);
 
-    m_testWS = {"ws1", "ws2", "ws3", "ws4"};
+    m_testWS = {"ws1", "ws2", "ws3", "ws4", "ws5", "ws6"};
   }
 
   void tearDown() override {
@@ -48,6 +55,8 @@ public:
     removeWS("ws2");
     removeWS("ws3");
     removeWS("ws4");
+    removeWS("ws5");
+    removeWS("ws6");
     m_testWS.clear();
   }
 
@@ -57,7 +66,8 @@ public:
   }
 
   void testHappyCase() {
-    m_testee.setProperty("InputWorkspaces", m_testWS);
+    m_testee.setProperty("InputWorkspaces",
+                         std::vector<std::string>{"ws1", "ws2", "ws3", "ws4"});
     m_testee.setProperty("OutputWorkspace", "out");
     TS_ASSERT_THROWS_NOTHING(m_testee.execute());
     TS_ASSERT(m_testee.isExecuted());
@@ -100,6 +110,93 @@ public:
     TS_ASSERT_EQUALS(xaxis[6], 1.);
   }
 
+  void testFailDifferentNumberBins() {
+    MatrixWorkspace_sptr ws5 =
+        AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>("ws5");
+
+    Counts counts{{5, 8}};
+    Points points{{0.4, 0.9}};
+    ws5->setHistogram(3, points, counts);
+
+    m_testee.setProperty("InputWorkspaces",
+                         std::vector<std::string>{"ws1", "ws5"});
+    TS_ASSERT_THROWS(m_testee.execute(), std::runtime_error);
+  }
+
+  void testPassDifferentAxes() {
+    MatrixWorkspace_sptr ws6 =
+        AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>("ws6");
+
+    Counts counts{{4, 9, 16}};
+    Points points{{0.4, 0.9, 1.1}};
+    ws6->setHistogram(3, points, counts);
+
+    m_testee.setProperty("InputWorkspaces",
+                         std::vector<std::string>{"ws1", "ws6"});
+
+    TS_ASSERT_THROWS_NOTHING(m_testee.execute());
+    TS_ASSERT(m_testee.isExecuted());
+
+    MatrixWorkspace_sptr out =
+        AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>("out");
+
+    TS_ASSERT(out);
+    TS_ASSERT_EQUALS(out->getNumberHistograms(), 5);
+    TS_ASSERT_EQUALS(out->blocksize(), 6);
+    TS_ASSERT(!out->isHistogramData());
+    TS_ASSERT_EQUALS(out->getAxis(0)->unit()->unitID(), "TOF");
+
+    auto spectrum0 = out->y(0).rawData();
+    auto error0 = out->e(0).rawData();
+    auto xaxis0 = out->x(0).rawData();
+
+    TS_ASSERT_EQUALS(spectrum0[0], 2.);
+    TS_ASSERT_EQUALS(spectrum0[1], 2.);
+    TS_ASSERT_EQUALS(spectrum0[2], 2.);
+    TS_ASSERT_EQUALS(spectrum0[3], 2.);
+    TS_ASSERT_EQUALS(spectrum0[4], 2.);
+    TS_ASSERT_EQUALS(spectrum0[5], 2.);
+
+    TS_ASSERT_EQUALS(error0[0], 3.);
+    TS_ASSERT_EQUALS(error0[1], 3.);
+    TS_ASSERT_EQUALS(error0[2], 3.);
+    TS_ASSERT_EQUALS(error0[3], 3.);
+    TS_ASSERT_EQUALS(error0[4], 3.);
+    TS_ASSERT_EQUALS(error0[5], 3.);
+
+    TS_ASSERT_EQUALS(xaxis0[0], 1.);
+    TS_ASSERT_EQUALS(xaxis0[1], 2.);
+    TS_ASSERT_EQUALS(xaxis0[2], 3.);
+    TS_ASSERT_EQUALS(xaxis0[3], 1.);
+    TS_ASSERT_EQUALS(xaxis0[4], 2.);
+    TS_ASSERT_EQUALS(xaxis0[5], 3.);
+
+    auto spectrum3 = out->y(3).rawData();
+    auto error3 = out->e(3).rawData();
+    auto xaxis3 = out->x(3).rawData();
+
+    TS_ASSERT_EQUALS(spectrum3[0], 2.);
+    TS_ASSERT_EQUALS(spectrum3[1], 2.);
+    TS_ASSERT_EQUALS(spectrum3[2], 2.);
+    TS_ASSERT_EQUALS(spectrum3[3], 4.);
+    TS_ASSERT_EQUALS(spectrum3[4], 9.);
+    TS_ASSERT_EQUALS(spectrum3[5], 16.);
+
+    TS_ASSERT_EQUALS(error3[0], 3.);
+    TS_ASSERT_EQUALS(error3[1], 3.);
+    TS_ASSERT_EQUALS(error3[2], 3.);
+    TS_ASSERT_EQUALS(error3[3], 2.);
+    TS_ASSERT_EQUALS(error3[4], 3.);
+    TS_ASSERT_EQUALS(error3[5], 4.);
+
+    TS_ASSERT_EQUALS(xaxis3[0], 1.);
+    TS_ASSERT_EQUALS(xaxis3[1], 2.);
+    TS_ASSERT_EQUALS(xaxis3[2], 3.);
+    TS_ASSERT_EQUALS(xaxis3[3], 0.4);
+    TS_ASSERT_EQUALS(xaxis3[4], 0.9);
+    TS_ASSERT_EQUALS(xaxis3[5], 1.1);
+  }
+
   void testFailWithNumLog() {
     AddSampleLog logAdder;
     logAdder.initialize();
@@ -110,7 +207,8 @@ public:
     logAdder.setProperty("LogText", "0.7");
     logAdder.execute();
 
-    m_testee.setProperty("InputWorkspaces", m_testWS);
+    m_testee.setProperty("InputWorkspaces",
+                         std::vector<std::string>{"ws1", "ws2", "ws3", "ws4"});
 
     m_testee.setProperty("SampleLogAsXAxis", "TestNumLog");
 
@@ -169,7 +267,8 @@ public:
     logAdder.setProperty("LogText", "str");
     logAdder.execute();
 
-    m_testee.setProperty("InputWorkspaces", m_testWS);
+    m_testee.setProperty("InputWorkspaces",
+                         std::vector<std::string>{"ws1", "ws2", "ws3", "ws4"});
     m_testee.setProperty("SampleLogAsXAxis", "TestStrLog");
 
     // string log not supported, fail
diff --git a/Framework/Algorithms/test/LineProfileTest.h b/Framework/Algorithms/test/LineProfileTest.h
index 341a432410e675d27e205cb5aad5ff0a0c2734b7..467d4e6aa9ed218818422b0f9292bc1111fae0be 100644
--- a/Framework/Algorithms/test/LineProfileTest.h
+++ b/Framework/Algorithms/test/LineProfileTest.h
@@ -58,7 +58,7 @@ public:
     TS_ASSERT_THROWS_NOTHING(alg.execute())
     TS_ASSERT(alg.isExecuted())
 
-    Workspace2D_sptr outputWS = alg.getProperty("OutputWorkspace");
+    MatrixWorkspace_sptr outputWS = alg.getProperty("OutputWorkspace");
     TS_ASSERT(outputWS);
     TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 1)
     const auto hist = outputWS->histogram(0);
@@ -88,7 +88,7 @@ public:
     }
     const int start = 2;
     const int end = nBins - 2;
-    Workspace2D_sptr outputWS =
+    MatrixWorkspace_sptr outputWS =
         profileOverTwoSpectra(inputWS, start, end, "Sum");
     TS_ASSERT(outputWS);
     TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 1)
@@ -135,7 +135,7 @@ public:
     TS_ASSERT_THROWS_NOTHING(alg.execute())
     TS_ASSERT(alg.isExecuted())
 
-    Workspace2D_sptr outputWS = alg.getProperty("OutputWorkspace");
+    MatrixWorkspace_sptr outputWS = alg.getProperty("OutputWorkspace");
     TS_ASSERT(outputWS);
     TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 1)
     const auto hist = outputWS->histogram(0);
@@ -175,7 +175,7 @@ public:
     TS_ASSERT_THROWS_NOTHING(alg.execute())
     TS_ASSERT(alg.isExecuted())
 
-    Workspace2D_sptr outputWS = alg.getProperty("OutputWorkspace");
+    MatrixWorkspace_sptr outputWS = alg.getProperty("OutputWorkspace");
     TS_ASSERT(outputWS);
     TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 1)
     const auto hist = outputWS->histogram(0);
@@ -215,7 +215,7 @@ public:
     TS_ASSERT_THROWS_NOTHING(alg.execute())
     TS_ASSERT(alg.isExecuted())
 
-    Workspace2D_sptr outputWS = alg.getProperty("OutputWorkspace");
+    MatrixWorkspace_sptr outputWS = alg.getProperty("OutputWorkspace");
     TS_ASSERT(outputWS);
     TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 1)
     const auto hist = outputWS->histogram(0);
@@ -258,7 +258,7 @@ public:
     TS_ASSERT_THROWS_NOTHING(alg.execute())
     TS_ASSERT(alg.isExecuted())
 
-    Workspace2D_sptr outputWS = alg.getProperty("OutputWorkspace");
+    MatrixWorkspace_sptr outputWS = alg.getProperty("OutputWorkspace");
     TS_ASSERT(outputWS);
     TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 1)
     const auto hist = outputWS->histogram(0);
@@ -366,7 +366,7 @@ public:
     TS_ASSERT_THROWS_NOTHING(alg.execute())
     TS_ASSERT(alg.isExecuted())
 
-    Workspace2D_sptr outputWS = alg.getProperty("OutputWorkspace");
+    MatrixWorkspace_sptr outputWS = alg.getProperty("OutputWorkspace");
     TS_ASSERT(outputWS);
     TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 1)
     const auto hist = outputWS->histogram(0);
@@ -391,9 +391,9 @@ public:
   }
 
 private:
-  Workspace2D_sptr profileOverTwoSpectra(MatrixWorkspace_sptr inputWS,
-                                         const int start, const int end,
-                                         const std::string &mode) {
+  MatrixWorkspace_sptr profileOverTwoSpectra(MatrixWorkspace_sptr inputWS,
+                                             const int start, const int end,
+                                             const std::string &mode) {
     LineProfile alg;
     // Don't put output in ADS by default
     alg.setChild(true);
@@ -413,7 +413,7 @@ private:
     TS_ASSERT_THROWS_NOTHING(alg.execute())
     TS_ASSERT(alg.isExecuted())
 
-    Workspace2D_sptr outputWS = alg.getProperty("OutputWorkspace");
+    MatrixWorkspace_sptr outputWS = alg.getProperty("OutputWorkspace");
     return outputWS;
   }
 };
diff --git a/Framework/Algorithms/test/PolarizationEfficiencyCorTest.h b/Framework/Algorithms/test/PolarizationEfficiencyCorTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..08999a4f7669142c541cefce5ab2913b0f57854b
--- /dev/null
+++ b/Framework/Algorithms/test/PolarizationEfficiencyCorTest.h
@@ -0,0 +1,1990 @@
+#ifndef MANTID_ALGORITHMS_POLARIZATIONEFFICIENCYCORTEST_H_
+#define MANTID_ALGORITHMS_POLARIZATIONEFFICIENCYCORTEST_H_
+
+#include <cxxtest/TestSuite.h>
+
+#include "MantidAlgorithms/PolarizationEfficiencyCor.h"
+
+#include "MantidAPI/AlgorithmManager.h"
+#include "MantidAPI/AnalysisDataService.h"
+#include "MantidAPI/MatrixWorkspace.h"
+#include "MantidAPI/TextAxis.h"
+#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidAPI/WorkspaceGroup.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidDataObjects/WorkspaceCreation.h"
+
+#include <Eigen/Dense>
+
+using Mantid::Algorithms::PolarizationEfficiencyCor;
+
+class PolarizationEfficiencyCorTest : 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 PolarizationEfficiencyCorTest *createSuite() {
+    return new PolarizationEfficiencyCorTest();
+  }
+  static void destroySuite(PolarizationEfficiencyCorTest *suite) {
+    delete suite;
+  }
+
+  void tearDown() override {
+    using namespace Mantid::API;
+    AnalysisDataService::Instance().clear();
+  }
+
+  void test_Init() {
+    PolarizationEfficiencyCor alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+  }
+
+  void test_IdealCaseFullCorrections() {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    constexpr size_t nBins{3};
+    constexpr size_t nHist{2};
+    BinEdges edges{0.3, 0.6, 0.9, 1.2};
+    const double yVal = 2.3;
+    Counts counts{yVal, 4.2 * yVal, yVal};
+    MatrixWorkspace_sptr ws00 =
+        create<Workspace2D>(nHist, Histogram(edges, counts));
+    MatrixWorkspace_sptr ws01 = ws00->clone();
+    MatrixWorkspace_sptr ws10 = ws00->clone();
+    MatrixWorkspace_sptr ws11 = ws00->clone();
+    const std::vector<std::string> wsNames{{"ws00", "ws01", "ws10", "ws11"}};
+    const std::array<MatrixWorkspace_sptr, 4> wsList{{ws00, ws01, ws10, ws11}};
+    for (size_t i = 0; i != 4; ++i) {
+      for (size_t j = 0; j != nHist; ++j) {
+        wsList[i]->mutableY(j) *= static_cast<double>(i + 1);
+        wsList[i]->mutableE(j) *= static_cast<double>(i + 1);
+      }
+      AnalysisDataService::Instance().addOrReplace(wsNames[i], wsList[i]);
+    }
+    auto effWS = idealEfficiencies(edges);
+    PolarizationEfficiencyCor alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspaces", wsNames))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", m_outputWSName))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Efficiencies", effWS))
+    TS_ASSERT_THROWS_NOTHING(alg.execute())
+    TS_ASSERT(alg.isExecuted())
+    WorkspaceGroup_sptr outputWS = alg.getProperty("OutputWorkspace");
+    TS_ASSERT(outputWS)
+    TS_ASSERT_EQUALS(outputWS->getNumberOfEntries(), 4)
+    const std::array<std::string, 4> POL_DIRS{{"++", "+-", "-+", "--"}};
+    for (size_t i = 0; i != 4; ++i) {
+      const std::string wsName =
+          m_outputWSName + std::string("_") + POL_DIRS[i];
+      MatrixWorkspace_sptr ws = boost::dynamic_pointer_cast<MatrixWorkspace>(
+          outputWS->getItem(wsName));
+      TS_ASSERT(ws)
+      TS_ASSERT_EQUALS(ws->getNumberHistograms(), nHist)
+      for (size_t j = 0; j != nHist; ++j) {
+        const auto &xs = ws->x(j);
+        const auto &ys = ws->y(j);
+        const auto &es = ws->e(j);
+        TS_ASSERT_EQUALS(ys.size(), nBins)
+        for (size_t k = 0; k != nBins; ++k) {
+          const double y = counts[k];
+          TS_ASSERT_EQUALS(xs[k], edges[k])
+          TS_ASSERT_EQUALS(ys[k], y * static_cast<double>(i + 1))
+          TS_ASSERT_EQUALS(es[k], std::sqrt(y) * static_cast<double>(i + 1))
+        }
+      }
+    }
+  }
+
+  void test_IdealCaseThreeInputs10Missing() { idealThreeInputsTest("10"); }
+
+  void test_IdealCaseThreeInputs01Missing() { idealThreeInputsTest("01"); }
+
+  void test_IdealCaseTwoInputsWithAnalyzer() {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    constexpr size_t nBins{3};
+    constexpr size_t nHist{2};
+    BinEdges edges{0.3, 0.6, 0.9, 1.2};
+    const double yVal = 2.3;
+    Counts counts{yVal, 4.2 * yVal, yVal};
+    MatrixWorkspace_sptr ws00 =
+        create<Workspace2D>(nHist, Histogram(edges, counts));
+    MatrixWorkspace_sptr ws11 = ws00->clone();
+    const std::vector<std::string> wsNames{
+        std::initializer_list<std::string>{"ws00", "ws11"}};
+    const std::array<MatrixWorkspace_sptr, 2> wsList{{ws00, ws11}};
+    for (size_t i = 0; i != nHist; ++i) {
+      ws11->mutableY(i) *= 2.;
+      ws11->mutableE(i) *= 2.;
+    }
+    AnalysisDataService::Instance().addOrReplace(wsNames.front(),
+                                                 wsList.front());
+    AnalysisDataService::Instance().addOrReplace(wsNames.back(), wsList.back());
+    auto effWS = idealEfficiencies(edges);
+    PolarizationEfficiencyCor alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspaces", wsNames))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", m_outputWSName))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Efficiencies", effWS))
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Flippers", "00, 11"))
+    TS_ASSERT_THROWS_NOTHING(alg.execute())
+    TS_ASSERT(alg.isExecuted())
+    WorkspaceGroup_sptr outputWS = alg.getProperty("OutputWorkspace");
+    TS_ASSERT(outputWS)
+    TS_ASSERT_EQUALS(outputWS->getNumberOfEntries(), 4)
+    const std::array<std::string, 4> POL_DIRS{{"++", "+-", "-+", "--"}};
+    for (size_t i = 0; i != 4; ++i) {
+      const auto &dir = POL_DIRS[i];
+      const std::string wsName = m_outputWSName + std::string("_") + dir;
+      MatrixWorkspace_sptr ws = boost::dynamic_pointer_cast<MatrixWorkspace>(
+          outputWS->getItem(wsName));
+      TS_ASSERT(ws)
+      TS_ASSERT_EQUALS(ws->getNumberHistograms(), nHist)
+      for (size_t j = 0; j != nHist; ++j) {
+        const auto &xs = ws->x(j);
+        const auto &ys = ws->y(j);
+        const auto &es = ws->e(j);
+        TS_ASSERT_EQUALS(ys.size(), nBins)
+        for (size_t k = 0; k != nBins; ++k) {
+          const double y = counts[k];
+          const double expected = [y, &dir]() {
+            if (dir == "++") {
+              return y;
+            } else if (dir == "--") {
+              return 2. * y;
+            } else {
+              return 0.;
+            }
+          }();
+          const double expectedError = [y, &dir]() {
+            if (dir == "++") {
+              return std::sqrt(y);
+            } else if (dir == "--") {
+              return 2. * std::sqrt(y);
+            } else {
+              return 0.;
+            }
+          }();
+          TS_ASSERT_EQUALS(xs[k], edges[k])
+          TS_ASSERT_EQUALS(ys[k], expected)
+          TS_ASSERT_EQUALS(es[k], expectedError)
+        }
+      }
+    }
+  }
+
+  void test_IdealCaseTwoInputsNoAnalyzer() {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    constexpr size_t nBins{3};
+    constexpr size_t nHist{2};
+    BinEdges edges{0.3, 0.6, 0.9, 1.2};
+    const double yVal = 2.3;
+    Counts counts{yVal, 4.2 * yVal, yVal};
+    MatrixWorkspace_sptr ws00 =
+        create<Workspace2D>(nHist, Histogram(edges, counts));
+    MatrixWorkspace_sptr ws11 = ws00->clone();
+    const std::vector<std::string> wsNames{
+        std::initializer_list<std::string>{"ws00", "ws11"}};
+    const std::array<MatrixWorkspace_sptr, 2> wsList{{ws00, ws11}};
+    for (size_t i = 0; i != nHist; ++i) {
+      ws11->mutableY(i) *= 2.;
+      ws11->mutableE(i) *= 2.;
+    }
+    AnalysisDataService::Instance().addOrReplace(wsNames.front(),
+                                                 wsList.front());
+    AnalysisDataService::Instance().addOrReplace(wsNames.back(), wsList.back());
+    auto effWS = idealEfficiencies(edges);
+    PolarizationEfficiencyCor alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspaces", wsNames))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", m_outputWSName))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Efficiencies", effWS))
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Flippers", "0, 1"))
+    TS_ASSERT_THROWS_NOTHING(alg.execute())
+    TS_ASSERT(alg.isExecuted())
+    WorkspaceGroup_sptr outputWS = alg.getProperty("OutputWorkspace");
+    TS_ASSERT(outputWS)
+    TS_ASSERT_EQUALS(outputWS->getNumberOfEntries(), 2)
+    const std::array<std::string, 2> POL_DIRS{{"++", "--"}};
+    for (size_t i = 0; i != 2; ++i) {
+      const auto &dir = POL_DIRS[i];
+      const std::string wsName = m_outputWSName + std::string("_") + dir;
+      MatrixWorkspace_sptr ws = boost::dynamic_pointer_cast<MatrixWorkspace>(
+          outputWS->getItem(wsName));
+      TS_ASSERT(ws)
+      TS_ASSERT_EQUALS(ws->getNumberHistograms(), nHist)
+      for (size_t j = 0; j != nHist; ++j) {
+        const auto &xs = ws->x(j);
+        const auto &ys = ws->y(j);
+        const auto &es = ws->e(j);
+        TS_ASSERT_EQUALS(ys.size(), nBins)
+        for (size_t k = 0; k != nBins; ++k) {
+          const double y = counts[k];
+          TS_ASSERT_EQUALS(xs[k], edges[k])
+          TS_ASSERT_EQUALS(ys[k], y * static_cast<double>(i + 1))
+          TS_ASSERT_EQUALS(es[k], std::sqrt(y) * static_cast<double>(i + 1))
+        }
+      }
+    }
+  }
+
+  void test_IdealCaseDirectBeamCorrections() {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    constexpr size_t nBins{3};
+    constexpr size_t nHist{2};
+    BinEdges edges{0.3, 0.6, 0.9, 1.2};
+    const double yVal = 2.3;
+    Counts counts{yVal, 4.2 * yVal, yVal};
+    MatrixWorkspace_sptr ws00 =
+        create<Workspace2D>(nHist, Histogram(edges, counts));
+    const std::vector<std::string> wsNames{{"ws00"}};
+    AnalysisDataService::Instance().addOrReplace(wsNames.front(), ws00);
+    auto effWS = idealEfficiencies(edges);
+    PolarizationEfficiencyCor alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspaces", wsNames))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", m_outputWSName))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Efficiencies", effWS))
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Flippers", "0"))
+    TS_ASSERT_THROWS_NOTHING(alg.execute())
+    TS_ASSERT(alg.isExecuted())
+    WorkspaceGroup_sptr outputWS = alg.getProperty("OutputWorkspace");
+    TS_ASSERT(outputWS)
+    TS_ASSERT_EQUALS(outputWS->getNumberOfEntries(), 1)
+    MatrixWorkspace_sptr ws = boost::dynamic_pointer_cast<MatrixWorkspace>(
+        outputWS->getItem(m_outputWSName + std::string("_++")));
+    TS_ASSERT(ws)
+    TS_ASSERT_EQUALS(ws->getNumberHistograms(), nHist)
+    for (size_t i = 0; i != nHist; ++i) {
+      const auto &xs = ws->x(i);
+      const auto &ys = ws->y(i);
+      const auto &es = ws->e(i);
+      TS_ASSERT_EQUALS(ys.size(), nBins)
+      for (size_t j = 0; j != nBins; ++j) {
+        const double y = counts[j];
+        TS_ASSERT_EQUALS(xs[j], edges[j])
+        TS_ASSERT_EQUALS(ys[j], y)
+        TS_ASSERT_EQUALS(es[j], std::sqrt(y))
+      }
+    }
+  }
+
+  void test_FullCorrections() {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    constexpr size_t nHist{2};
+    BinEdges edges{0.3, 0.6, 0.9, 1.2};
+    const double yVal = 2.3;
+    Counts counts{yVal, yVal, yVal};
+    MatrixWorkspace_sptr ws00 =
+        create<Workspace2D>(nHist, Histogram(edges, counts));
+    MatrixWorkspace_sptr ws01 = ws00->clone();
+    MatrixWorkspace_sptr ws10 = ws00->clone();
+    MatrixWorkspace_sptr ws11 = ws00->clone();
+    const std::vector<std::string> wsNames{{"ws00", "ws01", "ws10", "ws11"}};
+    const std::array<MatrixWorkspace_sptr, 4> wsList{{ws00, ws01, ws10, ws11}};
+    for (size_t i = 0; i != 4; ++i) {
+      for (size_t j = 0; j != nHist; ++j) {
+        wsList[i]->mutableY(j) *= static_cast<double>(i + 1);
+        wsList[i]->mutableE(j) *= static_cast<double>(i + 1);
+      }
+      AnalysisDataService::Instance().addOrReplace(wsNames[i], wsList[i]);
+    }
+    auto effWS = efficiencies(edges);
+    PolarizationEfficiencyCor alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspaces", wsNames))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", m_outputWSName))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Efficiencies", effWS))
+    TS_ASSERT_THROWS_NOTHING(alg.execute())
+    TS_ASSERT(alg.isExecuted())
+    WorkspaceGroup_sptr outputWS = alg.getProperty("OutputWorkspace");
+    TS_ASSERT(outputWS)
+    TS_ASSERT_EQUALS(outputWS->getNumberOfEntries(), 4)
+    fullFourInputsResultsCheck(outputWS, ws00, ws01, ws10, ws11, effWS);
+  }
+
+  void test_ThreeInputsWithMissing01FlipperConfiguration() {
+    threeInputsTest("01");
+  }
+
+  void test_ThreeInputsWithMissing10FlipperConfiguration() {
+    threeInputsTest("10");
+  }
+
+  void test_TwoInputsWithAnalyzer() {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    constexpr size_t nHist{2};
+    constexpr size_t nBins{3};
+    BinEdges edges{0.3, 0.6, 0.9, 1.2};
+    const double yVal = 2.3;
+    Counts counts{yVal, yVal, yVal};
+    MatrixWorkspace_sptr ws00 =
+        create<Workspace2D>(nHist, Histogram(edges, counts));
+    MatrixWorkspace_sptr ws01 = nullptr;
+    MatrixWorkspace_sptr ws10 = nullptr;
+    MatrixWorkspace_sptr ws11 = ws00->clone();
+    const std::vector<std::string> wsNames{
+        std::initializer_list<std::string>{"ws00", "ws11"}};
+    const std::array<MatrixWorkspace_sptr, 2> wsList{{ws00, ws11}};
+    for (size_t i = 0; i != 2; ++i) {
+      for (size_t j = 0; j != nHist; ++j) {
+        wsList[i]->mutableY(j) *= static_cast<double>(i + 1);
+        wsList[i]->mutableE(j) *= static_cast<double>(i + 1);
+      }
+      AnalysisDataService::Instance().addOrReplace(wsNames[i], wsList[i]);
+    }
+    auto effWS = efficiencies(edges);
+    PolarizationEfficiencyCor alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspaces", wsNames))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", m_outputWSName))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Efficiencies", effWS))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Flippers", "00, 11"))
+    TS_ASSERT_THROWS_NOTHING(alg.execute())
+    TS_ASSERT(alg.isExecuted())
+    WorkspaceGroup_sptr outputWS = alg.getProperty("OutputWorkspace");
+    TS_ASSERT(outputWS)
+    TS_ASSERT_EQUALS(outputWS->getNumberOfEntries(), 4)
+    solveMissingIntensities(ws00, ws01, ws10, ws11, effWS);
+    using namespace Mantid::API;
+    const double F1 = effWS->y(0).front();
+    const double F1e = effWS->e(0).front();
+    const double F2 = effWS->y(1).front();
+    const double F2e = effWS->e(1).front();
+    const double P1 = effWS->y(2).front();
+    const double P1e = effWS->e(2).front();
+    const double P2 = effWS->y(3).front();
+    const double P2e = effWS->e(3).front();
+    const Eigen::Vector4d y{ws00->y(0).front(), ws01->y(0).front(),
+                            ws10->y(0).front(), ws11->y(0).front()};
+    const auto expected = correction(y, F1, F2, P1, P2);
+    const Eigen::Vector4d e{ws00->e(0).front(), ws01->e(0).front(),
+                            ws10->e(0).front(), ws11->e(0).front()};
+    const auto expectedError = error(y, e, F1, F1e, F2, F2e, P1, P1e, P2, P2e);
+    MatrixWorkspace_sptr ppWS = boost::dynamic_pointer_cast<MatrixWorkspace>(
+        outputWS->getItem(m_outputWSName + std::string("_++")));
+    MatrixWorkspace_sptr pmWS = boost::dynamic_pointer_cast<MatrixWorkspace>(
+        outputWS->getItem(m_outputWSName + std::string("_+-")));
+    MatrixWorkspace_sptr mpWS = boost::dynamic_pointer_cast<MatrixWorkspace>(
+        outputWS->getItem(m_outputWSName + std::string("_-+")));
+    MatrixWorkspace_sptr mmWS = boost::dynamic_pointer_cast<MatrixWorkspace>(
+        outputWS->getItem(m_outputWSName + std::string("_--")));
+    TS_ASSERT(ppWS)
+    TS_ASSERT(pmWS)
+    TS_ASSERT(mpWS)
+    TS_ASSERT(mmWS)
+    TS_ASSERT_EQUALS(ppWS->getNumberHistograms(), nHist)
+    TS_ASSERT_EQUALS(pmWS->getNumberHistograms(), nHist)
+    TS_ASSERT_EQUALS(mpWS->getNumberHistograms(), nHist)
+    TS_ASSERT_EQUALS(mmWS->getNumberHistograms(), nHist)
+    for (size_t j = 0; j != nHist; ++j) {
+      const auto &ppX = ppWS->x(j);
+      const auto &ppY = ppWS->y(j);
+      const auto &ppE = ppWS->e(j);
+      const auto &pmX = pmWS->x(j);
+      const auto &pmY = pmWS->y(j);
+      const auto &pmE = pmWS->e(j);
+      const auto &mpX = mpWS->x(j);
+      const auto &mpY = mpWS->y(j);
+      const auto &mpE = mpWS->e(j);
+      const auto &mmX = mmWS->x(j);
+      const auto &mmY = mmWS->y(j);
+      const auto &mmE = mmWS->e(j);
+      TS_ASSERT_EQUALS(ppY.size(), nBins)
+      TS_ASSERT_EQUALS(pmY.size(), nBins)
+      TS_ASSERT_EQUALS(mpY.size(), nBins)
+      TS_ASSERT_EQUALS(mmY.size(), nBins)
+      for (size_t k = 0; k != nBins; ++k) {
+        TS_ASSERT_EQUALS(ppX[k], edges[k])
+        TS_ASSERT_EQUALS(pmX[k], edges[k])
+        TS_ASSERT_EQUALS(mpX[k], edges[k])
+        TS_ASSERT_EQUALS(mmX[k], edges[k])
+        TS_ASSERT_DELTA(ppY[k], expected[0], 1e-12)
+        TS_ASSERT_DELTA(pmY[k], expected[1], 1e-12)
+        TS_ASSERT_DELTA(mpY[k], expected[2], 1e-12)
+        TS_ASSERT_DELTA(mmY[k], expected[3], 1e-12)
+        // This test constructs the expected missing I01 and I10 intensities
+        // slightly different from what the algorithm does: I10 is solved
+        // first and then I01 is solved using all I00, I10 and I11. This
+        // results in slightly larger errors estimates for I01 and thus for
+        // the final corrected expected intensities.
+        TS_ASSERT_DELTA(ppE[k], expectedError[0], 1e-6)
+        TS_ASSERT_LESS_THAN(ppE[k], expectedError[0])
+        TS_ASSERT_DELTA(pmE[k], expectedError[1], 1e-2)
+        TS_ASSERT_LESS_THAN(pmE[k], expectedError[1])
+        TS_ASSERT_DELTA(mpE[k], expectedError[2], 1e-7)
+        TS_ASSERT_LESS_THAN(mpE[k], expectedError[2])
+        TS_ASSERT_DELTA(mmE[k], expectedError[3], 1e-5)
+        TS_ASSERT_LESS_THAN(mmE[k], expectedError[3])
+      }
+    }
+  }
+
+  void test_TwoInputsWithoutAnalyzer() {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    constexpr size_t nHist{2};
+    constexpr size_t nBins{3};
+    BinEdges edges{0.3, 0.6, 0.9, 1.2};
+    const double yVal = 2.3;
+    Counts counts{yVal, yVal, yVal};
+    MatrixWorkspace_sptr ws00 =
+        create<Workspace2D>(nHist, Histogram(edges, counts));
+    MatrixWorkspace_sptr ws11 = ws00->clone();
+    const std::vector<std::string> wsNames{
+        std::initializer_list<std::string>{"ws00", "ws11"}};
+    const std::array<MatrixWorkspace_sptr, 2> wsList{{ws00, ws11}};
+    for (size_t i = 0; i != 2; ++i) {
+      for (size_t j = 0; j != nHist; ++j) {
+        wsList[i]->mutableY(j) *= static_cast<double>(i + 1);
+        wsList[i]->mutableE(j) *= static_cast<double>(i + 1);
+      }
+      AnalysisDataService::Instance().addOrReplace(wsNames[i], wsList[i]);
+    }
+    auto effWS = efficiencies(edges);
+    PolarizationEfficiencyCor alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspaces", wsNames))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", m_outputWSName))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Efficiencies", effWS))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Flippers", "0, 1"))
+    TS_ASSERT_THROWS_NOTHING(alg.execute())
+    TS_ASSERT(alg.isExecuted())
+    WorkspaceGroup_sptr outputWS = alg.getProperty("OutputWorkspace");
+    TS_ASSERT(outputWS)
+    TS_ASSERT_EQUALS(outputWS->getNumberOfEntries(), 2)
+    const double F1 = effWS->y(0).front();
+    const double F1e = effWS->e(0).front();
+    const double P1 = effWS->y(2).front();
+    const double P1e = effWS->e(2).front();
+    const Eigen::Vector2d y{ws00->y(0).front(), ws11->y(0).front()};
+    const auto expected = correctionWithoutAnalyzer(y, F1, P1);
+    const Eigen::Vector2d e{ws00->e(0).front(), ws11->e(0).front()};
+    const auto expectedError = errorWithoutAnalyzer(y, e, F1, F1e, P1, P1e);
+    MatrixWorkspace_sptr ppWS = boost::dynamic_pointer_cast<MatrixWorkspace>(
+        outputWS->getItem(m_outputWSName + std::string("_++")));
+    MatrixWorkspace_sptr mmWS = boost::dynamic_pointer_cast<MatrixWorkspace>(
+        outputWS->getItem(m_outputWSName + std::string("_--")));
+    TS_ASSERT(ppWS)
+    TS_ASSERT(mmWS)
+    TS_ASSERT_EQUALS(ppWS->getNumberHistograms(), nHist)
+    TS_ASSERT_EQUALS(mmWS->getNumberHistograms(), nHist)
+    for (size_t j = 0; j != nHist; ++j) {
+      const auto &ppX = ppWS->x(j);
+      const auto &ppY = ppWS->y(j);
+      const auto &ppE = ppWS->e(j);
+      const auto &mmX = mmWS->x(j);
+      const auto &mmY = mmWS->y(j);
+      const auto &mmE = mmWS->e(j);
+      TS_ASSERT_EQUALS(ppY.size(), nBins)
+      TS_ASSERT_EQUALS(mmY.size(), nBins)
+      for (size_t k = 0; k != nBins; ++k) {
+        TS_ASSERT_EQUALS(ppX[k], edges[k])
+        TS_ASSERT_EQUALS(mmX[k], edges[k])
+        TS_ASSERT_DELTA(ppY[k], expected[0], 1e-12)
+        TS_ASSERT_DELTA(mmY[k], expected[1], 1e-12)
+        TS_ASSERT_DELTA(ppE[k], expectedError[0], 1e-12)
+        TS_ASSERT_DELTA(mmE[k], expectedError[1], 1e-12)
+      }
+    }
+  }
+
+  void test_directBeamOnlyInput() {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    constexpr size_t nHist{2};
+    constexpr size_t nBins{3};
+    BinEdges edges{0.3, 0.6, 0.9, 1.2};
+    const double yVal = 2.3;
+    Counts counts{yVal, yVal, yVal};
+    MatrixWorkspace_sptr ws00 =
+        create<Workspace2D>(nHist, Histogram(edges, counts));
+    const std::string wsName{"ws00"};
+    AnalysisDataService::Instance().addOrReplace(wsName, ws00);
+    auto effWS = efficiencies(edges);
+    PolarizationEfficiencyCor alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("InputWorkspaces", wsName))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", m_outputWSName))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Efficiencies", effWS))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Flippers", "0"))
+    TS_ASSERT_THROWS_NOTHING(alg.execute())
+    TS_ASSERT(alg.isExecuted())
+    WorkspaceGroup_sptr outputWS = alg.getProperty("OutputWorkspace");
+    TS_ASSERT(outputWS)
+    TS_ASSERT_EQUALS(outputWS->getNumberOfEntries(), 1)
+    const auto P1 = effWS->y(2).front();
+    const auto P1e = effWS->e(2).front();
+    const auto P2 = effWS->y(3).front();
+    const auto P2e = effWS->e(3).front();
+    const double y{ws00->y(0).front()};
+    const auto inverted = 1. / (1. - P2 - P1 + 2. * P1 * P2);
+    const auto expected = inverted * y;
+    const double e{ws00->e(0).front()};
+    const auto errorP1 = P1e * y * (2. * P1 - 1.) * inverted * inverted;
+    const auto errorP2 = P2e * y * (2. * P2 - 1.) * inverted * inverted;
+    const auto errorY = e * e * inverted * inverted;
+    const auto expectedError =
+        std::sqrt(errorP1 * errorP1 + errorP2 * errorP2 + errorY);
+    MatrixWorkspace_sptr ppWS = boost::dynamic_pointer_cast<MatrixWorkspace>(
+        outputWS->getItem(m_outputWSName + std::string("_++")));
+    TS_ASSERT(ppWS)
+    TS_ASSERT_EQUALS(ppWS->getNumberHistograms(), nHist)
+    for (size_t j = 0; j != nHist; ++j) {
+      const auto &ppX = ppWS->x(j);
+      const auto &ppY = ppWS->y(j);
+      const auto &ppE = ppWS->e(j);
+      TS_ASSERT_EQUALS(ppY.size(), nBins)
+      for (size_t k = 0; k != nBins; ++k) {
+        TS_ASSERT_EQUALS(ppX[k], edges[k])
+        TS_ASSERT_DELTA(ppY[k], expected, 1e-12)
+        TS_ASSERT_DELTA(ppE[k], expectedError, 1e-12)
+      }
+    }
+  }
+
+  void test_FailureWhenEfficiencyHistogramIsMissing() {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    BinEdges edges{0.3, 0.6, 0.9, 1.2};
+    Counts counts{0., 0., 0.};
+    MatrixWorkspace_sptr ws00 =
+        create<Workspace2D>(1, Histogram(edges, counts));
+    const std::string wsName{"ws00"};
+    AnalysisDataService::Instance().addOrReplace(wsName, ws00);
+    auto effWS = idealEfficiencies(edges);
+    // Rename F1 to something else.
+    auto axis = make_unique<TextAxis>(4);
+    axis->setLabel(0, "__wrong_histogram_label");
+    axis->setLabel(1, "F2");
+    axis->setLabel(2, "P1");
+    axis->setLabel(3, "P2");
+    effWS->replaceAxis(1, axis.release());
+    PolarizationEfficiencyCor alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("InputWorkspaces", wsName))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", m_outputWSName))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Efficiencies", effWS))
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Flippers", "0"))
+    TS_ASSERT_THROWS(alg.execute(), std::runtime_error)
+    TS_ASSERT(!alg.isExecuted())
+  }
+
+  void test_FailureWhenEfficiencyXDataMismatches() {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    BinEdges edges{0.3, 0.6, 0.9, 1.2};
+    Counts counts{0., 0., 0.};
+    MatrixWorkspace_sptr ws00 =
+        create<Workspace2D>(1, Histogram(edges, counts));
+    const std::string wsName{"ws00"};
+    AnalysisDataService::Instance().addOrReplace(wsName, ws00);
+    auto effWS = idealEfficiencies(edges);
+    // Change a bin edge of one of the histograms.
+    auto &xs = effWS->mutableX(0);
+    xs[xs.size() / 2] *= 1.01;
+    PolarizationEfficiencyCor alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("InputWorkspaces", wsName))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", m_outputWSName))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Efficiencies", effWS))
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Flippers", "0"))
+    TS_ASSERT_THROWS(alg.execute(), std::runtime_error)
+    TS_ASSERT(!alg.isExecuted())
+  }
+
+  void test_FailureWhenNumberOfHistogramsInInputWorkspacesMismatch() {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    constexpr size_t nHist{2};
+    BinEdges edges{0.3, 0.6, 0.9, 1.2};
+    Counts counts{0., 0., 0.};
+    MatrixWorkspace_sptr ws00 =
+        create<Workspace2D>(nHist, Histogram(edges, counts));
+    MatrixWorkspace_sptr ws01 = ws00->clone();
+    MatrixWorkspace_sptr ws10 =
+        create<Workspace2D>(nHist + 1, Histogram(edges, counts));
+    MatrixWorkspace_sptr ws11 = ws00->clone();
+    const std::vector<std::string> wsNames{{"ws00", "ws01", "ws10", "ws11"}};
+    const std::array<MatrixWorkspace_sptr, 4> wsList{{ws00, ws01, ws10, ws11}};
+    for (size_t i = 0; i != 4; ++i) {
+      AnalysisDataService::Instance().addOrReplace(wsNames[i], wsList[i]);
+    }
+    auto effWS = idealEfficiencies(edges);
+    PolarizationEfficiencyCor alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspaces", wsNames))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", m_outputWSName))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Efficiencies", effWS))
+    TS_ASSERT_THROWS(alg.execute(), std::runtime_error)
+    TS_ASSERT(!alg.isExecuted())
+  }
+
+  void test_FailureWhenAnInputWorkspaceIsMissing() {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    constexpr size_t nHist{2};
+    BinEdges edges{0.3, 0.6, 0.9, 1.2};
+    Counts counts{0., 0., 0.};
+    MatrixWorkspace_sptr ws00 =
+        create<Workspace2D>(nHist, Histogram(edges, counts));
+    MatrixWorkspace_sptr ws01 = ws00->clone();
+    MatrixWorkspace_sptr ws11 = ws00->clone();
+    AnalysisDataService::Instance().addOrReplace("ws00", ws00);
+    AnalysisDataService::Instance().addOrReplace("ws01", ws01);
+    AnalysisDataService::Instance().addOrReplace("ws11", ws11);
+    PolarizationEfficiencyCor alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS(
+        alg.setPropertyValue("InputWorkspaces", "ws00, ws01, ws10, ws11"),
+        std::invalid_argument)
+  }
+
+private:
+  const std::string m_outputWSName{"output"};
+
+  Mantid::API::MatrixWorkspace_sptr
+  efficiencies(const Mantid::HistogramData::BinEdges &edges) {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    const auto nBins = edges.size() - 1;
+    constexpr size_t nHist{4};
+    Counts counts(nBins, 0.0);
+    MatrixWorkspace_sptr ws =
+        create<Workspace2D>(nHist, Histogram(edges, counts));
+    ws->mutableY(0) = 0.95;
+    ws->mutableE(0) = 0.01;
+    ws->mutableY(1) = 0.92;
+    ws->mutableE(1) = 0.02;
+    ws->mutableY(2) = 0.05;
+    ws->mutableE(2) = 0.015;
+    ws->mutableY(3) = 0.04;
+    ws->mutableE(3) = 0.03;
+    auto axis = make_unique<TextAxis>(4);
+    axis->setLabel(0, "F1");
+    axis->setLabel(1, "F2");
+    axis->setLabel(2, "P1");
+    axis->setLabel(3, "P2");
+    ws->replaceAxis(1, axis.release());
+    return ws;
+  }
+
+  Mantid::API::MatrixWorkspace_sptr
+  idealEfficiencies(const Mantid::HistogramData::BinEdges &edges) {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    const auto nBins = edges.size() - 1;
+    constexpr size_t nHist{4};
+    Counts counts(nBins, 0.0);
+    MatrixWorkspace_sptr ws =
+        create<Workspace2D>(nHist, Histogram(edges, counts));
+    ws->mutableY(0) = 1.;
+    ws->mutableY(1) = 1.;
+    auto axis = make_unique<TextAxis>(4);
+    axis->setLabel(0, "F1");
+    axis->setLabel(1, "F2");
+    axis->setLabel(2, "P1");
+    axis->setLabel(3, "P2");
+    ws->replaceAxis(1, axis.release());
+    return ws;
+  }
+
+  void idealThreeInputsTest(const std::string &missingFlipperConf) {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    constexpr size_t nBins{3};
+    constexpr size_t nHist{2};
+    BinEdges edges{0.3, 0.6, 0.9, 1.2};
+    const double yVal = 2.3;
+    Counts counts{yVal, 4.2 * yVal, yVal};
+    MatrixWorkspace_sptr ws00 =
+        create<Workspace2D>(nHist, Histogram(edges, counts));
+    MatrixWorkspace_sptr wsXX = ws00->clone();
+    MatrixWorkspace_sptr ws11 = ws00->clone();
+    const std::vector<std::string> wsNames{{"ws00", "wsXX", "ws11"}};
+    const std::array<MatrixWorkspace_sptr, 3> wsList{{ws00, wsXX, ws11}};
+    for (size_t i = 0; i != 3; ++i) {
+      for (size_t j = 0; j != nHist; ++j) {
+        wsList[i]->mutableY(j) *= static_cast<double>(i + 1);
+        wsList[i]->mutableE(j) *= static_cast<double>(i + 1);
+      }
+      AnalysisDataService::Instance().addOrReplace(wsNames[i], wsList[i]);
+    }
+    auto effWS = idealEfficiencies(edges);
+    PolarizationEfficiencyCor alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspaces", wsNames))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", m_outputWSName))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Efficiencies", effWS))
+    const std::string presentFlipperConf =
+        missingFlipperConf == "01" ? "10" : "01";
+    const std::string flipperConf = "00, " + presentFlipperConf + ", 11";
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Flippers", flipperConf))
+    TS_ASSERT_THROWS_NOTHING(alg.execute())
+    TS_ASSERT(alg.isExecuted())
+    WorkspaceGroup_sptr outputWS = alg.getProperty("OutputWorkspace");
+    TS_ASSERT(outputWS)
+    TS_ASSERT_EQUALS(outputWS->getNumberOfEntries(), 4)
+    const std::array<std::string, 4> POL_DIRS{{"++", "+-", "-+", "--"}};
+    for (size_t i = 0; i != 4; ++i) {
+      const auto &dir = POL_DIRS[i];
+      const std::string wsName = m_outputWSName + std::string("_") + dir;
+      MatrixWorkspace_sptr ws = boost::dynamic_pointer_cast<MatrixWorkspace>(
+          outputWS->getItem(wsName));
+      TS_ASSERT(ws)
+      TS_ASSERT_EQUALS(ws->getNumberHistograms(), nHist)
+      for (size_t j = 0; j != nHist; ++j) {
+        const auto &xs = ws->x(j);
+        const auto &ys = ws->y(j);
+        const auto &es = ws->e(j);
+        TS_ASSERT_EQUALS(ys.size(), nBins)
+        for (size_t k = 0; k != nBins; ++k) {
+          const double y = counts[k];
+          const double expected = [y, &dir]() {
+            if (dir == "++") {
+              return y;
+            } else if (dir == "--") {
+              return 3. * y;
+            } else {
+              return 2. * y;
+            }
+          }();
+          const double expectedError = [y, &dir, &missingFlipperConf]() {
+            if (dir == "++") {
+              return std::sqrt(y);
+            } else if (dir == "--") {
+              return 3. * std::sqrt(y);
+            } else {
+              std::string conf = std::string(dir.front() == '+' ? "0" : "1") +
+                                 std::string(dir.back() == '+' ? "0" : "1");
+              if (conf != missingFlipperConf) {
+                return 2. * std::sqrt(y);
+              } else {
+                return 0.;
+              }
+            }
+          }();
+          TS_ASSERT_EQUALS(xs[k], edges[k])
+          TS_ASSERT_EQUALS(ys[k], expected)
+          TS_ASSERT_EQUALS(es[k], expectedError)
+        }
+      }
+    }
+  }
+
+  void threeInputsTest(const std::string &missingFlipperConf) {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+    using namespace Mantid::HistogramData;
+    using namespace Mantid::Kernel;
+    constexpr size_t nHist{2};
+    BinEdges edges{0.3, 0.6, 0.9, 1.2};
+    const double yVal = 2.3;
+    Counts counts{yVal, yVal, yVal};
+    MatrixWorkspace_sptr ws00 =
+        create<Workspace2D>(nHist, Histogram(edges, counts));
+    MatrixWorkspace_sptr ws01 =
+        missingFlipperConf == "01" ? nullptr : ws00->clone();
+    MatrixWorkspace_sptr ws10 =
+        missingFlipperConf == "10" ? nullptr : ws00->clone();
+    MatrixWorkspace_sptr ws11 = ws00->clone();
+    const std::vector<std::string> wsNames{{"ws00", "wsXX", "ws11"}};
+    const std::array<MatrixWorkspace_sptr, 3> wsList{
+        {ws00, ws01 != nullptr ? ws01 : ws10, ws11}};
+    for (size_t i = 0; i != 3; ++i) {
+      for (size_t j = 0; j != nHist; ++j) {
+        wsList[i]->mutableY(j) *= static_cast<double>(i + 1);
+        wsList[i]->mutableE(j) *= static_cast<double>(i + 1);
+      }
+      AnalysisDataService::Instance().addOrReplace(wsNames[i], wsList[i]);
+    }
+    auto effWS = efficiencies(edges);
+    PolarizationEfficiencyCor alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspaces", wsNames))
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", m_outputWSName))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Efficiencies", effWS))
+    const std::string presentFlipperConf =
+        missingFlipperConf == "01" ? "10" : "01";
+    const std::string flipperConf = "00, " + presentFlipperConf + ", 11";
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Flippers", flipperConf))
+    TS_ASSERT_THROWS_NOTHING(alg.execute())
+    TS_ASSERT(alg.isExecuted())
+    WorkspaceGroup_sptr outputWS = alg.getProperty("OutputWorkspace");
+    TS_ASSERT(outputWS)
+    TS_ASSERT_EQUALS(outputWS->getNumberOfEntries(), 4)
+    solveMissingIntensity(ws00, ws01, ws10, ws11, effWS);
+    fullFourInputsResultsCheck(outputWS, ws00, ws01, ws10, ws11, effWS);
+  }
+
+  void fullFourInputsResultsCheck(Mantid::API::WorkspaceGroup_sptr &outputWS,
+                                  Mantid::API::MatrixWorkspace_sptr &ws00,
+                                  Mantid::API::MatrixWorkspace_sptr &ws01,
+                                  Mantid::API::MatrixWorkspace_sptr &ws10,
+                                  Mantid::API::MatrixWorkspace_sptr &ws11,
+                                  Mantid::API::MatrixWorkspace_sptr &effWS) {
+    using namespace Mantid::API;
+    const auto nHist = ws00->getNumberHistograms();
+    const auto nBins = ws00->y(0).size();
+    const auto edges = ws00->binEdges(0);
+    const double F1 = effWS->y(0).front();
+    const double F1e = effWS->e(0).front();
+    const double F2 = effWS->y(1).front();
+    const double F2e = effWS->e(1).front();
+    const double P1 = effWS->y(2).front();
+    const double P1e = effWS->e(2).front();
+    const double P2 = effWS->y(3).front();
+    const double P2e = effWS->e(3).front();
+    const Eigen::Vector4d y{ws00->y(0).front(), ws01->y(0).front(),
+                            ws10->y(0).front(), ws11->y(0).front()};
+    const auto expected = correction(y, F1, F2, P1, P2);
+    const Eigen::Vector4d e{ws00->e(0).front(), ws01->e(0).front(),
+                            ws10->e(0).front(), ws11->e(0).front()};
+    const auto expectedError = error(y, e, F1, F1e, F2, F2e, P1, P1e, P2, P2e);
+    MatrixWorkspace_sptr ppWS = boost::dynamic_pointer_cast<MatrixWorkspace>(
+        outputWS->getItem(m_outputWSName + std::string("_++")));
+    MatrixWorkspace_sptr pmWS = boost::dynamic_pointer_cast<MatrixWorkspace>(
+        outputWS->getItem(m_outputWSName + std::string("_+-")));
+    MatrixWorkspace_sptr mpWS = boost::dynamic_pointer_cast<MatrixWorkspace>(
+        outputWS->getItem(m_outputWSName + std::string("_-+")));
+    MatrixWorkspace_sptr mmWS = boost::dynamic_pointer_cast<MatrixWorkspace>(
+        outputWS->getItem(m_outputWSName + std::string("_--")));
+    TS_ASSERT(ppWS)
+    TS_ASSERT(pmWS)
+    TS_ASSERT(mpWS)
+    TS_ASSERT(mmWS)
+    TS_ASSERT_EQUALS(ppWS->getNumberHistograms(), nHist)
+    TS_ASSERT_EQUALS(pmWS->getNumberHistograms(), nHist)
+    TS_ASSERT_EQUALS(mpWS->getNumberHistograms(), nHist)
+    TS_ASSERT_EQUALS(mmWS->getNumberHistograms(), nHist)
+    for (size_t j = 0; j != nHist; ++j) {
+      const auto &ppX = ppWS->x(j);
+      const auto &ppY = ppWS->y(j);
+      const auto &ppE = ppWS->e(j);
+      const auto &pmX = pmWS->x(j);
+      const auto &pmY = pmWS->y(j);
+      const auto &pmE = pmWS->e(j);
+      const auto &mpX = mpWS->x(j);
+      const auto &mpY = mpWS->y(j);
+      const auto &mpE = mpWS->e(j);
+      const auto &mmX = mmWS->x(j);
+      const auto &mmY = mmWS->y(j);
+      const auto &mmE = mmWS->e(j);
+      TS_ASSERT_EQUALS(ppY.size(), nBins)
+      TS_ASSERT_EQUALS(pmY.size(), nBins)
+      TS_ASSERT_EQUALS(mpY.size(), nBins)
+      TS_ASSERT_EQUALS(mmY.size(), nBins)
+      for (size_t k = 0; k != nBins; ++k) {
+        TS_ASSERT_EQUALS(ppX[k], edges[k])
+        TS_ASSERT_EQUALS(pmX[k], edges[k])
+        TS_ASSERT_EQUALS(mpX[k], edges[k])
+        TS_ASSERT_EQUALS(mmX[k], edges[k])
+        TS_ASSERT_DELTA(ppY[k], expected[0], 1e-12)
+        TS_ASSERT_DELTA(pmY[k], expected[1], 1e-12)
+        TS_ASSERT_DELTA(mpY[k], expected[2], 1e-12)
+        TS_ASSERT_DELTA(mmY[k], expected[3], 1e-12)
+        TS_ASSERT_DELTA(ppE[k], expectedError[0], 1e-12)
+        TS_ASSERT_DELTA(pmE[k], expectedError[1], 1e-12)
+        TS_ASSERT_DELTA(mpE[k], expectedError[2], 1e-12)
+        TS_ASSERT_DELTA(mmE[k], expectedError[3], 1e-12)
+      }
+    }
+  }
+  Eigen::Matrix4d invertedF1(const double f1) {
+    Eigen::Matrix4d m;
+    m << f1, 0., 0., 0., 0., f1, 0., 0., f1 - 1., 0., 1., 0., 0., f1 - 1., 0.,
+        1.;
+    m *= 1. / f1;
+    return m;
+  }
+
+  Eigen::Matrix4d invertedF1Derivative(const double f1) {
+    Eigen::Matrix4d m;
+    m << 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., -1., 0., 0., 1., 0., -1.;
+    m *= 1. / (f1 * f1);
+    return m;
+  }
+
+  Eigen::Matrix4d invertedF2(const double f2) {
+    Eigen::Matrix4d m;
+    m << f2, 0., 0., 0., f2 - 1., 1., 0., 0., 0., 0., f2, 0., 0., 0., f2 - 1.,
+        1.;
+    m *= 1. / f2;
+    return m;
+  }
+
+  Eigen::Matrix4d invertedF2Derivative(const double f2) {
+    Eigen::Matrix4d m;
+    m << 0., 0., 0., 0., 1., -1., 0., 0., 0., 0., 0., 0., 0., 0., 1., -1.;
+    m *= 1. / (f2 * f2);
+    return m;
+  }
+
+  Eigen::Matrix4d invertedP1(const double p1) {
+    Eigen::Matrix4d m;
+    m << p1 - 1., 0., p1, 0., 0., p1 - 1., 0., p1, p1, 0., p1 - 1., 0., 0., p1,
+        0., p1 - 1.;
+    m *= 1. / (2. * p1 - 1.);
+    return m;
+  }
+
+  Eigen::Matrix4d invertedP1Derivative(const double p1) {
+    Eigen::Matrix4d m;
+    m << 1., 0., -1., 0., 0., 1., 0., -1., -1., 0., 1., 0., 0., -1., 0., 1.;
+    m *= 1. / (2. * p1 - 1.) / (2. * p1 - 1.);
+    return m;
+  }
+
+  Eigen::Matrix4d invertedP2(const double p2) {
+    Eigen::Matrix4d m;
+    m << p2 - 1., p2, 0., 0., p2, p2 - 1., 0., 0., 0., 0., p2 - 1., p2, 0., 0.,
+        p2, p2 - 1.;
+    m *= 1. / (2. * p2 - 1.);
+    return m;
+  }
+
+  Eigen::Matrix4d invertedP2Derivative(const double p2) {
+    Eigen::Matrix4d m;
+    m << 1., -1., 0., 0., -1., 1., 0., 0., 0., 0., 1., -1., 0., 0., -1., 1.;
+    m *= 1. / (2. * p2 - 1.) / (2. * p2 - 1.);
+    return m;
+  }
+
+  Eigen::Vector4d correction(const Eigen::Vector4d &y, const double f1,
+                             const double f2, const double p1,
+                             const double p2) {
+    const Eigen::Matrix4d F1 = invertedF1(f1);
+    const Eigen::Matrix4d F2 = invertedF2(f2);
+    const Eigen::Matrix4d P1 = invertedP1(p1);
+    const Eigen::Matrix4d P2 = invertedP2(p2);
+    const Eigen::Matrix4d inverted = (P2 * P1 * F2 * F1).matrix();
+    return (inverted * y).matrix();
+  }
+
+  Eigen::Vector4d error(const Eigen::Vector4d &y, const Eigen::Vector4d &e,
+                        const double f1, const double f1e, const double f2,
+                        const double f2e, const double p1, const double p1e,
+                        const double p2, const double p2e) {
+    const Eigen::Matrix4d F1 = invertedF1(f1);
+    const Eigen::Matrix4d dF1 = f1e * invertedF1Derivative(f1);
+    const Eigen::Matrix4d F2 = invertedF2(f2);
+    const Eigen::Matrix4d dF2 = f2e * invertedF2Derivative(f2);
+    const Eigen::Matrix4d P1 = invertedP1(p1);
+    const Eigen::Matrix4d dP1 = p1e * invertedP1Derivative(p1);
+    const Eigen::Matrix4d P2 = invertedP2(p2);
+    const Eigen::Matrix4d dP2 = p2e * invertedP2Derivative(p2);
+    const auto p2Error = (dP2 * P1 * F2 * F1 * y).array();
+    const auto p1Error = (P2 * dP1 * F2 * F1 * y).array();
+    const auto f2Error = (P2 * P1 * dF2 * F1 * y).array();
+    const auto f1Error = (P2 * P1 * F2 * dF1 * y).array();
+    const auto inverted = (P2 * P1 * F2 * F1).array();
+    const auto yError = ((inverted * inverted).matrix() *
+                         (e.array() * e.array()).matrix()).array();
+    return (p2Error * p2Error + p1Error * p1Error + f2Error * f2Error +
+            f1Error * f1Error + yError)
+        .sqrt()
+        .matrix();
+  }
+
+  Eigen::Vector2d correctionWithoutAnalyzer(const Eigen::Vector2d &y,
+                                            const double f1, const double p1) {
+    Eigen::Matrix2d F1;
+    F1 << f1, 0., f1 - 1., 1.;
+    F1 *= 1. / f1;
+    Eigen::Matrix2d P1;
+    P1 << p1 - 1., p1, p1, p1 - 1.;
+    P1 *= 1. / (2. * p1 - 1.);
+    const Eigen::Matrix2d inverted = (P1 * F1).matrix();
+    return static_cast<Eigen::Vector2d>(inverted * y);
+  }
+
+  Eigen::Vector2d errorWithoutAnalyzer(const Eigen::Vector2d &y,
+                                       const Eigen::Vector2d &e,
+                                       const double f1, const double f1e,
+                                       const double p1, const double p1e) {
+    Eigen::Matrix2d F1;
+    F1 << f1, 0, f1 - 1., 1.;
+    F1 *= 1. / f1;
+    Eigen::Matrix2d dF1;
+    dF1 << 0., 0., 1., -1.;
+    dF1 *= f1e / (f1 * f1);
+    Eigen::Matrix2d P1;
+    P1 << p1 - 1., p1, p1, p1 - 1.;
+    P1 *= 1. / (2. * p1 - 1.);
+    Eigen::Matrix2d dP1;
+    dP1 << 1., -1., -1., 1.;
+    dP1 *= p1e / ((2. * p1 - 1.) * (2. * p1 - 1.));
+    const auto p1Error = (dP1 * F1 * y).array();
+    const auto f1Error = (P1 * dF1 * y).array();
+    const auto inverted = (P1 * F1).array();
+    const auto yError = ((inverted * inverted).matrix() *
+                         (e.array() * e.array()).matrix()).array();
+    return (p1Error * p1Error + f1Error * f1Error + yError).sqrt().matrix();
+  }
+
+  void solveMissingIntensity(const Mantid::API::MatrixWorkspace_sptr &ppWS,
+                             Mantid::API::MatrixWorkspace_sptr &pmWS,
+                             Mantid::API::MatrixWorkspace_sptr &mpWS,
+                             const Mantid::API::MatrixWorkspace_sptr &mmWS,
+                             const Mantid::API::MatrixWorkspace_sptr &effWS) {
+    const auto &F1 = effWS->y(0);
+    const auto &F2 = effWS->y(1);
+    const auto &P1 = effWS->y(2);
+    const auto &P2 = effWS->y(3);
+    if (!pmWS) {
+      pmWS = mpWS->clone();
+      for (size_t wsIndex = 0; wsIndex != pmWS->getNumberHistograms();
+           ++wsIndex) {
+        const auto &ppY = ppWS->y(wsIndex);
+        auto &pmY = pmWS->mutableY(wsIndex);
+        auto &pmE = pmWS->mutableE(wsIndex);
+        const auto &mpY = mpWS->y(wsIndex);
+        const auto &mmY = mmWS->y(wsIndex);
+        for (size_t binIndex = 0; binIndex != mpY.size(); ++binIndex) {
+          pmY[binIndex] =
+              -(2 * ppY[binIndex] * F2[binIndex] * P2[binIndex] -
+                P2[binIndex] * mmY[binIndex] -
+                2 * mpY[binIndex] * F2[binIndex] * P2[binIndex] +
+                mpY[binIndex] * P2[binIndex] - ppY[binIndex] * P2[binIndex] +
+                P1[binIndex] * mmY[binIndex] -
+                2 * ppY[binIndex] * F1[binIndex] * P1[binIndex] +
+                ppY[binIndex] * P1[binIndex] - P1[binIndex] * mpY[binIndex] +
+                ppY[binIndex] * F1[binIndex] + mpY[binIndex] * F2[binIndex] -
+                ppY[binIndex] * F2[binIndex]) /
+              (P2[binIndex] - P1[binIndex] + 2 * F1[binIndex] * P1[binIndex] -
+               F1[binIndex]);
+          // Error propagation is not implemented in the algorithm.
+          pmE[binIndex] = 0.;
+        }
+      }
+    } else {
+      mpWS = pmWS->clone();
+      for (size_t wsIndex = 0; wsIndex != mpWS->getNumberHistograms();
+           ++wsIndex) {
+        const auto &ppY = ppWS->y(wsIndex);
+        const auto &pmY = pmWS->y(wsIndex);
+        auto &mpY = mpWS->mutableY(wsIndex);
+        auto &mpE = mpWS->mutableE(wsIndex);
+        const auto &mmY = mmWS->y(wsIndex);
+        for (size_t binIndex = 0; binIndex != mpY.size(); ++binIndex) {
+          mpY[binIndex] =
+              (-ppY[binIndex] * P2[binIndex] + P2[binIndex] * pmY[binIndex] -
+               P2[binIndex] * mmY[binIndex] +
+               2 * ppY[binIndex] * F2[binIndex] * P2[binIndex] -
+               pmY[binIndex] * P1[binIndex] + P1[binIndex] * mmY[binIndex] +
+               ppY[binIndex] * P1[binIndex] -
+               2 * ppY[binIndex] * F1[binIndex] * P1[binIndex] +
+               2 * pmY[binIndex] * F1[binIndex] * P1[binIndex] +
+               ppY[binIndex] * F1[binIndex] - ppY[binIndex] * F2[binIndex] -
+               pmY[binIndex] * F1[binIndex]) /
+              (-P2[binIndex] + 2 * F2[binIndex] * P2[binIndex] + P1[binIndex] -
+               F2[binIndex]);
+          // Error propagation is not implemented in the algorithm.
+          mpE[binIndex] = 0.;
+        }
+      }
+    }
+  }
+
+  void solveMissingIntensities(const Mantid::API::MatrixWorkspace_sptr &ppWS,
+                               Mantid::API::MatrixWorkspace_sptr &pmWS,
+                               Mantid::API::MatrixWorkspace_sptr &mpWS,
+                               const Mantid::API::MatrixWorkspace_sptr &mmWS,
+                               const Mantid::API::MatrixWorkspace_sptr &effWS) {
+    const auto &F1 = effWS->y(0);
+    const auto &F1E = effWS->e(0);
+    const auto &F2 = effWS->y(1);
+    const auto &F2E = effWS->e(1);
+    const auto &P1 = effWS->y(2);
+    const auto &P1E = effWS->e(2);
+    const auto &P2 = effWS->y(3);
+    const auto &P2E = effWS->e(3);
+    pmWS = ppWS->clone();
+    mpWS = ppWS->clone();
+    for (size_t wsIndex = 0; wsIndex != ppWS->getNumberHistograms();
+         ++wsIndex) {
+      const auto &ppY = ppWS->y(wsIndex);
+      const auto &ppE = ppWS->e(wsIndex);
+      auto &pmY = pmWS->mutableY(wsIndex);
+      auto &pmE = pmWS->mutableE(wsIndex);
+      auto &mpY = mpWS->mutableY(wsIndex);
+      auto &mpE = mpWS->mutableE(wsIndex);
+      const auto &mmY = mmWS->y(wsIndex);
+      const auto &mmE = mmWS->e(wsIndex);
+      for (size_t binIndex = 0; binIndex != mpY.size(); ++binIndex) {
+        const double P12 = P1[binIndex] * P1[binIndex];
+        const double P13 = P1[binIndex] * P12;
+        const double P14 = P1[binIndex] * P13;
+        const double P22 = P2[binIndex] * P2[binIndex];
+        const double P23 = P2[binIndex] * P22;
+        const double F12 = F1[binIndex] * F1[binIndex];
+        {
+          mpY[binIndex] =
+              -(-mmY[binIndex] * P22 * F1[binIndex] +
+                2 * F1[binIndex] * P1[binIndex] * mmY[binIndex] * P22 -
+                2 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P2[binIndex] -
+                8 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P12 *
+                    P2[binIndex] +
+                2 * ppY[binIndex] * F2[binIndex] * P12 * P2[binIndex] +
+                8 * ppY[binIndex] * F12 * F2[binIndex] * P12 * P2[binIndex] +
+                2 * ppY[binIndex] * F12 * F2[binIndex] * P2[binIndex] -
+                8 * ppY[binIndex] * F12 * F2[binIndex] * P2[binIndex] *
+                    P1[binIndex] -
+                2 * F1[binIndex] * P1[binIndex] * mmY[binIndex] * P2[binIndex] -
+                2 * ppY[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] +
+                8 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] *
+                    P2[binIndex] +
+                mmY[binIndex] * P2[binIndex] * F1[binIndex] +
+                ppY[binIndex] * F1[binIndex] * F2[binIndex] -
+                ppY[binIndex] * F2[binIndex] * P12 +
+                4 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P12 +
+                4 * ppY[binIndex] * F12 * F2[binIndex] * P1[binIndex] -
+                4 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] +
+                ppY[binIndex] * F2[binIndex] * P1[binIndex] -
+                4 * ppY[binIndex] * F12 * F2[binIndex] * P12 -
+                ppY[binIndex] * F12 * F2[binIndex]) /
+              (-F1[binIndex] * F2[binIndex] +
+               2 * F2[binIndex] * P1[binIndex] * P2[binIndex] +
+               3 * F1[binIndex] * F2[binIndex] * P1[binIndex] -
+               2 * F1[binIndex] * F2[binIndex] * P22 -
+               2 * P22 * F1[binIndex] * P1[binIndex] +
+               2 * P2[binIndex] * F1[binIndex] * P1[binIndex] +
+               3 * F1[binIndex] * F2[binIndex] * P2[binIndex] -
+               P2[binIndex] * F1[binIndex] + P22 * F1[binIndex] +
+               F2[binIndex] * P12 - 2 * F2[binIndex] * P12 * P2[binIndex] -
+               2 * F1[binIndex] * F2[binIndex] * P12 -
+               F2[binIndex] * P1[binIndex] -
+               8 * F1[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] +
+               4 * F1[binIndex] * F2[binIndex] * P1[binIndex] * P22 +
+               4 * F1[binIndex] * F2[binIndex] * P12 * P2[binIndex]);
+          const double dI00 =
+              -F2[binIndex] *
+              (-2 * P2[binIndex] * F1[binIndex] + 2 * P12 * P2[binIndex] +
+               8 * P2[binIndex] * F1[binIndex] * P1[binIndex] -
+               2 * P1[binIndex] * P2[binIndex] + 2 * P2[binIndex] * F12 -
+               8 * P2[binIndex] * F12 * P1[binIndex] -
+               8 * P2[binIndex] * F1[binIndex] * P12 +
+               8 * P2[binIndex] * F12 * P12 - 4 * F1[binIndex] * P1[binIndex] -
+               F12 + 4 * F12 * P1[binIndex] + P1[binIndex] + F1[binIndex] -
+               P12 + 4 * F1[binIndex] * P12 - 4 * F12 * P12) /
+              (-P2[binIndex] * F1[binIndex] +
+               3 * F1[binIndex] * F2[binIndex] * P2[binIndex] -
+               2 * P22 * F1[binIndex] * P1[binIndex] -
+               2 * F1[binIndex] * F2[binIndex] * P22 -
+               2 * F2[binIndex] * P12 * P2[binIndex] -
+               2 * F1[binIndex] * F2[binIndex] * P12 +
+               2 * P2[binIndex] * F1[binIndex] * P1[binIndex] +
+               P22 * F1[binIndex] + F2[binIndex] * P12 +
+               3 * F1[binIndex] * F2[binIndex] * P1[binIndex] +
+               2 * F2[binIndex] * P1[binIndex] * P2[binIndex] -
+               F1[binIndex] * F2[binIndex] - F2[binIndex] * P1[binIndex] -
+               8 * F1[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] +
+               4 * F1[binIndex] * F2[binIndex] * P1[binIndex] * P22 +
+               4 * F1[binIndex] * F2[binIndex] * P12 * P2[binIndex]);
+          const double dI11 =
+              -P2[binIndex] * F1[binIndex] *
+              (1 - 2 * P1[binIndex] - P2[binIndex] +
+               2 * P1[binIndex] * P2[binIndex]) /
+              (-P2[binIndex] * F1[binIndex] +
+               3 * F1[binIndex] * F2[binIndex] * P2[binIndex] -
+               2 * P22 * F1[binIndex] * P1[binIndex] -
+               2 * F1[binIndex] * F2[binIndex] * P22 -
+               2 * F2[binIndex] * P12 * P2[binIndex] -
+               2 * F1[binIndex] * F2[binIndex] * P12 +
+               2 * P2[binIndex] * F1[binIndex] * P1[binIndex] +
+               P22 * F1[binIndex] + F2[binIndex] * P12 +
+               3 * F1[binIndex] * F2[binIndex] * P1[binIndex] +
+               2 * F2[binIndex] * P1[binIndex] * P2[binIndex] -
+               F1[binIndex] * F2[binIndex] - F2[binIndex] * P1[binIndex] -
+               8 * F1[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] +
+               4 * F1[binIndex] * F2[binIndex] * P1[binIndex] * P22 +
+               4 * F1[binIndex] * F2[binIndex] * P12 * P2[binIndex]);
+          const double divisor1 =
+              (-P2[binIndex] * F1[binIndex] +
+               3 * F1[binIndex] * F2[binIndex] * P2[binIndex] -
+               2 * P22 * F1[binIndex] * P1[binIndex] -
+               2 * F1[binIndex] * F2[binIndex] * P22 -
+               2 * F2[binIndex] * P12 * P2[binIndex] -
+               2 * F1[binIndex] * F2[binIndex] * P12 +
+               2 * P2[binIndex] * F1[binIndex] * P1[binIndex] +
+               P22 * F1[binIndex] + F2[binIndex] * P12 +
+               3 * F1[binIndex] * F2[binIndex] * P1[binIndex] +
+               2 * F2[binIndex] * P1[binIndex] * P2[binIndex] -
+               F1[binIndex] * F2[binIndex] - F2[binIndex] * P1[binIndex] -
+               8 * F1[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] +
+               4 * F1[binIndex] * F2[binIndex] * P1[binIndex] * P22 +
+               4 * F1[binIndex] * F2[binIndex] * P12 * P2[binIndex]);
+          const double dF1 =
+              -F2[binIndex] *
+              (-P1[binIndex] * mmY[binIndex] * P2[binIndex] +
+               4 * ppY[binIndex] * F2[binIndex] * P1[binIndex] * P22 -
+               ppY[binIndex] * F2[binIndex] * P12 * P2[binIndex] -
+               10 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P12 -
+               8 * ppY[binIndex] * F2[binIndex] * P12 * P22 +
+               2 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] -
+               ppY[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] -
+               32 * ppY[binIndex] * F12 * F2[binIndex] * P14 * P2[binIndex] +
+               32 * ppY[binIndex] * F2[binIndex] * P14 * P2[binIndex] *
+                   F1[binIndex] -
+               32 * ppY[binIndex] * F2[binIndex] * P14 * P22 * F1[binIndex] +
+               32 * ppY[binIndex] * F12 * F2[binIndex] * P14 * P22 +
+               32 * ppY[binIndex] * F12 * F2[binIndex] * P13 * P23 +
+               2 * ppY[binIndex] * F2[binIndex] * P14 +
+               4 * ppY[binIndex] * P13 * P23 - 4 * P13 * mmY[binIndex] * P23 -
+               8 * ppY[binIndex] * F2[binIndex] * P13 * P23 -
+               16 * ppY[binIndex] * P23 * F12 * P13 +
+               8 * ppY[binIndex] * F12 * F2[binIndex] * P14 -
+               8 * ppY[binIndex] * F2[binIndex] * P14 * P2[binIndex] +
+               8 * ppY[binIndex] * F2[binIndex] * P14 * P22 -
+               8 * ppY[binIndex] * F2[binIndex] * P14 * F1[binIndex] +
+               10 * ppY[binIndex] * F2[binIndex] * P13 * P2[binIndex] -
+               4 * ppY[binIndex] * F2[binIndex] * P13 * P22 +
+               16 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P13 -
+               4 * ppY[binIndex] * F2[binIndex] * P1[binIndex] * P23 +
+               12 * ppY[binIndex] * F2[binIndex] * P12 * P23 +
+               18 * ppY[binIndex] * P22 * F12 * P1[binIndex] -
+               20 * ppY[binIndex] * F12 * F2[binIndex] * P13 -
+               36 * ppY[binIndex] * P22 * F12 * P12 +
+               24 * ppY[binIndex] * P22 * F12 * P13 -
+               6 * ppY[binIndex] * P2[binIndex] * F12 * P1[binIndex] -
+               5 * ppY[binIndex] * F12 * F2[binIndex] * P2[binIndex] +
+               8 * ppY[binIndex] * F12 * F2[binIndex] * P22 -
+               8 * ppY[binIndex] * P2[binIndex] * F12 * P13 +
+               12 * ppY[binIndex] * P2[binIndex] * F12 * P12 +
+               18 * ppY[binIndex] * F12 * F2[binIndex] * P12 -
+               7 * ppY[binIndex] * F12 * F2[binIndex] * P1[binIndex] -
+               12 * ppY[binIndex] * P23 * F12 * P1[binIndex] +
+               24 * ppY[binIndex] * P23 * F12 * P12 -
+               4 * ppY[binIndex] * F12 * F2[binIndex] * P23 -
+               3 * ppY[binIndex] * P1[binIndex] * P22 +
+               ppY[binIndex] * F2[binIndex] * P12 -
+               3 * ppY[binIndex] * P12 * P2[binIndex] +
+               3 * P12 * mmY[binIndex] * P2[binIndex] -
+               9 * P12 * mmY[binIndex] * P22 + 9 * ppY[binIndex] * P12 * P22 +
+               ppY[binIndex] * P1[binIndex] * P2[binIndex] +
+               3 * P1[binIndex] * mmY[binIndex] * P22 -
+               8 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] *
+                   P2[binIndex] +
+               8 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] *
+                   P22 +
+               40 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P12 *
+                   P2[binIndex] -
+               40 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P12 * P22 -
+               64 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P13 *
+                   P2[binIndex] +
+               64 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P13 * P22 +
+               34 * ppY[binIndex] * F12 * F2[binIndex] * P2[binIndex] *
+                   P1[binIndex] -
+               52 * ppY[binIndex] * F12 * F2[binIndex] * P22 * P1[binIndex] -
+               84 * ppY[binIndex] * F12 * F2[binIndex] * P12 * P2[binIndex] +
+               120 * ppY[binIndex] * F12 * F2[binIndex] * P12 * P22 +
+               88 * ppY[binIndex] * F12 * F2[binIndex] * P13 * P2[binIndex] -
+               112 * ppY[binIndex] * F12 * F2[binIndex] * P13 * P22 +
+               24 * ppY[binIndex] * F12 * F2[binIndex] * P23 * P1[binIndex] -
+               48 * ppY[binIndex] * F12 * F2[binIndex] * P12 * P23 +
+               2 * ppY[binIndex] * P13 * P2[binIndex] -
+               6 * ppY[binIndex] * P13 * P22 -
+               3 * ppY[binIndex] * F2[binIndex] * P13 +
+               2 * ppY[binIndex] * P1[binIndex] * P23 -
+               6 * ppY[binIndex] * P12 * P23 +
+               ppY[binIndex] * P2[binIndex] * F12 -
+               3 * ppY[binIndex] * P22 * F12 +
+               ppY[binIndex] * F12 * F2[binIndex] +
+               2 * ppY[binIndex] * P23 * F12 -
+               2 * P13 * mmY[binIndex] * P2[binIndex] +
+               6 * P13 * mmY[binIndex] * P22 + 6 * P12 * mmY[binIndex] * P23 -
+               2 * P1[binIndex] * mmY[binIndex] * P23) /
+              (divisor1 * divisor1);
+          const double divisor2 =
+              (-P2[binIndex] * F1[binIndex] +
+               3 * F1[binIndex] * F2[binIndex] * P2[binIndex] -
+               2 * P22 * F1[binIndex] * P1[binIndex] -
+               2 * F1[binIndex] * F2[binIndex] * P22 -
+               2 * F2[binIndex] * P12 * P2[binIndex] -
+               2 * F1[binIndex] * F2[binIndex] * P12 +
+               2 * P2[binIndex] * F1[binIndex] * P1[binIndex] +
+               P22 * F1[binIndex] + F2[binIndex] * P12 +
+               3 * F1[binIndex] * F2[binIndex] * P1[binIndex] +
+               2 * F2[binIndex] * P1[binIndex] * P2[binIndex] -
+               F1[binIndex] * F2[binIndex] - F2[binIndex] * P1[binIndex] -
+               8 * F1[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] +
+               4 * F1[binIndex] * F2[binIndex] * P1[binIndex] * P22 +
+               4 * F1[binIndex] * F2[binIndex] * P12 * P2[binIndex]);
+          const double dF2 =
+              P2[binIndex] * F1[binIndex] *
+              (3 * P1[binIndex] * mmY[binIndex] * P2[binIndex] -
+               12 * ppY[binIndex] * P22 * F1[binIndex] * P1[binIndex] -
+               36 * ppY[binIndex] * P2[binIndex] * F1[binIndex] * P12 +
+               24 * ppY[binIndex] * P22 * F1[binIndex] * P12 +
+               18 * ppY[binIndex] * P2[binIndex] * F1[binIndex] * P1[binIndex] +
+               12 * ppY[binIndex] * F1[binIndex] * P12 +
+               24 * ppY[binIndex] * P2[binIndex] * F1[binIndex] * P13 -
+               16 * ppY[binIndex] * P22 * F1[binIndex] * P13 +
+               12 * ppY[binIndex] * P22 * F12 * P1[binIndex] -
+               24 * ppY[binIndex] * P22 * F12 * P12 +
+               16 * ppY[binIndex] * P22 * F12 * P13 -
+               18 * ppY[binIndex] * P2[binIndex] * F12 * P1[binIndex] -
+               24 * ppY[binIndex] * P2[binIndex] * F12 * P13 +
+               36 * ppY[binIndex] * P2[binIndex] * F12 * P12 -
+               19 * F1[binIndex] * P1[binIndex] * mmY[binIndex] * P2[binIndex] +
+               28 * F1[binIndex] * P12 * mmY[binIndex] * P2[binIndex] -
+               12 * F1[binIndex] * P13 * mmY[binIndex] * P2[binIndex] +
+               22 * F1[binIndex] * P1[binIndex] * mmY[binIndex] * P22 -
+               28 * F1[binIndex] * P12 * mmY[binIndex] * P22 +
+               8 * F1[binIndex] * P13 * mmY[binIndex] * P22 -
+               8 * F1[binIndex] * P1[binIndex] * mmY[binIndex] * P23 +
+               8 * F1[binIndex] * P12 * mmY[binIndex] * P23 -
+               ppY[binIndex] * F12 + 2 * ppY[binIndex] * P13 -
+               2 * P13 * mmY[binIndex] - mmY[binIndex] * F1[binIndex] +
+               2 * ppY[binIndex] * P1[binIndex] * P22 +
+               9 * ppY[binIndex] * P12 * P2[binIndex] -
+               9 * P12 * mmY[binIndex] * P2[binIndex] +
+               6 * P12 * mmY[binIndex] * P22 - 6 * ppY[binIndex] * P12 * P22 -
+               3 * ppY[binIndex] * P1[binIndex] * P2[binIndex] -
+               2 * P1[binIndex] * mmY[binIndex] * P22 -
+               6 * ppY[binIndex] * F1[binIndex] * P1[binIndex] +
+               2 * ppY[binIndex] * P22 * F1[binIndex] -
+               3 * ppY[binIndex] * P2[binIndex] * F1[binIndex] -
+               P1[binIndex] * mmY[binIndex] + ppY[binIndex] * P1[binIndex] -
+               3 * ppY[binIndex] * P12 + ppY[binIndex] * F1[binIndex] +
+               3 * P12 * mmY[binIndex] -
+               6 * ppY[binIndex] * P13 * P2[binIndex] +
+               4 * ppY[binIndex] * P13 * P22 +
+               3 * ppY[binIndex] * P2[binIndex] * F12 -
+               2 * ppY[binIndex] * P22 * F12 +
+               5 * F1[binIndex] * P1[binIndex] * mmY[binIndex] +
+               6 * ppY[binIndex] * F12 * P1[binIndex] -
+               8 * F1[binIndex] * P12 * mmY[binIndex] -
+               12 * F12 * P12 * ppY[binIndex] -
+               8 * ppY[binIndex] * F1[binIndex] * P13 +
+               6 * P13 * mmY[binIndex] * P2[binIndex] +
+               4 * F1[binIndex] * P13 * mmY[binIndex] +
+               8 * F12 * P13 * ppY[binIndex] - 4 * P13 * mmY[binIndex] * P22 -
+               5 * mmY[binIndex] * P22 * F1[binIndex] +
+               2 * mmY[binIndex] * P23 * F1[binIndex] +
+               4 * mmY[binIndex] * P2[binIndex] * F1[binIndex]) /
+              (divisor2 * divisor2);
+          const double divisor3 =
+              (-P2[binIndex] * F1[binIndex] +
+               3 * F1[binIndex] * F2[binIndex] * P2[binIndex] -
+               2 * P22 * F1[binIndex] * P1[binIndex] -
+               2 * F1[binIndex] * F2[binIndex] * P22 -
+               2 * F2[binIndex] * P12 * P2[binIndex] -
+               2 * F1[binIndex] * F2[binIndex] * P12 +
+               2 * P2[binIndex] * F1[binIndex] * P1[binIndex] +
+               P22 * F1[binIndex] + F2[binIndex] * P12 +
+               3 * F1[binIndex] * F2[binIndex] * P1[binIndex] +
+               2 * F2[binIndex] * P1[binIndex] * P2[binIndex] -
+               F1[binIndex] * F2[binIndex] - F2[binIndex] * P1[binIndex] -
+               8 * F1[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] +
+               4 * F1[binIndex] * F2[binIndex] * P1[binIndex] * P22 +
+               4 * F1[binIndex] * F2[binIndex] * P12 * P2[binIndex]);
+          const double dP1 =
+              -F1[binIndex] * F2[binIndex] *
+              (-2 * P1[binIndex] * mmY[binIndex] * P2[binIndex] -
+               2 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P2[binIndex] +
+               8 * ppY[binIndex] * F2[binIndex] * P1[binIndex] * P22 +
+               24 * ppY[binIndex] * P22 * F1[binIndex] * P1[binIndex] +
+               8 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P22 +
+               8 * ppY[binIndex] * P2[binIndex] * F1[binIndex] * P12 +
+               6 * ppY[binIndex] * F2[binIndex] * P12 * P2[binIndex] +
+               4 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P12 -
+               24 * ppY[binIndex] * P22 * F1[binIndex] * P12 -
+               12 * ppY[binIndex] * F2[binIndex] * P12 * P22 -
+               8 * ppY[binIndex] * P2[binIndex] * F1[binIndex] * P1[binIndex] -
+               2 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] -
+               2 * ppY[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] +
+               ppY[binIndex] * F2[binIndex] * P2[binIndex] -
+               4 * ppY[binIndex] * F2[binIndex] * P22 -
+               8 * ppY[binIndex] * F2[binIndex] * P1[binIndex] * P23 -
+               16 * ppY[binIndex] * P23 * F1[binIndex] * P1[binIndex] -
+               8 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P23 +
+               16 * ppY[binIndex] * P23 * F1[binIndex] * P12 +
+               8 * ppY[binIndex] * F2[binIndex] * P12 * P23 -
+               24 * ppY[binIndex] * P22 * F12 * P1[binIndex] +
+               24 * ppY[binIndex] * P22 * F12 * P12 +
+               8 * ppY[binIndex] * P2[binIndex] * F12 * P1[binIndex] +
+               6 * ppY[binIndex] * F12 * F2[binIndex] * P2[binIndex] -
+               12 * ppY[binIndex] * F12 * F2[binIndex] * P22 -
+               8 * ppY[binIndex] * P2[binIndex] * F12 * P12 -
+               4 * ppY[binIndex] * F12 * F2[binIndex] * P12 +
+               4 * ppY[binIndex] * F12 * F2[binIndex] * P1[binIndex] +
+               16 * ppY[binIndex] * P23 * F12 * P1[binIndex] -
+               16 * ppY[binIndex] * P23 * F12 * P12 +
+               8 * ppY[binIndex] * F12 * F2[binIndex] * P23 +
+               4 * F1[binIndex] * P1[binIndex] * mmY[binIndex] * P2[binIndex] -
+               4 * F1[binIndex] * P12 * mmY[binIndex] * P2[binIndex] -
+               12 * F1[binIndex] * P1[binIndex] * mmY[binIndex] * P22 +
+               12 * F1[binIndex] * P12 * mmY[binIndex] * P22 +
+               8 * F1[binIndex] * P1[binIndex] * mmY[binIndex] * P23 -
+               8 * F1[binIndex] * P12 * mmY[binIndex] * P23 +
+               2 * mmY[binIndex] * P23 - 2 * ppY[binIndex] * P23 +
+               4 * ppY[binIndex] * F2[binIndex] * P23 -
+               6 * ppY[binIndex] * P1[binIndex] * P22 -
+               ppY[binIndex] * F2[binIndex] * P12 -
+               2 * ppY[binIndex] * P12 * P2[binIndex] +
+               2 * P12 * mmY[binIndex] * P2[binIndex] -
+               6 * P12 * mmY[binIndex] * P22 + 6 * ppY[binIndex] * P12 * P22 +
+               2 * ppY[binIndex] * P1[binIndex] * P2[binIndex] -
+               ppY[binIndex] * P2[binIndex] +
+               6 * P1[binIndex] * mmY[binIndex] * P22 -
+               6 * ppY[binIndex] * P22 * F1[binIndex] +
+               2 * ppY[binIndex] * P2[binIndex] * F1[binIndex] +
+               3 * ppY[binIndex] * P22 +
+               16 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] *
+                   P2[binIndex] -
+               40 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] *
+                   P22 -
+               24 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P12 *
+                   P2[binIndex] +
+               48 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P12 * P22 +
+               mmY[binIndex] * P2[binIndex] - 3 * mmY[binIndex] * P22 +
+               32 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] *
+                   P23 -
+               32 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P12 * P23 -
+               24 * ppY[binIndex] * F12 * F2[binIndex] * P2[binIndex] *
+                   P1[binIndex] +
+               48 * ppY[binIndex] * F12 * F2[binIndex] * P22 * P1[binIndex] +
+               24 * ppY[binIndex] * F12 * F2[binIndex] * P12 * P2[binIndex] -
+               48 * ppY[binIndex] * F12 * F2[binIndex] * P12 * P22 -
+               32 * ppY[binIndex] * F12 * F2[binIndex] * P23 * P1[binIndex] +
+               32 * ppY[binIndex] * F12 * F2[binIndex] * P12 * P23 +
+               4 * ppY[binIndex] * P1[binIndex] * P23 +
+               4 * ppY[binIndex] * P23 * F1[binIndex] -
+               4 * ppY[binIndex] * P12 * P23 -
+               2 * ppY[binIndex] * P2[binIndex] * F12 +
+               6 * ppY[binIndex] * P22 * F12 -
+               ppY[binIndex] * F12 * F2[binIndex] -
+               4 * ppY[binIndex] * P23 * F12 + 4 * P12 * mmY[binIndex] * P23 -
+               4 * P1[binIndex] * mmY[binIndex] * P23 +
+               3 * mmY[binIndex] * P22 * F1[binIndex] -
+               2 * mmY[binIndex] * P23 * F1[binIndex] -
+               mmY[binIndex] * P2[binIndex] * F1[binIndex]) /
+              (divisor3 * divisor3);
+          const double divisor4 =
+              (-P2[binIndex] * F1[binIndex] +
+               3 * F1[binIndex] * F2[binIndex] * P2[binIndex] -
+               2 * P22 * F1[binIndex] * P1[binIndex] -
+               2 * F1[binIndex] * F2[binIndex] * P22 -
+               2 * F2[binIndex] * P12 * P2[binIndex] -
+               2 * F1[binIndex] * F2[binIndex] * P12 +
+               2 * P2[binIndex] * F1[binIndex] * P1[binIndex] +
+               P22 * F1[binIndex] + F2[binIndex] * P12 +
+               3 * F1[binIndex] * F2[binIndex] * P1[binIndex] +
+               2 * F2[binIndex] * P1[binIndex] * P2[binIndex] -
+               F1[binIndex] * F2[binIndex] - F2[binIndex] * P1[binIndex] -
+               8 * F1[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] +
+               4 * F1[binIndex] * F2[binIndex] * P1[binIndex] * P22 +
+               4 * F1[binIndex] * F2[binIndex] * P12 * P2[binIndex]);
+          const double dP2 =
+              F1[binIndex] * F2[binIndex] *
+              (-2 * P1[binIndex] * mmY[binIndex] * P2[binIndex] -
+               4 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P2[binIndex] +
+               4 * ppY[binIndex] * F2[binIndex] * P1[binIndex] * P22 +
+               12 * ppY[binIndex] * P22 * F1[binIndex] * P1[binIndex] +
+               4 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P22 +
+               24 * ppY[binIndex] * P2[binIndex] * F1[binIndex] * P12 +
+               12 * ppY[binIndex] * F2[binIndex] * P12 * P2[binIndex] +
+               12 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P12 -
+               24 * ppY[binIndex] * P22 * F1[binIndex] * P12 -
+               12 * ppY[binIndex] * F2[binIndex] * P12 * P22 -
+               12 * ppY[binIndex] * P2[binIndex] * F1[binIndex] * P1[binIndex] -
+               6 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] -
+               4 * ppY[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] -
+               12 * ppY[binIndex] * F1[binIndex] * P12 -
+               16 * ppY[binIndex] * P2[binIndex] * F1[binIndex] * P13 +
+               16 * ppY[binIndex] * P22 * F1[binIndex] * P13 -
+               8 * ppY[binIndex] * F2[binIndex] * P13 * P2[binIndex] +
+               8 * ppY[binIndex] * F2[binIndex] * P13 * P22 -
+               8 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P13 -
+               12 * ppY[binIndex] * P22 * F12 * P1[binIndex] +
+               8 * ppY[binIndex] * F12 * F2[binIndex] * P13 +
+               24 * ppY[binIndex] * P22 * F12 * P12 -
+               16 * ppY[binIndex] * P22 * F12 * P13 +
+               12 * ppY[binIndex] * P2[binIndex] * F12 * P1[binIndex] +
+               4 * ppY[binIndex] * F12 * F2[binIndex] * P2[binIndex] -
+               4 * ppY[binIndex] * F12 * F2[binIndex] * P22 +
+               16 * ppY[binIndex] * P2[binIndex] * F12 * P13 -
+               24 * ppY[binIndex] * P2[binIndex] * F12 * P12 -
+               12 * ppY[binIndex] * F12 * F2[binIndex] * P12 +
+               6 * ppY[binIndex] * F12 * F2[binIndex] * P1[binIndex] +
+               10 * F1[binIndex] * P1[binIndex] * mmY[binIndex] * P2[binIndex] -
+               16 * F1[binIndex] * P12 * mmY[binIndex] * P2[binIndex] +
+               8 * F1[binIndex] * P13 * mmY[binIndex] * P2[binIndex] -
+               6 * F1[binIndex] * P1[binIndex] * mmY[binIndex] * P22 +
+               12 * F1[binIndex] * P12 * mmY[binIndex] * P22 -
+               8 * F1[binIndex] * P13 * mmY[binIndex] * P22 +
+               ppY[binIndex] * F12 - 2 * ppY[binIndex] * P13 +
+               2 * P13 * mmY[binIndex] + mmY[binIndex] * F1[binIndex] -
+               2 * ppY[binIndex] * P1[binIndex] * P22 +
+               ppY[binIndex] * F2[binIndex] * P1[binIndex] -
+               3 * ppY[binIndex] * F2[binIndex] * P12 -
+               6 * ppY[binIndex] * P12 * P2[binIndex] +
+               6 * P12 * mmY[binIndex] * P2[binIndex] -
+               6 * P12 * mmY[binIndex] * P22 + 6 * ppY[binIndex] * P12 * P22 +
+               2 * ppY[binIndex] * P1[binIndex] * P2[binIndex] +
+               ppY[binIndex] * F1[binIndex] * F2[binIndex] +
+               2 * P1[binIndex] * mmY[binIndex] * P22 +
+               6 * ppY[binIndex] * F1[binIndex] * P1[binIndex] -
+               2 * ppY[binIndex] * P22 * F1[binIndex] +
+               2 * ppY[binIndex] * P2[binIndex] * F1[binIndex] +
+               24 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] *
+                   P2[binIndex] -
+               24 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] *
+                   P22 -
+               48 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P12 *
+                   P2[binIndex] +
+               48 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P12 * P22 +
+               P1[binIndex] * mmY[binIndex] - ppY[binIndex] * P1[binIndex] +
+               3 * ppY[binIndex] * P12 - ppY[binIndex] * F1[binIndex] -
+               3 * P12 * mmY[binIndex] +
+               32 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P13 *
+                   P2[binIndex] -
+               32 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P13 * P22 -
+               24 * ppY[binIndex] * F12 * F2[binIndex] * P2[binIndex] *
+                   P1[binIndex] +
+               24 * ppY[binIndex] * F12 * F2[binIndex] * P22 * P1[binIndex] +
+               48 * ppY[binIndex] * F12 * F2[binIndex] * P12 * P2[binIndex] -
+               48 * ppY[binIndex] * F12 * F2[binIndex] * P12 * P22 -
+               32 * ppY[binIndex] * F12 * F2[binIndex] * P13 * P2[binIndex] +
+               32 * ppY[binIndex] * F12 * F2[binIndex] * P13 * P22 +
+               4 * ppY[binIndex] * P13 * P2[binIndex] -
+               4 * ppY[binIndex] * P13 * P22 +
+               2 * ppY[binIndex] * F2[binIndex] * P13 -
+               2 * ppY[binIndex] * P2[binIndex] * F12 +
+               2 * ppY[binIndex] * P22 * F12 -
+               ppY[binIndex] * F12 * F2[binIndex] -
+               5 * F1[binIndex] * P1[binIndex] * mmY[binIndex] -
+               6 * ppY[binIndex] * F12 * P1[binIndex] +
+               8 * F1[binIndex] * P12 * mmY[binIndex] +
+               12 * F12 * P12 * ppY[binIndex] +
+               8 * ppY[binIndex] * F1[binIndex] * P13 -
+               4 * P13 * mmY[binIndex] * P2[binIndex] -
+               4 * F1[binIndex] * P13 * mmY[binIndex] -
+               8 * F12 * P13 * ppY[binIndex] + 4 * P13 * mmY[binIndex] * P22 +
+               mmY[binIndex] * P22 * F1[binIndex] -
+               2 * mmY[binIndex] * P2[binIndex] * F1[binIndex]) /
+              (divisor4 * divisor4);
+          const double e1 = dI00 * ppE[binIndex];
+          const double e2 = dI11 * mmE[binIndex];
+          const double e3 = dF1 * F1E[binIndex];
+          const double e4 = dF2 * F2E[binIndex];
+          const double e5 = dP1 * P1E[binIndex];
+          const double e6 = dP2 * P2E[binIndex];
+          mpE[binIndex] = std::sqrt(e1 * e1 + e2 * e2 + e3 * e3 + e4 * e4 +
+                                    e5 * e5 + e6 * e6);
+        }
+        {
+          pmY[binIndex] =
+              -(ppY[binIndex] * P2[binIndex] * F1[binIndex] -
+                2 * ppY[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] -
+                2 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P2[binIndex] -
+                2 * ppY[binIndex] * P2[binIndex] * F1[binIndex] * P1[binIndex] +
+                2 * P1[binIndex] * mpY[binIndex] * F2[binIndex] * P2[binIndex] +
+                ppY[binIndex] * P1[binIndex] * P2[binIndex] -
+                P1[binIndex] * mpY[binIndex] * P2[binIndex] +
+                4 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] *
+                    P2[binIndex] +
+                P1[binIndex] * mmY[binIndex] * P2[binIndex] -
+                ppY[binIndex] * F1[binIndex] +
+                2 * ppY[binIndex] * F1[binIndex] * P1[binIndex] -
+                P1[binIndex] * mmY[binIndex] -
+                P1[binIndex] * mpY[binIndex] * F2[binIndex] +
+                ppY[binIndex] * F2[binIndex] * P1[binIndex] +
+                ppY[binIndex] * F1[binIndex] * F2[binIndex] -
+                2 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] +
+                P1[binIndex] * mpY[binIndex] - ppY[binIndex] * P1[binIndex]) /
+              ((-P1[binIndex] + 2 * F1[binIndex] * P1[binIndex] -
+                F1[binIndex]) *
+               (-1 + P2[binIndex]));
+          const double dI00 =
+              -(-P1[binIndex] + P1[binIndex] * P2[binIndex] +
+                F2[binIndex] * P1[binIndex] -
+                2 * F2[binIndex] * P1[binIndex] * P2[binIndex] +
+                2 * F1[binIndex] * P1[binIndex] -
+                2 * P2[binIndex] * F1[binIndex] * P1[binIndex] -
+                2 * F1[binIndex] * F2[binIndex] * P1[binIndex] +
+                4 * F1[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] +
+                F1[binIndex] * F2[binIndex] - F1[binIndex] +
+                P2[binIndex] * F1[binIndex] -
+                2 * F1[binIndex] * F2[binIndex] * P2[binIndex]) /
+              ((-P1[binIndex] + 2 * F1[binIndex] * P1[binIndex] -
+                F1[binIndex]) *
+               (-1 + P2[binIndex]));
+          const double dI11 =
+              -(P1[binIndex] * P2[binIndex] - P1[binIndex]) /
+              ((-P1[binIndex] + 2 * F1[binIndex] * P1[binIndex] -
+                F1[binIndex]) *
+               (-1 + P2[binIndex]));
+          const double dI10 =
+              -(P1[binIndex] - P1[binIndex] * P2[binIndex] -
+                F2[binIndex] * P1[binIndex] +
+                2 * F2[binIndex] * P1[binIndex] * P2[binIndex]) /
+              ((-P1[binIndex] + 2 * F1[binIndex] * P1[binIndex] -
+                F1[binIndex]) *
+               (-1 + P2[binIndex]));
+          const double factor1 =
+              (-P1[binIndex] + 2 * F1[binIndex] * P1[binIndex] - F1[binIndex]);
+          const double dF1 =
+              -(ppY[binIndex] * P2[binIndex] -
+                2 * ppY[binIndex] * F2[binIndex] * P2[binIndex] -
+                2 * ppY[binIndex] * P1[binIndex] * P2[binIndex] +
+                4 * ppY[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] -
+                ppY[binIndex] + 2 * ppY[binIndex] * P1[binIndex] +
+                ppY[binIndex] * F2[binIndex] -
+                2 * ppY[binIndex] * F2[binIndex] * P1[binIndex]) /
+                  ((-P1[binIndex] + 2 * F1[binIndex] * P1[binIndex] -
+                    F1[binIndex]) *
+                   (-1 + P2[binIndex])) +
+              (ppY[binIndex] * P2[binIndex] * F1[binIndex] -
+               2 * ppY[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] -
+               2 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P2[binIndex] -
+               2 * ppY[binIndex] * P2[binIndex] * F1[binIndex] * P1[binIndex] +
+               2 * P1[binIndex] * mpY[binIndex] * F2[binIndex] * P2[binIndex] +
+               ppY[binIndex] * P1[binIndex] * P2[binIndex] -
+               P1[binIndex] * mpY[binIndex] * P2[binIndex] +
+               4 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] *
+                   P2[binIndex] +
+               P1[binIndex] * mmY[binIndex] * P2[binIndex] -
+               ppY[binIndex] * F1[binIndex] +
+               2 * ppY[binIndex] * F1[binIndex] * P1[binIndex] -
+               P1[binIndex] * mmY[binIndex] -
+               P1[binIndex] * mpY[binIndex] * F2[binIndex] +
+               ppY[binIndex] * F2[binIndex] * P1[binIndex] +
+               ppY[binIndex] * F1[binIndex] * F2[binIndex] -
+               2 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] +
+               P1[binIndex] * mpY[binIndex] - ppY[binIndex] * P1[binIndex]) *
+                  (-1 + 2 * P1[binIndex]) /
+                  ((factor1 * factor1) * (-1 + P2[binIndex]));
+          const double dF2 =
+              -(-2 * ppY[binIndex] * P1[binIndex] * P2[binIndex] -
+                2 * ppY[binIndex] * P2[binIndex] * F1[binIndex] +
+                2 * P1[binIndex] * mpY[binIndex] * P2[binIndex] +
+                4 * ppY[binIndex] * P2[binIndex] * F1[binIndex] * P1[binIndex] -
+                P1[binIndex] * mpY[binIndex] + ppY[binIndex] * P1[binIndex] +
+                ppY[binIndex] * F1[binIndex] -
+                2 * ppY[binIndex] * F1[binIndex] * P1[binIndex]) /
+              ((-P1[binIndex] + 2 * F1[binIndex] * P1[binIndex] -
+                F1[binIndex]) *
+               (-1 + P2[binIndex]));
+          const double factor2 =
+              (-P1[binIndex] + 2 * F1[binIndex] * P1[binIndex] - F1[binIndex]);
+          const double dP1 =
+              -(-2 * ppY[binIndex] * F2[binIndex] * P2[binIndex] -
+                2 * ppY[binIndex] * P2[binIndex] * F1[binIndex] +
+                2 * mpY[binIndex] * F2[binIndex] * P2[binIndex] +
+                ppY[binIndex] * P2[binIndex] - mpY[binIndex] * P2[binIndex] +
+                4 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P2[binIndex] +
+                mmY[binIndex] * P2[binIndex] +
+                2 * ppY[binIndex] * F1[binIndex] - mmY[binIndex] -
+                mpY[binIndex] * F2[binIndex] + ppY[binIndex] * F2[binIndex] -
+                2 * ppY[binIndex] * F1[binIndex] * F2[binIndex] +
+                mpY[binIndex] - ppY[binIndex]) /
+                  ((-P1[binIndex] + 2 * F1[binIndex] * P1[binIndex] -
+                    F1[binIndex]) *
+                   (-1 + P2[binIndex])) +
+              (ppY[binIndex] * P2[binIndex] * F1[binIndex] -
+               2 * ppY[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] -
+               2 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P2[binIndex] -
+               2 * ppY[binIndex] * P2[binIndex] * F1[binIndex] * P1[binIndex] +
+               2 * P1[binIndex] * mpY[binIndex] * F2[binIndex] * P2[binIndex] +
+               ppY[binIndex] * P1[binIndex] * P2[binIndex] -
+               P1[binIndex] * mpY[binIndex] * P2[binIndex] +
+               4 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] *
+                   P2[binIndex] +
+               P1[binIndex] * mmY[binIndex] * P2[binIndex] -
+               ppY[binIndex] * F1[binIndex] +
+               2 * ppY[binIndex] * F1[binIndex] * P1[binIndex] -
+               P1[binIndex] * mmY[binIndex] -
+               P1[binIndex] * mpY[binIndex] * F2[binIndex] +
+               ppY[binIndex] * F2[binIndex] * P1[binIndex] +
+               ppY[binIndex] * F1[binIndex] * F2[binIndex] -
+               2 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] +
+               P1[binIndex] * mpY[binIndex] - ppY[binIndex] * P1[binIndex]) *
+                  (-1 + 2 * F1[binIndex]) /
+                  ((factor2 * factor2) * (-1 + P2[binIndex]));
+          const double factor3 = (-1 + P2[binIndex]);
+          const double dP2 =
+              -(ppY[binIndex] * F1[binIndex] -
+                2 * ppY[binIndex] * F2[binIndex] * P1[binIndex] -
+                2 * ppY[binIndex] * F1[binIndex] * F2[binIndex] -
+                2 * ppY[binIndex] * F1[binIndex] * P1[binIndex] +
+                2 * P1[binIndex] * mpY[binIndex] * F2[binIndex] +
+                ppY[binIndex] * P1[binIndex] - P1[binIndex] * mpY[binIndex] +
+                4 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] +
+                P1[binIndex] * mmY[binIndex]) /
+                  ((-P1[binIndex] + 2 * F1[binIndex] * P1[binIndex] -
+                    F1[binIndex]) *
+                   (-1 + P2[binIndex])) +
+              (ppY[binIndex] * P2[binIndex] * F1[binIndex] -
+               2 * ppY[binIndex] * F2[binIndex] * P1[binIndex] * P2[binIndex] -
+               2 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P2[binIndex] -
+               2 * ppY[binIndex] * P2[binIndex] * F1[binIndex] * P1[binIndex] +
+               2 * P1[binIndex] * mpY[binIndex] * F2[binIndex] * P2[binIndex] +
+               ppY[binIndex] * P1[binIndex] * P2[binIndex] -
+               P1[binIndex] * mpY[binIndex] * P2[binIndex] +
+               4 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] *
+                   P2[binIndex] +
+               P1[binIndex] * mmY[binIndex] * P2[binIndex] -
+               ppY[binIndex] * F1[binIndex] +
+               2 * ppY[binIndex] * F1[binIndex] * P1[binIndex] -
+               P1[binIndex] * mmY[binIndex] -
+               P1[binIndex] * mpY[binIndex] * F2[binIndex] +
+               ppY[binIndex] * F2[binIndex] * P1[binIndex] +
+               ppY[binIndex] * F1[binIndex] * F2[binIndex] -
+               2 * ppY[binIndex] * F1[binIndex] * F2[binIndex] * P1[binIndex] +
+               P1[binIndex] * mpY[binIndex] - ppY[binIndex] * P1[binIndex]) /
+                  ((-P1[binIndex] + 2 * F1[binIndex] * P1[binIndex] -
+                    F1[binIndex]) *
+                   (factor3 * factor3));
+          const double e1 = dI00 * ppE[binIndex];
+          const double e2 = dI11 * mmE[binIndex];
+          const double e3 = dI10 * mpE[binIndex];
+          const double e4 = dF1 * F1E[binIndex];
+          const double e5 = dF2 * F2E[binIndex];
+          const double e6 = dP1 * P1E[binIndex];
+          const double e7 = dP2 * P2E[binIndex];
+          pmE[binIndex] = std::sqrt(e1 * e1 + e2 * e2 + e3 * e3 + e4 * e4 +
+                                    e5 * e5 + e6 * e6 + e7 * e7);
+        }
+      }
+    }
+  }
+};
+
+class PolarizationEfficiencyCorTestPerformance : public CxxTest::TestSuite {
+public:
+  void setUp() override {
+    using namespace Mantid::API;
+    auto loadWS =
+        AlgorithmManager::Instance().createUnmanaged("LoadILLReflectometry");
+    loadWS->setChild(true);
+    loadWS->initialize();
+    loadWS->setProperty("Filename", "ILL/D17/317370.nxs");
+    loadWS->setProperty("OutputWorkspace", "output");
+    loadWS->setProperty("XUnit", "TimeOfFlight");
+    loadWS->execute();
+    m_ws00 = loadWS->getProperty("OutputWorkspace");
+    auto groupDetectors =
+        AlgorithmManager::Instance().createUnmanaged("GroupDetectors");
+    groupDetectors->setChild(true);
+    groupDetectors->initialize();
+    groupDetectors->setProperty("InputWorkspace", m_ws00);
+    groupDetectors->setProperty("OutputWorkspace", "output");
+    groupDetectors->setPropertyValue("WorkspaceIndexList", "201, 202, 203");
+    groupDetectors->execute();
+    m_ws00 = groupDetectors->getProperty("OutputWorkspace");
+    auto convertUnits =
+        AlgorithmManager::Instance().createUnmanaged("ConvertUnits");
+    convertUnits->setChild(true);
+    convertUnits->initialize();
+    convertUnits->setProperty("InputWorkspace", m_ws00);
+    convertUnits->setProperty("OutputWorkspace", "output");
+    convertUnits->setProperty("Target", "Wavelength");
+    convertUnits->execute();
+    m_ws00 = convertUnits->getProperty("OutputWorkspace");
+    auto crop = AlgorithmManager::Instance().createUnmanaged("CropWorkspace");
+    crop->setChild(true);
+    crop->initialize();
+    crop->setProperty("InputWorkspace", m_ws00);
+    crop->setProperty("OutputWorkspace", "output");
+    crop->setProperty("XMin", 0.);
+    crop->execute();
+    m_ws00 = crop->getProperty("OutputWorkspace");
+    AnalysisDataService::Instance().addOrReplace("00", m_ws00);
+    m_ws01 = m_ws00->clone();
+    AnalysisDataService::Instance().addOrReplace("01", m_ws01);
+    m_ws10 = m_ws00->clone();
+    AnalysisDataService::Instance().addOrReplace("10", m_ws10);
+    m_ws11 = m_ws00->clone();
+    AnalysisDataService::Instance().addOrReplace("11", m_ws11);
+    auto loadEff = AlgorithmManager::Instance().createUnmanaged(
+        "LoadILLPolarizationFactors");
+    loadEff->setChild(true);
+    loadEff->initialize();
+    loadEff->setProperty("Filename", "ILL/D17/PolarizationFactors.txt");
+    loadEff->setProperty("OutputWorkspace", "output");
+    loadEff->setProperty("WavelengthReference", m_ws00);
+    loadEff->execute();
+    m_effWS = loadEff->getProperty("OutputWorkspace");
+  }
+
+  void tearDown() override {
+    using namespace Mantid::API;
+    AnalysisDataService::Instance().clear();
+  }
+
+  void test_DirectBeamPerformance() {
+    using namespace Mantid::API;
+    for (int i = 0; i < 3000; ++i) {
+      PolarizationEfficiencyCor correction;
+      correction.setChild(true);
+      correction.setRethrows(true);
+      correction.initialize();
+      correction.setProperty("InputWorkspaces", "00");
+      correction.setProperty("OutputWorkspace", "output");
+      correction.setProperty("Flippers", "0");
+      correction.setProperty("Efficiencies", m_effWS);
+      TS_ASSERT_THROWS_NOTHING(correction.execute())
+    }
+  }
+
+  void test_ThreeInputsPerformanceMissing01() {
+    using namespace Mantid::API;
+    for (int i = 0; i < 3000; ++i) {
+      PolarizationEfficiencyCor correction;
+      correction.setChild(true);
+      correction.setRethrows(true);
+      correction.initialize();
+      correction.setProperty("InputWorkspaces", "00, 10, 11");
+      correction.setProperty("OutputWorkspace", "output");
+      correction.setProperty("Flippers", "00, 10, 11");
+      correction.setProperty("Efficiencies", m_effWS);
+      TS_ASSERT_THROWS_NOTHING(correction.execute())
+    }
+  }
+
+  void test_ThreeInputsPerformanceMissing10() {
+    using namespace Mantid::API;
+    for (int i = 0; i < 3000; ++i) {
+      PolarizationEfficiencyCor correction;
+      correction.setChild(true);
+      correction.setRethrows(true);
+      correction.initialize();
+      correction.setProperty("InputWorkspaces", "00, 01, 11");
+      correction.setProperty("OutputWorkspace", "output");
+      correction.setProperty("Flippers", "00, 01, 11");
+      correction.setProperty("Efficiencies", m_effWS);
+      TS_ASSERT_THROWS_NOTHING(correction.execute())
+    }
+  }
+
+  void test_TwoInputsNoAnalyzerPerformance() {
+    using namespace Mantid::API;
+    for (int i = 0; i < 3000; ++i) {
+      PolarizationEfficiencyCor correction;
+      correction.setChild(true);
+      correction.setRethrows(true);
+      correction.initialize();
+      correction.setProperty("InputWorkspaces", "00, 11");
+      correction.setProperty("OutputWorkspace", "output");
+      correction.setProperty("Flippers", "0, 1");
+      correction.setProperty("Efficiencies", m_effWS);
+      TS_ASSERT_THROWS_NOTHING(correction.execute())
+    }
+  }
+
+  void test_TwoInputsPerformance() {
+    using namespace Mantid::API;
+    for (int i = 0; i < 3000; ++i) {
+      PolarizationEfficiencyCor correction;
+      correction.setChild(true);
+      correction.setRethrows(true);
+      correction.initialize();
+      correction.setProperty("InputWorkspaces", "00, 11");
+      correction.setProperty("OutputWorkspace", "output");
+      correction.setProperty("Flippers", "00, 11");
+      correction.setProperty("Efficiencies", m_effWS);
+      TS_ASSERT_THROWS_NOTHING(correction.execute())
+    }
+  }
+
+private:
+  Mantid::API::MatrixWorkspace_sptr m_effWS;
+  Mantid::API::MatrixWorkspace_sptr m_ws00;
+  Mantid::API::MatrixWorkspace_sptr m_ws01;
+  Mantid::API::MatrixWorkspace_sptr m_ws10;
+  Mantid::API::MatrixWorkspace_sptr m_ws11;
+};
+
+#endif /* MANTID_ALGORITHMS_POLARIZATIONEFFICIENCYCORTEST_H_ */
diff --git a/Framework/Algorithms/test/RebinTest.h b/Framework/Algorithms/test/RebinTest.h
index afa07ed154765a782223f44100d167227093884a..00ea719f4815fea3275828127e1670443f765c05 100644
--- a/Framework/Algorithms/test/RebinTest.h
+++ b/Framework/Algorithms/test/RebinTest.h
@@ -36,7 +36,7 @@ std::unique_ptr<Rebin> prepare_rebin(const Parallel::Communicator &comm,
   auto create = ParallelTestHelpers::create<Algorithms::CreateWorkspace>(comm);
   std::vector<double> dataEYX(2000);
   for (size_t i = 0; i < dataEYX.size(); ++i)
-    dataEYX[i] = static_cast<double>(i % 2 + 1);
+    dataEYX[i] = static_cast<double>(i % 2);
   int nspec = 1000;
   create->setProperty<int>("NSpec", nspec);
   create->setProperty<std::vector<double>>("DataX", dataEYX);
@@ -69,19 +69,12 @@ void run_rebin_params_only_bin_width(const Parallel::Communicator &comm,
   using namespace Parallel;
   auto rebin = prepare_rebin(comm, storageMode);
   rebin->setProperty("Params", "0.5");
-  if (fromString(storageMode) == StorageMode::Distributed && comm.size() > 1) {
-    TS_ASSERT_THROWS_EQUALS(
-        rebin->execute(), const std::runtime_error &e, std::string(e.what()),
-        "MatrixWorkspace: Parallel support for XMin and XMax not implemented.");
+  TS_ASSERT_THROWS_NOTHING(rebin->execute());
+  MatrixWorkspace_const_sptr ws = rebin->getProperty("OutputWorkspace");
+  if (comm.rank() == 0 || fromString(storageMode) != StorageMode::MasterOnly) {
+    TS_ASSERT_EQUALS(ws->storageMode(), fromString(storageMode));
   } else {
-    TS_ASSERT_THROWS_NOTHING(rebin->execute());
-    MatrixWorkspace_const_sptr ws = rebin->getProperty("OutputWorkspace");
-    if (comm.rank() == 0 ||
-        fromString(storageMode) != StorageMode::MasterOnly) {
-      TS_ASSERT_EQUALS(ws->storageMode(), fromString(storageMode));
-    } else {
-      TS_ASSERT_EQUALS(ws, nullptr);
-    }
+    TS_ASSERT_EQUALS(ws, nullptr);
   }
 }
 }
diff --git a/Framework/Algorithms/test/SofQWCutTest.h b/Framework/Algorithms/test/SofQWCutTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..d8c7909bc8bc70ae1c723350be3447006149fa24
--- /dev/null
+++ b/Framework/Algorithms/test/SofQWCutTest.h
@@ -0,0 +1,226 @@
+#ifndef SOFQWCUTTEST_H_
+#define SOFQWCUTTEST_H_
+
+#include <cxxtest/TestSuite.h>
+#include "MantidAlgorithms/SofQW.h"
+#include "MantidAlgorithms/SofQWPolygon.h"
+#include "MantidAlgorithms/SofQWNormalisedPolygon.h"
+#include "MantidAPI/AnalysisDataService.h"
+#include "MantidAPI/Axis.h"
+#include "MantidAPI/MatrixWorkspace.h"
+#include "MantidAPI/WorkspaceHistory.h"
+#include "MantidAPI/WorkspaceGroup.h"
+#include "MantidKernel/Unit.h"
+#include "MantidDataHandling/LoadNexusProcessed.h"
+
+#include <boost/make_shared.hpp>
+
+using namespace Mantid::API;
+
+class SofQWCutTest : public CxxTest::TestSuite {
+public:
+  template <typename SQWType> WorkspaceGroup_sptr runSQW() {
+    Mantid::DataHandling::LoadNexusProcessed loader;
+    loader.initialize();
+    loader.setChild(true);
+    loader.setProperty("Filename", "MAR21335_Ei60meV.nxs");
+    loader.setPropertyValue("OutputWorkspace", "__unused");
+    loader.execute();
+
+    Workspace_sptr loadedWS = loader.getProperty("OutputWorkspace");
+    auto inWS = boost::dynamic_pointer_cast<MatrixWorkspace>(loadedWS);
+
+    // First make a cut along |Q|
+    SQWType sqw_q;
+    sqw_q.initialize();
+    // Cannot be marked as child or history is not recorded
+    TS_ASSERT_THROWS_NOTHING(sqw_q.setProperty("InputWorkspace", inWS));
+    std::ostringstream wsname_q;
+    wsname_q << "_tmp_q_" << loadedWS;
+    TS_ASSERT_THROWS_NOTHING(
+        sqw_q.setPropertyValue("OutputWorkspace", wsname_q.str()));
+    TS_ASSERT_THROWS_NOTHING(
+        sqw_q.setPropertyValue("QAxisBinning", "0,0.0125,10"));
+    TS_ASSERT_THROWS_NOTHING(sqw_q.setPropertyValue("EMode", "Direct"));
+    TS_ASSERT_THROWS_NOTHING(
+        sqw_q.setPropertyValue("EAxisBinning", "-1.5,3,1.5"));
+    TS_ASSERT_THROWS_NOTHING(sqw_q.execute());
+    TS_ASSERT(sqw_q.isExecuted());
+
+    // Now make a cut along E
+    SQWType sqw_e;
+    sqw_e.initialize();
+    // Cannot be marked as child or history is not recorded
+    TS_ASSERT_THROWS_NOTHING(sqw_e.setProperty("InputWorkspace", inWS));
+    std::ostringstream wsname_e;
+    wsname_e << "_tmp_e_" << loadedWS;
+    TS_ASSERT_THROWS_NOTHING(
+        sqw_e.setPropertyValue("OutputWorkspace", wsname_e.str()));
+    TS_ASSERT_THROWS_NOTHING(sqw_e.setPropertyValue("QAxisBinning", "5,5,10"));
+    TS_ASSERT_THROWS_NOTHING(sqw_e.setPropertyValue("EMode", "Direct"));
+    TS_ASSERT_THROWS_NOTHING(
+        sqw_e.setPropertyValue("EAxisBinning", "-5,0.5,55"));
+    TS_ASSERT_THROWS_NOTHING(sqw_e.execute());
+    TS_ASSERT(sqw_e.isExecuted());
+
+    auto &dataStore = AnalysisDataService::Instance();
+    auto ws_q = dataStore.retrieveWS<MatrixWorkspace>(wsname_q.str());
+    auto ws_e = dataStore.retrieveWS<MatrixWorkspace>(wsname_e.str());
+    WorkspaceGroup_sptr result = boost::make_shared<WorkspaceGroup>();
+    result->addWorkspace(ws_q);
+    result->addWorkspace(ws_e);
+    dataStore.remove(wsname_q.str());
+    dataStore.remove(wsname_e.str());
+
+    return result;
+  }
+
+  void test_sofqw1() {
+    auto result = runSQW<Mantid::Algorithms::SofQW>();
+    const double delta(1e-08);
+
+    auto ws_q =
+        boost::dynamic_pointer_cast<MatrixWorkspace>(result->getItem(0));
+    TS_ASSERT_EQUALS(ws_q->getAxis(0)->length(), 2);
+    TS_ASSERT_EQUALS(ws_q->getAxis(0)->unit()->unitID(), "DeltaE");
+    TS_ASSERT_EQUALS((*(ws_q->getAxis(0)))(0), -1.5);
+    TS_ASSERT_EQUALS((*(ws_q->getAxis(0)))(1), 1.5);
+    TS_ASSERT_EQUALS(ws_q->getAxis(1)->length(), 801);
+    TS_ASSERT_EQUALS(ws_q->getAxis(1)->unit()->unitID(), "MomentumTransfer");
+    TS_ASSERT_EQUALS((*(ws_q->getAxis(1)))(0), 0.0);
+    TS_ASSERT_DELTA((*(ws_q->getAxis(1)))(400), 5.0, delta);
+    TS_ASSERT_EQUALS((*(ws_q->getAxis(1)))(800), 10.);
+    TS_ASSERT_DELTA(ws_q->readY(44)[0], 957.651473192, delta);
+    TS_ASSERT_DELTA(ws_q->readE(44)[0], 11.170620862, delta);
+    TS_ASSERT_DELTA(ws_q->readY(231)[0], 398.376497999, delta);
+    TS_ASSERT_DELTA(ws_q->readE(231)[0], 62.100406977, delta);
+    TS_ASSERT_DELTA(ws_q->readY(377)[0], 232.378738932, delta);
+    TS_ASSERT_DELTA(ws_q->readE(377)[0], 14.249051816, delta);
+    TS_ASSERT_DELTA(ws_q->readY(536)[0], 1832.305224868, delta);
+    TS_ASSERT_DELTA(ws_q->readE(536)[0], 30.518095107, delta);
+    TS_ASSERT_DELTA(ws_q->readY(575)[0], 453.761721652, delta);
+    TS_ASSERT_DELTA(ws_q->readE(575)[0], 13.114162862, delta);
+
+    auto ws_e =
+        boost::dynamic_pointer_cast<MatrixWorkspace>(result->getItem(1));
+    TS_ASSERT_EQUALS(ws_e->getAxis(0)->length(), 121);
+    TS_ASSERT_EQUALS(ws_e->getAxis(0)->unit()->unitID(), "DeltaE");
+    TS_ASSERT_EQUALS((*(ws_e->getAxis(0)))(0), -5.);
+    TS_ASSERT_DELTA((*(ws_e->getAxis(0)))(60), 25.0, delta);
+    TS_ASSERT_EQUALS((*(ws_e->getAxis(0)))(120), 55.);
+    TS_ASSERT_EQUALS(ws_e->getAxis(1)->length(), 2);
+    TS_ASSERT_EQUALS(ws_e->getAxis(1)->unit()->unitID(), "MomentumTransfer");
+    TS_ASSERT_EQUALS((*(ws_e->getAxis(1)))(0), 5.);
+    TS_ASSERT_EQUALS((*(ws_e->getAxis(1)))(1), 10.);
+    TS_ASSERT_DELTA(ws_e->readY(0)[29], 9.254559817, delta);
+    TS_ASSERT_DELTA(ws_e->readE(0)[29], 0.030174342, delta);
+    TS_ASSERT_DELTA(ws_e->readY(0)[87], 13.447772682, delta);
+    TS_ASSERT_DELTA(ws_e->readE(0)[87], 0.051154627, delta);
+    TS_ASSERT_DELTA(ws_e->readY(0)[88], 10.455499052, delta);
+    TS_ASSERT_DELTA(ws_e->readE(0)[88], 0.044293372, delta);
+    TS_ASSERT_DELTA(ws_e->readY(0)[93], 3.587987494, delta);
+    TS_ASSERT_DELTA(ws_e->readE(0)[93], 0.026975541, delta);
+    TS_ASSERT_DELTA(ws_e->readY(0)[113], 1.038679349, delta);
+    TS_ASSERT_DELTA(ws_e->readE(0)[113], 0.044564335, delta);
+  }
+
+  void test_sofqw2() {
+    auto result = runSQW<Mantid::Algorithms::SofQWPolygon>();
+    const double delta(1e-08);
+
+    auto ws_q =
+        boost::dynamic_pointer_cast<MatrixWorkspace>(result->getItem(0));
+    TS_ASSERT_EQUALS(ws_q->getAxis(0)->length(), 2);
+    TS_ASSERT_EQUALS(ws_q->getAxis(0)->unit()->unitID(), "DeltaE");
+    TS_ASSERT_EQUALS((*(ws_q->getAxis(0)))(0), -1.5);
+    TS_ASSERT_EQUALS((*(ws_q->getAxis(0)))(1), 1.5);
+    TS_ASSERT_EQUALS(ws_q->getAxis(1)->length(), 801);
+    TS_ASSERT_EQUALS(ws_q->getAxis(1)->unit()->unitID(), "MomentumTransfer");
+    TS_ASSERT_EQUALS((*(ws_q->getAxis(1)))(0), 0.0);
+    TS_ASSERT_DELTA((*(ws_q->getAxis(1)))(400), 5.0, delta);
+    TS_ASSERT_EQUALS((*(ws_q->getAxis(1)))(800), 10.);
+    TS_ASSERT_DELTA(ws_q->readY(46)[0], 0.577055734, delta);
+    TS_ASSERT_DELTA(ws_q->readE(46)[0], 0.016266516, delta);
+    TS_ASSERT_DELTA(ws_q->readY(461)[0], 0.642083585, delta);
+    TS_ASSERT_DELTA(ws_q->readE(461)[0], 0.027694702, delta);
+    TS_ASSERT_DELTA(ws_q->readY(703)[0], 8.619229199, delta);
+    TS_ASSERT_DELTA(ws_q->readE(703)[0], 0.119106057, delta);
+    TS_ASSERT_DELTA(ws_q->readY(727)[0], 1.212655693, delta);
+    TS_ASSERT_DELTA(ws_q->readE(727)[0], 0.047618940, delta);
+    TS_ASSERT_DELTA(ws_q->readY(787)[0], 12.280788436, delta);
+    TS_ASSERT_DELTA(ws_q->readE(787)[0], 0.239880567, delta);
+
+    auto ws_e =
+        boost::dynamic_pointer_cast<MatrixWorkspace>(result->getItem(1));
+    TS_ASSERT_EQUALS(ws_e->getAxis(0)->length(), 121);
+    TS_ASSERT_EQUALS(ws_e->getAxis(0)->unit()->unitID(), "DeltaE");
+    TS_ASSERT_EQUALS((*(ws_e->getAxis(0)))(0), -5.);
+    TS_ASSERT_DELTA((*(ws_e->getAxis(0)))(60), 25.0, delta);
+    TS_ASSERT_EQUALS((*(ws_e->getAxis(0)))(120), 55.);
+    TS_ASSERT_EQUALS(ws_e->getAxis(1)->length(), 2);
+    TS_ASSERT_EQUALS(ws_e->getAxis(1)->unit()->unitID(), "MomentumTransfer");
+    TS_ASSERT_EQUALS((*(ws_e->getAxis(1)))(0), 5.);
+    TS_ASSERT_EQUALS((*(ws_e->getAxis(1)))(1), 10.);
+    TS_ASSERT_DELTA(ws_e->readY(0)[5], 1120.875680688, delta);
+    TS_ASSERT_DELTA(ws_e->readE(0)[5], 5.143783614, delta);
+    TS_ASSERT_DELTA(ws_e->readY(0)[16], 171.212246850, delta);
+    TS_ASSERT_DELTA(ws_e->readE(0)[16], 2.079560024, delta);
+    TS_ASSERT_DELTA(ws_e->readY(0)[28], 40.854749824, delta);
+    TS_ASSERT_DELTA(ws_e->readE(0)[28], 1.014309882, delta);
+    TS_ASSERT_DELTA(ws_e->readY(0)[36], 54.655069317, delta);
+    TS_ASSERT_DELTA(ws_e->readE(0)[36], 1.179213931, delta);
+    TS_ASSERT_DELTA(ws_e->readY(0)[113], 3.724579351, delta);
+    TS_ASSERT_DELTA(ws_e->readE(0)[113], 0.485226781, delta);
+  }
+
+  void test_sofqw3() {
+    auto result = runSQW<Mantid::Algorithms::SofQWNormalisedPolygon>();
+    const double delta(1e-08);
+
+    auto ws_q =
+        boost::dynamic_pointer_cast<MatrixWorkspace>(result->getItem(0));
+    TS_ASSERT_EQUALS(ws_q->getAxis(0)->length(), 2);
+    TS_ASSERT_EQUALS(ws_q->getAxis(0)->unit()->unitID(), "DeltaE");
+    TS_ASSERT_EQUALS((*(ws_q->getAxis(0)))(0), -1.5);
+    TS_ASSERT_EQUALS((*(ws_q->getAxis(0)))(1), 1.5);
+    TS_ASSERT_EQUALS(ws_q->getAxis(1)->length(), 801);
+    TS_ASSERT_EQUALS(ws_q->getAxis(1)->unit()->unitID(), "MomentumTransfer");
+    TS_ASSERT_EQUALS((*(ws_q->getAxis(1)))(0), 0.0);
+    TS_ASSERT_DELTA((*(ws_q->getAxis(1)))(400), 5.0, delta);
+    TS_ASSERT_EQUALS((*(ws_q->getAxis(1)))(800), 10.);
+    TS_ASSERT_DELTA(ws_q->readY(64)[0], 0.144715421, delta);
+    TS_ASSERT_DELTA(ws_q->readE(64)[0], 0.004902364, delta);
+    TS_ASSERT_DELTA(ws_q->readY(345)[0], 0.658678386, delta);
+    TS_ASSERT_DELTA(ws_q->readE(345)[0], 0.016428510, delta);
+    TS_ASSERT_DELTA(ws_q->readY(595)[0], 0.159563545, delta);
+    TS_ASSERT_DELTA(ws_q->readE(595)[0], 0.007806737, delta);
+    TS_ASSERT_DELTA(ws_q->readY(683)[0], 0.178108225, delta);
+    TS_ASSERT_DELTA(ws_q->readE(683)[0], 0.012961016, delta);
+    TS_ASSERT_DELTA(ws_q->readY(745)[0], 2.086237760, delta);
+    TS_ASSERT_DELTA(ws_q->readE(745)[0], 0.034274820, delta);
+
+    auto ws_e =
+        boost::dynamic_pointer_cast<MatrixWorkspace>(result->getItem(1));
+    TS_ASSERT_EQUALS(ws_e->getAxis(0)->length(), 121);
+    TS_ASSERT_EQUALS(ws_e->getAxis(0)->unit()->unitID(), "DeltaE");
+    TS_ASSERT_EQUALS((*(ws_e->getAxis(0)))(0), -5.);
+    TS_ASSERT_DELTA((*(ws_e->getAxis(0)))(60), 25.0, delta);
+    TS_ASSERT_EQUALS((*(ws_e->getAxis(0)))(120), 55.);
+    TS_ASSERT_EQUALS(ws_e->getAxis(1)->length(), 2);
+    TS_ASSERT_EQUALS(ws_e->getAxis(1)->unit()->unitID(), "MomentumTransfer");
+    TS_ASSERT_EQUALS((*(ws_e->getAxis(1)))(0), 5.);
+    TS_ASSERT_EQUALS((*(ws_e->getAxis(1)))(1), 10.);
+    TS_ASSERT_DELTA(ws_e->readY(0)[3], 2.003485282, delta);
+    TS_ASSERT_DELTA(ws_e->readE(0)[3], 0.012959174, delta);
+    TS_ASSERT_DELTA(ws_e->readY(0)[20], 0.136945077, delta);
+    TS_ASSERT_DELTA(ws_e->readE(0)[20], 0.003215584, delta);
+    TS_ASSERT_DELTA(ws_e->readY(0)[27], 0.158356991, delta);
+    TS_ASSERT_DELTA(ws_e->readE(0)[27], 0.003823510, delta);
+    TS_ASSERT_DELTA(ws_e->readY(0)[78], 0.197240860, delta);
+    TS_ASSERT_DELTA(ws_e->readE(0)[78], 0.005090697, delta);
+    TS_ASSERT_DELTA(ws_e->readY(0)[119], 0.027223857, delta);
+    TS_ASSERT_DELTA(ws_e->readE(0)[119], 0.002884865, delta);
+  }
+};
+
+#endif /*SOFQWCUTTEST_H_*/
diff --git a/Framework/Beamline/CMakeLists.txt b/Framework/Beamline/CMakeLists.txt
index f3074cab31aa23a13cd5e32f5698276a8d01c9e6..5d3253bff2446886aa79de5643db8249d09314d2 100644
--- a/Framework/Beamline/CMakeLists.txt
+++ b/Framework/Beamline/CMakeLists.txt
@@ -32,15 +32,19 @@ endif(UNITY_BUILD)
 add_library ( Beamline ${SRC_FILES} ${INC_FILES} )
 # Set the name of the generated library
 set_target_properties ( Beamline PROPERTIES OUTPUT_NAME MantidBeamline
-                                 COMPILE_DEFINITIONS IN_MANTID_BEAMLINE
-                                 INSTALL_RPATH "@loader_path/../MacOS" )
+                                 COMPILE_DEFINITIONS IN_MANTID_BEAMLINE )
+if (OSX_VERSION VERSION_GREATER 10.8)
+  set_target_properties(Beamline PROPERTIES INSTALL_RPATH "@loader_path/../MacOS" )
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(Beamline PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
+endif()
 
 target_include_directories( Beamline SYSTEM PUBLIC ${Boost_INCLUDE_DIRS})
 
 # Add to the 'Framework' group in VS
 set_property ( TARGET Beamline PROPERTY FOLDER "MantidFramework" )
 
-target_link_libraries ( Beamline LINK_PUBLIC Eigen3::Eigen LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} 
+target_link_libraries ( Beamline LINK_PUBLIC Eigen3::Eigen LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME}
                         ${GSL_LIBRARIES} ${MANTIDLIBS} )
 
 # Add the unit tests directory
diff --git a/Framework/Crystal/CMakeLists.txt b/Framework/Crystal/CMakeLists.txt
index 797b0684c62888cbffeb2984b881660a9b5965b6..fcc6c95a1e9b4c2b76a5f5a34cd12231012b0234 100644
--- a/Framework/Crystal/CMakeLists.txt
+++ b/Framework/Crystal/CMakeLists.txt
@@ -228,9 +228,13 @@ endif()
 # Add the target for this directory
 add_library ( Crystal ${SRC_FILES} ${INC_FILES})
 # Set the name of the generated library
-set_target_properties ( Crystal PROPERTIES OUTPUT_NAME MantidCrystal
-  INSTALL_RPATH "@loader_path/../Contents/MacOS/"
-)
+set_target_properties ( Crystal PROPERTIES OUTPUT_NAME MantidCrystal )
+if (OSX_VERSION VERSION_GREATER 10.8)
+  set_target_properties(Crystal PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS/" )
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(Crystal PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
+endif()
+
 # Add to the 'Framework' group in VS
 set_property ( TARGET Crystal PROPERTY FOLDER "MantidFramework" )
 
diff --git a/Framework/Crystal/inc/MantidCrystal/SaveLauenorm.h b/Framework/Crystal/inc/MantidCrystal/SaveLauenorm.h
index 19c0535417087e6e9bd01fceb4c227e3e8d0a379..81dc93b6f4812fd8f57e3c2cdea7005b253397c3 100644
--- a/Framework/Crystal/inc/MantidCrystal/SaveLauenorm.h
+++ b/Framework/Crystal/inc/MantidCrystal/SaveLauenorm.h
@@ -4,6 +4,7 @@
 #include "MantidKernel/System.h"
 #include "MantidAPI/Algorithm.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
+#include "MantidGeometry/Crystal/OrientedLattice.h"
 
 namespace Mantid {
 namespace Crystal {
@@ -39,6 +40,8 @@ private:
 
   DataObjects::PeaksWorkspace_sptr ws;
   void sizeBanks(std::string bankName, int &nCols, int &nRows);
+  std::vector<int> crystalSystem(Geometry::OrientedLattice lattice,
+                                 std::vector<DataObjects::Peak> peaks);
 };
 
 } // namespace Mantid
diff --git a/Framework/Crystal/src/SaveLauenorm.cpp b/Framework/Crystal/src/SaveLauenorm.cpp
index b00daba0e4f5eec0b342ddae5da4bac6a3da39f0..5d097aa0aff0a82a3306adf772ad93479fa563b0 100644
--- a/Framework/Crystal/src/SaveLauenorm.cpp
+++ b/Framework/Crystal/src/SaveLauenorm.cpp
@@ -8,7 +8,11 @@
 #include "MantidCrystal/AnvredCorrection.h"
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/Strings.h"
+#include "MantidAPI/Sample.h"
+#include "MantidGeometry/Instrument/Goniometer.h"
 #include <fstream>
+#include <iostream>
+#include <iomanip>
 #include <Poco/File.h>
 #include <Poco/Path.h>
 #include <cmath>
@@ -61,6 +65,7 @@ void SaveLauenorm::init() {
       Kernel::make_unique<ArrayProperty<std::string>>("EliminateBankNumbers",
                                                       Direction::Input),
       "Comma deliminated string of bank numbers to exclude for example 1,2,5");
+  declareProperty("LaueScaleFormat", false, "New format for Lauescale");
 }
 
 //----------------------------------------------------------------------------------------------
@@ -69,6 +74,8 @@ void SaveLauenorm::init() {
 void SaveLauenorm::exec() {
 
   std::string filename = getProperty("Filename");
+  Poco::Path path(filename);
+  std::string basename = path.getBaseName(); // Filename minus extension
   ws = getProperty("InputWorkspace");
   double scaleFactor = getProperty("ScalePeaks");
   double dMin = getProperty("MinDSpacing");
@@ -78,6 +85,7 @@ void SaveLauenorm::exec() {
   double minIsigI = getProperty("MinIsigI");
   double minIntensity = getProperty("MinIntensity");
   int widthBorder = getProperty("WidthBorder");
+  bool newFormat = getProperty("LaueScaleFormat");
 
   // sequenceNo and run number
   int sequenceNo = 0;
@@ -114,6 +122,112 @@ void SaveLauenorm::exec() {
   // scaleDet scales intensity and sigI for detector banks
   bool scaleDet = getProperty("UseDetScale");
   auto inst = ws->getInstrument();
+  OrientedLattice lattice;
+  if (newFormat) {
+    type = "RunNumber";
+    if (!ws->sample().hasOrientedLattice()) {
+
+      const std::string fft("FindUBUsingIndexedPeaks");
+      API::IAlgorithm_sptr findUB = this->createChildAlgorithm(fft);
+      findUB->initialize();
+      findUB->setProperty<PeaksWorkspace_sptr>("PeaksWorkspace", ws);
+      findUB->executeAsChildAlg();
+
+      if (!ws->sample().hasOrientedLattice()) {
+        g_log.notice(std::string("Could not find UB for ") +
+                     std::string(ws->getName()));
+        throw std::invalid_argument(std::string("Could not find UB for ") +
+                                    std::string(ws->getName()));
+      }
+    }
+    lattice = ws->sample().getOrientedLattice();
+  }
+  // Count peaks
+  std::vector<int> numPeaks;
+  int count = 0;
+  std::vector<double> maxLamVec;
+  std::vector<double> minLamVec;
+  std::vector<double> sumLamVec;
+  std::vector<double> minDVec;
+  double maxLam = 0;
+  double minLam = EMPTY_DBL();
+  double sumLam = 0;
+  double minD = EMPTY_DBL();
+  for (int wi = 0; wi < ws->getNumberPeaks(); wi++) {
+
+    Peak &p = peaks[wi];
+    double intensity = p.getIntensity();
+    double sigI = p.getSigmaIntensity();
+    if (intensity == 0.0 || !(std::isfinite(sigI)))
+      continue;
+    if (minIsigI != EMPTY_DBL() && intensity < std::abs(minIsigI * sigI))
+      continue;
+    int sequence = p.getRunNumber();
+    std::string bankName = p.getBankName();
+    int nCols, nRows;
+    sizeBanks(bankName, nCols, nRows);
+    if (widthBorder != EMPTY_INT() &&
+        (p.getCol() < widthBorder || p.getRow() < widthBorder ||
+         p.getCol() > (nCols - widthBorder) ||
+         p.getRow() > (nRows - widthBorder)))
+      continue;
+    // Take out the "bank" part of the bank name and convert to an int
+    bankName.erase(remove_if(bankName.begin(), bankName.end(),
+                             not1(std::ptr_fun(::isdigit))),
+                   bankName.end());
+    if (type.compare(0, 2, "Ba") == 0) {
+      Strings::convert(bankName, sequence);
+    }
+    // Do not use peaks from these banks
+    std::vector<std::string> notBanks = getProperty("EliminateBankNumbers");
+    if (std::find(notBanks.begin(), notBanks.end(), bankName) != notBanks.end())
+      continue;
+    if (scaleDet) {
+      if (inst->hasParameter("detScale" + bankName)) {
+        double correc = static_cast<double>(
+            inst->getNumberParameter("detScale" + bankName)[0]);
+        intensity *= correc;
+        sigI *= correc;
+      }
+    }
+    if (minIntensity != EMPTY_DBL() && intensity < minIntensity)
+      continue;
+    double lambda = p.getWavelength();
+    double dsp = p.getDSpacing();
+    if (dsp < dMin || lambda < wlMin ||
+        (wlMax != EMPTY_DBL() && lambda > wlMax))
+      continue;
+    if (p.getH() == 0 && p.getK() == 0 && p.getL() == 0)
+      continue;
+
+    if (sequence != oldSequence) {
+      oldSequence = sequence;
+      numPeaks.push_back(count);
+      maxLamVec.push_back(maxLam);
+      minLamVec.push_back(minLam);
+      sumLamVec.push_back(sumLam);
+      minDVec.push_back(minD);
+      count = 0;
+      maxLam = 0;
+      minLam = EMPTY_DBL();
+      sumLam = 0;
+      minD = EMPTY_DBL();
+    }
+    count++;
+    if (lambda < minLam)
+      minLam = lambda;
+    if (lambda > maxLam)
+      maxLam = lambda;
+    if (dsp < minD)
+      minD = dsp;
+    sumLam += lambda;
+  }
+  numPeaks.push_back(count);
+  maxLamVec.push_back(maxLam);
+  minLamVec.push_back(minLam);
+  sumLamVec.push_back(sumLam);
+  minDVec.push_back(minD);
+  oldSequence = -1;
   // Go through each peak at this run / bank
   for (int wi = 0; wi < ws->getNumberPeaks(); wi++) {
 
@@ -137,7 +251,7 @@ void SaveLauenorm::exec() {
     bankName.erase(remove_if(bankName.begin(), bankName.end(),
                              not1(std::ptr_fun(::isdigit))),
                    bankName.end());
-    if (type.compare(0, 2, "Ru") != 0) {
+    if (type.compare(0, 2, "Ba") == 0) {
       Strings::convert(bankName, sequence);
     }
     // Do not use peaks from these banks
@@ -160,11 +274,16 @@ void SaveLauenorm::exec() {
     double lambda = p.getWavelength();
     double dsp = p.getDSpacing();
     if (dsp < dMin || lambda < wlMin ||
-        (minIsigI != EMPTY_DBL() && lambda > wlMax))
+        (wlMax != EMPTY_DBL() && lambda > wlMax))
       continue;
     // This can be bank number of run number depending on
     if (sequence != oldSequence) {
       oldSequence = sequence;
+      if (newFormat) {
+        out << "END-OF-REFLECTION-DATA\n";
+        out << "HARMONICS DATA    0 REFLECTIONS\n";
+        out << "END-OF-FILE\n";
+      }
       out.flush();
       out.close();
       sequenceNo++;
@@ -172,13 +291,113 @@ void SaveLauenorm::exec() {
       ss.clear();
       ss << std::setw(3) << std::setfill('0') << sequenceNo;
 
-      Poco::Path path(filename);
-      std::string basename = path.getBaseName(); // Filename minus extension
       // Chop off filename
       path.makeParent();
       path.append(basename + ss.str());
+      if (newFormat)
+        path.setExtension("geasc");
       Poco::File fileobj(path);
       out.open(path.toString().c_str(), std::ios::out);
+      if (newFormat) {
+        out << "TITL\n";
+        out << basename << "\n";
+        out << "CRYS " << basename.substr(0, 6) << "\n";
+        out << "FIDX     1.00000     1.00000     1.00000     1.00000     "
+               "1.00000     1.00000\n";
+        out << "FIDY     1.00000     1.00000     1.00000     1.00000     "
+               "1.00000     1.00000\n";
+        out << "OMEG     1.00000     1.00000     1.00000     1.00000     "
+               "1.00000     1.00000\n";
+        out << "CELL " << std::setw(11) << std::setprecision(4)
+            << 1.0 / lattice.a() << std::setw(12) << std::setprecision(4)
+            << 1.0 / lattice.b() << std::setw(12) << std::setprecision(4)
+            << 1.0 / lattice.c() << std::setw(9)
+            << static_cast<int>(lattice.alpha() + 0.5) << std::setw(9)
+            << static_cast<int>(lattice.beta() + 0.5) << std::setw(9)
+            << static_cast<int>(lattice.gamma() + 0.5) << "\n";
+        std::vector<int> systemNo = crystalSystem(lattice, peaks);
+        out << "SYST    " << systemNo[0] << "   " << systemNo[1] << "   0   0"
+            << "\n";
+        out << "RAST      0.050"
+            << "\n";
+        out << "IBOX   1  1   1   1   1"
+            << "\n";
+        Goniometer gon(p.getGoniometerMatrix());
+        std::vector<double> angles = gon.getEulerAngles("yzy");
+
+        double phi = angles[2];
+        double chi = angles[1];
+        double omega = angles[0];
+
+        out << "PHIS " << std::setw(11) << std::setprecision(4) << phi
+            << std::setw(12) << std::setprecision(4) << chi << std::setw(12)
+            << std::setprecision(4) << omega << "\n";
+        out << "LAMS      ";
+
+        out << std::setprecision(1) << std::fixed
+            << sumLamVec[sequenceNo] / numPeaks[sequenceNo] << " "
+            << minLamVec[sequenceNo] << " " << maxLamVec[sequenceNo] << "\n";
+
+        out << "DMIN      ";
+        out << std::setprecision(2) << std::fixed << minDVec[sequenceNo]
+            << "\n";
+
+        // distance from sample to detector (use first pixel) in mm
+        double L2 = 500.0;
+        out << "RADI     " << std::setprecision(0) << std::fixed << L2 << "\n";
+        out << "SPIN      0.000"
+            << "\n";
+        out << "XC_S     0.00000     0.00000     0.00000     0.00000     "
+               "0.00000     0.00000\n";
+        out << "YC_S     0.00000     0.00000     0.00000     0.00000     "
+               "0.00000     0.00000\n";
+        out << "WC_S     0.00000     0.00000     0.00000     0.00000     "
+               "0.00000     0.00000\n";
+        out << "DELT       0.0000"
+            << "\n";
+        out << "TWIS    0.00000     0.00000     0.00000     0.00000     "
+               "0.00000     0.00000 \n";
+        out << "TILT    0.00000     0.00000     0.00000     0.00000     "
+               "0.00000     0.00000 \n";
+        out << "BULG    0.00000     0.00000     0.00000     0.00000     "
+               "0.00000     0.00000 \n";
+        out << "CTOF     " << L2 << "\n";
+        out << "YSCA     0.00000     0.00000     0.00000     0.00000     "
+               "0.00000     0.00000\n";
+        out << "CRAT     0.00000     0.00000     0.00000     0.00000     "
+               "0.00000     0.00000\n";
+        out << "MINI          ";
+        if (minIntensity != EMPTY_DBL()) {
+          out << minIntensity << "\n";
+        } else {
+          out << "0.0\n";
+        }
+        out << "MULT  ";
+        out << numPeaks[sequenceNo]
+            << "     0      0      0      0      0      0      0      "
+               "0      0\n";
+        out << "      0      0      0      0      0      0      0      0      "
+               "0      0\n";
+        out << "      0 \n";
+        out << "LAMH  " << numPeaks[sequenceNo]
+            << "     0      0      0      0      0      0      0      0      "
+               "0\n";
+        out << "      0      0      0      0      0      0\n";
+        out << "VERS  1"
+            << "\n";
+        out << "PACK        0"
+            << "\n";
+        out << "NSPT   " << numPeaks[sequenceNo]
+            << "      0      0      0      0"
+            << "\n";
+        out << "NODH " << numPeaks[sequenceNo]
+            << "    0      0      0      0      0      0      0      0      0\n"
+            << "      0      0\n";
+        out << "INTF        0"
+            << "\n";
+        out << "REFLECTION DATA   " << numPeaks[sequenceNo] << " REFLECTIONS"
+            << "\n";
+      }
     }
     // h k l lambda theta intensity and  sig(intensity)  in format
     // (3I5,2F10.5,2I10)
@@ -189,10 +408,28 @@ void SaveLauenorm::exec() {
     out << std::setw(5) << Utils::round(qSign * p.getH()) << std::setw(5)
         << Utils::round(qSign * p.getK()) << std::setw(5)
         << Utils::round(qSign * p.getL());
+    if (newFormat) {
+      // Convert to mm from centre
+      out << std::setw(10) << std::fixed << std::setprecision(5)
+          << (p.getCol() - 127.5) * 150.0 / 256.0;
+      out << std::setw(10) << std::fixed << std::setprecision(5)
+          << (p.getRow() - 127.5) * 150.0 / 256.0 << "\n";
+    }
     out << std::setw(10) << std::fixed << std::setprecision(5) << lambda;
-    // Assume that want theta not two-theta
-    out << std::setw(10) << std::fixed << std::setprecision(5)
-        << 0.5 * scattering;
+    if (newFormat) {
+      // mult nodal ovlp close h2 k2 l2 nidx lambda2 ipoint
+      out << " 1 0 0 0 0 0 0 0 0 0 ";
+    }
+
+    if (newFormat) {
+      // Dmin threshold squared for next harmonic
+      out << std::setw(10) << std::fixed << std::setprecision(5)
+          << dsp * dsp * 0.25 << "\n";
+    } else {
+      // Assume that want theta not two-theta
+      out << std::setw(10) << std::fixed << std::setprecision(5)
+          << 0.5 * scattering;
+    }
 
     // SHELX can read data without the space between the l and intensity
     if (p.getDetectorID() != -1) {
@@ -201,18 +438,45 @@ void SaveLauenorm::exec() {
         g_log.warning() << "Scaled intensity, " << ckIntensity
                         << " is too large for format.  Decrease ScalePeaks.\n";
       out << std::setw(10) << Utils::round(ckIntensity);
+      if (newFormat) {
+        // mult nodal ovlp close h2 k2 l2 nidx lambda2 ipoint
+        out << " -9999 -9999 -9999 -9999 -9999 \n";
+      }
 
       out << std::setw(10) << Utils::round(scaleFactor * sigI);
+      if (newFormat) {
+        // mult nodal ovlp close h2 k2 l2 nidx lambda2 ipoint
+        out << " -9999 -9999 -9999 -9999 -9999 \n";
+        out << std::setw(10) << Utils::round(ckIntensity);
+        out << " -9999 -9999 -9999 -9999 -9999 \n";
+        out << std::setw(10) << Utils::round(scaleFactor * sigI);
+        out << " -9999 -9999 -9999 -9999 -9999 * ";
+      }
     } else {
       // This is data from LoadLauenorm which is already corrected
       out << std::setw(10) << Utils::round(intensity);
-
+      if (newFormat) {
+        // 5 more films (dummy)
+        out << " -9999 -9999 -9999 -9999 -9999 \n";
+      }
       out << std::setw(10) << Utils::round(sigI);
+      if (newFormat) {
+        // 5 more films (dummy)
+        out << " -9999 -9999 -9999 -9999 -9999 \n";
+        out << std::setw(10) << Utils::round(intensity);
+        out << " -9999 -9999 -9999 -9999 -9999 \n";
+        out << std::setw(10) << Utils::round(sigI);
+        out << " -9999 -9999 -9999 -9999 -9999 * ";
+      }
     }
 
     out << '\n';
   }
-
+  if (newFormat) {
+    out << "END-OF-REFLECTION-DATA\n";
+    out << "HARMONICS DATA    0 REFLECTIONS\n";
+    out << "END-OF-FILE\n";
+  }
   out.flush();
   out.close();
 }
@@ -242,6 +506,102 @@ void SaveLauenorm::sizeBanks(std::string bankName, int &nCols, int &nRows) {
     nCols = static_cast<int>(children.size());
   }
 }
+std::vector<int> SaveLauenorm::crystalSystem(OrientedLattice lattice,
+                                             std::vector<Peak> peaks) {
+  std::vector<int> systemVec;
+  int alpha = static_cast<int>(lattice.alpha() + 0.5);
+  int beta = static_cast<int>(lattice.beta() + 0.5);
+  int gamma = static_cast<int>(lattice.gamma() + 0.5);
+  int a = static_cast<int>(lattice.a() * 1000 + 0.5);
+  int b = static_cast<int>(lattice.b() * 1000 + 0.5);
+  int c = static_cast<int>(lattice.c() * 1000 + 0.5);
+  if (alpha == 90 && beta == 90 && gamma == 90) {
+    if (a == b && a == c) {
+      systemVec.push_back(7); // cubic I,F
+    } else if (a == b) {
+      systemVec.push_back(4); // tetragonal I
+    } else {
+      systemVec.push_back(3); // orthorhombic I,A,B,C,F
+    }
+  } else if (alpha == 90 && beta == 90 && gamma == 120 && a == b) {
+    systemVec.push_back(6); // hexagonal
+  } else if ((alpha == 90 && beta == 90) || (alpha == 90 && gamma == 90) ||
+             (beta == 90 && gamma == 90)) {
+    systemVec.push_back(2); // monoclinic I,A,B,C
+  } else if (alpha == 90 && beta == 90 && gamma != 90 && a == b && a == c) {
+    systemVec.push_back(5); // rhombohedral R
+  } else {
+    systemVec.push_back(1); // triclinic
+  }
+  int i = 0;
+  int fp = 0;
+  int fm = 0;
+  int cc = 0;
+  int bc = 0;
+  int ac = 0;
+  int r = 0;
+  int total = 0;
+  for (size_t j = 0; j < peaks.size(); j++) {
+    int h = static_cast<int>(peaks[j].getH() + 0.5);
+    int k = static_cast<int>(peaks[j].getK() + 0.5);
+    int l = static_cast<int>(peaks[j].getL() + 0.5);
+    if (h + k + l == 0)
+      continue;
+    total++;
+    if ((h + k + l) % 2 == 0) {
+      i++;
+    }
+    if (h % 2 == 0 && k % 2 == 0 && l % 2 == 0) {
+      fp++;
+    }
+    if (h % 2 != 0 && k % 2 != 0 && l % 2 != 0) {
+      fm++;
+    }
+    if ((h + k) % 2 == 0) {
+      cc++;
+    }
+    if ((h + l) % 2 == 0) {
+      bc++;
+    }
+    if ((k + l) % 2 == 0) {
+      ac++;
+    }
+    if ((-h + k + l) % 3 == 0) {
+      r++;
+    }
+  }
+  int maxCen = 1;
+  int maxPeaks = 0;
+  if (maxPeaks < i) {
+    maxCen = 2; // I
+    maxPeaks = i;
+  }
+  if (maxPeaks < ac) {
+    maxCen = 3; // A
+    maxPeaks = ac;
+  }
+  if (maxPeaks < bc) {
+    maxCen = 4; // B
+    maxPeaks = bc;
+  }
+  if (maxPeaks < cc) {
+    maxCen = 5; // C
+    maxPeaks = cc;
+  }
+  if (maxPeaks < fp || maxPeaks < fm) {
+    maxCen = 6; // F
+    maxPeaks = std::max(fp, fm);
+  }
+  if (maxPeaks < r) {
+    maxCen = 7; // R
+    maxPeaks = r;
+  }
+  if (maxPeaks < 6 * total / 10) {
+    maxCen = 1; // P
+  }
+  systemVec.push_back(maxCen); // P
+  return systemVec;
+}
 
 } // namespace Mantid
 } // namespace Crystal
diff --git a/Framework/CurveFitting/CMakeLists.txt b/Framework/CurveFitting/CMakeLists.txt
index 18aa84dbcfa919e253f8369bb664e7b6643adedf..010f0e4dfd3fc781d2c2fa98a96915aaabfa7e75 100644
--- a/Framework/CurveFitting/CMakeLists.txt
+++ b/Framework/CurveFitting/CMakeLists.txt
@@ -475,13 +475,15 @@ enable_precompiled_headers( inc/MantidCurveFitting/PrecompiledHeader.h SRC_FILES
 # Add the target for this directory
 add_library ( CurveFitting ${SRC_FILES} ${INC_FILES})
 # Set the name of the generated library
-set_target_properties ( CurveFitting PROPERTIES OUTPUT_NAME MantidCurveFitting 
+set_target_properties ( CurveFitting PROPERTIES OUTPUT_NAME MantidCurveFitting
   COMPILE_DEFINITIONS IN_MANTID_CURVEFITTING
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( CurveFitting PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
-endif () 
+  set_target_properties(CurveFitting PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(CurveFitting PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
+endif ()
 
 # Add to the 'Framework' group in VS
 set_property ( TARGET CurveFitting PROPERTY FOLDER "MantidFramework" )
diff --git a/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldControl.h b/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldControl.h
index c13ca3b7e4e49806613f992378217c508bb0faf9..2f75b05900291d8235f0efb86188464fa576bec8 100644
--- a/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldControl.h
+++ b/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldControl.h
@@ -88,10 +88,6 @@ private:
   std::vector<double> m_temperatures;
   /// Cache the default peak FWHMs
   std::vector<double> m_FWHMs;
-  /// Cache number of fitted peaks
-  // mutable std::vector<size_t> m_nPeaks;
-  /// Cache the list of "spectra" corresponding to physical properties
-  std::vector<int> m_physprops;
   /// Caches of the width functions
   std::vector<std::vector<double>> m_fwhmX;
   std::vector<std::vector<double>> m_fwhmY;
diff --git a/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldFunction.h b/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldFunction.h
index 29080f77220e60f7118d96e4bf3d808d93ece112..4168512286dc9117c90f8c45d4693e0682b36575 100644
--- a/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldFunction.h
+++ b/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldFunction.h
@@ -241,6 +241,7 @@ private:
       m_mapPrefixes2PhysProps;
   /// Temporary cache for parameter values during source function resetting.
   mutable std::vector<double> m_parameterResetCache;
+  mutable std::vector<bool> m_fixResetCache;
 };
 
 } // namespace Functions
diff --git a/Framework/CurveFitting/src/Algorithms/NormaliseByPeakArea.cpp b/Framework/CurveFitting/src/Algorithms/NormaliseByPeakArea.cpp
index 023be8cddb104d2c4538c2a79f549841ecb9563c..449539f0fec3dc262fb5bd850edb9ef8a26e7ce8 100644
--- a/Framework/CurveFitting/src/Algorithms/NormaliseByPeakArea.cpp
+++ b/Framework/CurveFitting/src/Algorithms/NormaliseByPeakArea.cpp
@@ -342,19 +342,22 @@ void NormaliseByPeakArea::symmetriseYSpace() {
   const int64_t nhist =
       static_cast<int64_t>(m_symmetrisedWS->getNumberHistograms());
 
+  PARALLEL_FOR_IF(Kernel::threadSafe(*m_symmetrisedWS))
   for (int64_t i = 0; i < nhist; ++i) {
     const auto &xsym = m_symmetrisedWS->x(i);
     auto &ySymOut = m_symmetrisedWS->mutableY(i);
     auto &eSymOut = m_symmetrisedWS->mutableE(i);
+    const auto &yyspace = m_yspaceWS->y(i);
+    const auto &eyspace = m_yspaceWS->e(i);
 
     for (size_t j = 0; j < npts; ++j) {
-      const double ein = m_yspaceWS->e(i)[j];
+      const double ein = eyspace[j];
       const double absXj = fabs(xsym[j]);
 
       double yout(0.0), eout(1e8);
       for (size_t k = 0; k < npts; ++k) {
-        const double yk = m_yspaceWS->y(i)[k];
-        const double ek = m_yspaceWS->e(i)[k];
+        const double yk = yyspace[k];
+        const double ek = eyspace[k];
         const double absXk = fabs(xsym[k]);
         if (absXj >= (absXk - dy) && absXj <= (absXk + dy) && ein != 0.0) {
 
diff --git a/Framework/CurveFitting/src/Functions/CrystalFieldControl.cpp b/Framework/CurveFitting/src/Functions/CrystalFieldControl.cpp
index b017bee64d023f445d6d516c859df8417a33d253..ff604962cb3b2be6c93d07fbc6a2c4517561df5a 100644
--- a/Framework/CurveFitting/src/Functions/CrystalFieldControl.cpp
+++ b/Framework/CurveFitting/src/Functions/CrystalFieldControl.cpp
@@ -55,10 +55,31 @@ void CrystalFieldControl::setAttribute(const std::string &name,
       m_temperatures = attr.asVector();
       buildControls();
     } else if (name == "FWHMs") {
+      const size_t nSpec = m_temperatures.size();
       m_FWHMs = attr.asVector();
-      if (m_FWHMs.size() == 1 && m_FWHMs.size() != m_temperatures.size()) {
-        m_FWHMs.assign(m_temperatures.size(), m_FWHMs.front());
+      if (m_FWHMs.size() == 1 && m_FWHMs.size() != nSpec) {
+        m_FWHMs.assign(nSpec, m_FWHMs.front());
+      }
+      if (!m_FWHMs.empty()) {
+        m_fwhmX.resize(nSpec);
+        m_fwhmY.resize(nSpec);
+        for (size_t i = 0; i < nSpec; ++i) {
+          m_fwhmX[i].clear();
+          m_fwhmY[i].clear();
+          if (nSpec > 1) {
+            auto &control = *getFunction(i);
+            control.setAttribute("FWHMX", Attribute(std::vector<double>()));
+            control.setAttribute("FWHMY", Attribute(std::vector<double>()));
+          } else {
+            setAttribute("FWHMX", Attribute(std::vector<double>()));
+            setAttribute("FWHMY", Attribute(std::vector<double>()));
+          }
+        }
       }
+    } else if ((name.compare(0, 5, "FWHMX") == 0 ||
+                name.compare(0, 5, "FWHMY") == 0) &&
+               !attr.asVector().empty()) {
+      m_FWHMs.clear();
     }
     API::IFunction::setAttribute(name, attr);
   }
diff --git a/Framework/CurveFitting/src/Functions/CrystalFieldFunction.cpp b/Framework/CurveFitting/src/Functions/CrystalFieldFunction.cpp
index df242ad42379f308cfd3986aa84597715baa8991..99c8063ad8e508a98fbbbb2e88b7ef10da61b6d7 100644
--- a/Framework/CurveFitting/src/Functions/CrystalFieldFunction.cpp
+++ b/Framework/CurveFitting/src/Functions/CrystalFieldFunction.cpp
@@ -492,6 +492,10 @@ void CrystalFieldFunction::setAttribute(const std::string &attName,
     m_source.reset();
   }
   attRef.first->setAttribute(attRef.second, attr);
+  if (attName.find("FWHM") != std::string::npos ||
+      attName.find("Background") != std::string::npos) {
+    m_dirtyTarget = true;
+  }
 }
 
 /// Check if attribute attName exists
@@ -632,9 +636,12 @@ void CrystalFieldFunction::buildSourceFunction() const {
       m_parameterResetCache.size() == m_source->nParams()) {
     for (size_t i = 0; i < m_parameterResetCache.size(); ++i) {
       m_source->setParameter(i, m_parameterResetCache[i]);
+      if (m_fixResetCache[i])
+        m_source->fix(i);
     }
   }
   m_parameterResetCache.clear();
+  m_fixResetCache.clear();
 }
 
 /// Update spectrum function if necessary.
@@ -741,9 +748,9 @@ void CrystalFieldFunction::buildSingleSiteMultiSpectrum() const {
   for (size_t i = 0; i < nSpec; ++i) {
     auto intensityScaling =
         m_control.getFunction(i)->getParameter("IntensityScaling");
-    fun->addFunction(buildSpectrum(nre, energies, waveFunctions,
-                                   temperatures[i], FWHMs[i], i, addBackground,
-                                   intensityScaling));
+    fun->addFunction(buildSpectrum(
+        nre, energies, waveFunctions, temperatures[i],
+        FWHMs.size() > i ? FWHMs[i] : 0., i, addBackground, intensityScaling));
     fun->setDomainIndex(i, i);
   }
   auto &physProps = m_control.physProps();
@@ -843,9 +850,10 @@ void CrystalFieldFunction::buildMultiSiteMultiSpectrum() const {
     for (size_t i = 0; i < nSpec; ++i) {
       auto spectrumIntensityScaling =
           m_control.getFunction(i)->getParameter("IntensityScaling");
-      spectra[i]->addFunction(buildSpectrum(
-          nre, energies, waveFunctions, temperatures[i], FWHMs[i], i,
-          addBackground, ionIntensityScaling * spectrumIntensityScaling));
+      spectra[i]->addFunction(
+          buildSpectrum(nre, energies, waveFunctions, temperatures[i],
+                        FWHMs.size() > i ? FWHMs[i] : 0., i, addBackground,
+                        ionIntensityScaling * spectrumIntensityScaling));
     }
 
     size_t i = 0;
@@ -934,7 +942,7 @@ API::IFunction_sptr CrystalFieldFunction::buildSpectrum(
   }
 
   auto xVec = m_control.getFunction(iSpec)->getAttribute("FWHMX").asVector();
-  auto yVec = m_control.getFunction(iSpec)->getAttribute("FWHMX").asVector();
+  auto yVec = m_control.getFunction(iSpec)->getAttribute("FWHMY").asVector();
   CrystalFieldUtils::buildSpectrumFunction(*spectrum, peakShape, values, xVec,
                                            yVec, fwhmVariation, fwhm,
                                            nRequiredPeaks, fixAllPeaks);
@@ -1052,7 +1060,7 @@ void CrystalFieldFunction::updateSingleSiteSingleSpectrum() const {
   auto peakShape = m_control.getAttribute("PeakShape").asString();
   bool fixAllPeaks = m_control.getAttribute("FixAllPeaks").asBool();
   auto xVec = m_control.getAttribute("FWHMX").asVector();
-  auto yVec = m_control.getAttribute("FWHMX").asVector();
+  auto yVec = m_control.getAttribute("FWHMY").asVector();
   auto &FWHMs = m_control.FWHMs();
   auto defaultFWHM = FWHMs.empty() ? 0.0 : FWHMs[0];
   size_t indexShift = hasBackground() ? 1 : 0;
@@ -1085,7 +1093,8 @@ void CrystalFieldFunction::updateSingleSiteMultiSpectrum() const {
   auto &FWHMs = m_control.FWHMs();
   for (size_t iSpec = 0; iSpec < temperatures.size(); ++iSpec) {
     updateSpectrum(*fun.getFunction(iSpec), nre, energies, waveFunctions,
-                   temperatures[iSpec], FWHMs[iSpec], iSpec, iFirst);
+                   temperatures[iSpec],
+                   FWHMs.size() > iSpec ? FWHMs[iSpec] : 0., iSpec, iFirst);
   }
 
   for (auto &prop : m_mapPrefixes2PhysProps) {
@@ -1099,7 +1108,7 @@ void CrystalFieldFunction::updateMultiSiteSingleSpectrum() const {
   auto peakShape = getAttribute("PeakShape").asString();
   bool fixAllPeaks = getAttribute("FixAllPeaks").asBool();
   auto xVec = m_control.getAttribute("FWHMX").asVector();
-  auto yVec = m_control.getAttribute("FWHMX").asVector();
+  auto yVec = m_control.getAttribute("FWHMY").asVector();
   auto &FWHMs = m_control.FWHMs();
   auto defaultFWHM = FWHMs.empty() ? 0.0 : FWHMs[0];
 
@@ -1142,7 +1151,8 @@ void CrystalFieldFunction::updateMultiSiteMultiSpectrum() const {
       auto &ionSpectrum =
           dynamic_cast<CompositeFunction &>(*spectrum.getFunction(ionIndex));
       updateSpectrum(ionSpectrum, nre, energies, waveFunctions,
-                     temperatures[iSpec], FWHMs[iSpec], iSpec, iFirst);
+                     temperatures[iSpec],
+                     FWHMs.size() > iSpec ? FWHMs[iSpec] : 0., iSpec, iFirst);
     }
 
     std::string prefix("ion");
@@ -1174,7 +1184,7 @@ void CrystalFieldFunction::updateSpectrum(
   const auto peakShape = getAttribute("PeakShape").asString();
   const bool fixAllPeaks = getAttribute("FixAllPeaks").asBool();
   auto xVec = m_control.getFunction(iSpec)->getAttribute("FWHMX").asVector();
-  auto yVec = m_control.getFunction(iSpec)->getAttribute("FWHMX").asVector();
+  auto yVec = m_control.getFunction(iSpec)->getAttribute("FWHMY").asVector();
   auto intensityScaling =
       m_control.getFunction(iSpec)->getParameter("IntensityScaling");
   FunctionValues values;
@@ -1390,8 +1400,10 @@ void CrystalFieldFunction::cacheSourceParameters() const {
   }
   auto np = m_source->nParams();
   m_parameterResetCache.resize(np);
+  m_fixResetCache.resize(np);
   for (size_t i = 0; i < np; ++i) {
     m_parameterResetCache[i] = m_source->getParameter(i);
+    m_fixResetCache[i] = m_source->isFixed(i);
   }
 }
 
diff --git a/Framework/CurveFitting/src/Functions/CrystalFieldMultiSpectrum.cpp b/Framework/CurveFitting/src/Functions/CrystalFieldMultiSpectrum.cpp
index 246fec09c7f9e1398a8f8b857c20006f2e891e37..f1af30eea7407b205972ccd4e73adc63fcabba0a 100644
--- a/Framework/CurveFitting/src/Functions/CrystalFieldMultiSpectrum.cpp
+++ b/Framework/CurveFitting/src/Functions/CrystalFieldMultiSpectrum.cpp
@@ -15,6 +15,7 @@
 #include "MantidAPI/ParameterTie.h"
 
 #include "MantidKernel/Exception.h"
+#include <boost/regex.hpp>
 #include <iostream>
 
 namespace Mantid {
@@ -29,6 +30,10 @@ DECLARE_FUNCTION(CrystalFieldMultiSpectrum)
 
 namespace {
 
+// Regex for the FWHMX# type strings (single-site mode)
+const boost::regex FWHMX_ATTR_REGEX("FWHMX([0-9]+)");
+const boost::regex FWHMY_ATTR_REGEX("FWHMY([0-9]+)");
+
 /// Define the source function for CrystalFieldMultiSpectrum.
 /// Its function() method is not needed.
 class Peaks : public CrystalFieldPeaksBase, public API::IFunctionGeneral {
@@ -51,11 +56,15 @@ public:
   std::vector<size_t> m_IntensityScalingIdx;
   std::vector<size_t> m_PPLambdaIdxChild;
   std::vector<size_t> m_PPLambdaIdxSelf;
+  std::vector<size_t> m_PPChi0IdxChild;
+  std::vector<size_t> m_PPChi0IdxSelf;
   /// Declare the intensity scaling parameters: one per spectrum.
   void declareIntensityScaling(size_t nSpec) {
     m_IntensityScalingIdx.clear();
     m_PPLambdaIdxChild.resize(nSpec, -1);
     m_PPLambdaIdxSelf.resize(nSpec, -1);
+    m_PPChi0IdxChild.resize(nSpec, -1);
+    m_PPChi0IdxSelf.resize(nSpec, -1);
     for (size_t i = 0; i < nSpec; ++i) {
       auto si = std::to_string(i);
       try { // If parameter has already been declared, don't declare it.
@@ -71,6 +80,8 @@ public:
     if (m_PPLambdaIdxSelf.size() <= iSpec) {
       m_PPLambdaIdxSelf.resize(iSpec + 1, -1);
       m_PPLambdaIdxChild.resize(iSpec + 1, -1);
+      m_PPChi0IdxSelf.resize(iSpec + 1, -1);
+      m_PPChi0IdxChild.resize(iSpec + 1, -1);
     }
     auto si = std::to_string(iSpec);
     try { // If parameter has already been declared, don't declare it.
@@ -78,7 +89,13 @@ public:
                        "Effective exchange coupling of dataset " + si);
     } catch (std::invalid_argument &) {
     }
+    try { // If parameter has already been declared, don't declare it.
+      declareParameter("Chi0" + si, 0.0,
+                       "Effective exchange coupling of dataset " + si);
+    } catch (std::invalid_argument &) {
+    }
     m_PPLambdaIdxSelf[iSpec] = parameterIndex("Lambda" + si);
+    m_PPChi0IdxSelf[iSpec] = parameterIndex("Chi0" + si);
   }
 };
 }
@@ -123,6 +140,7 @@ CrystalFieldMultiSpectrum::createEquivalentFunctions() const {
 /// Perform custom actions on setting certain attributes.
 void CrystalFieldMultiSpectrum::setAttribute(const std::string &name,
                                              const Attribute &attr) {
+  boost::smatch match;
   if (name == "Temperatures") {
     // Define (declare) the parameters for intensity scaling.
     const auto nSpec = attr.asVector().size();
@@ -130,13 +148,23 @@ void CrystalFieldMultiSpectrum::setAttribute(const std::string &name,
     m_nOwnParams = m_source->nParams();
     m_fwhmX.resize(nSpec);
     m_fwhmY.resize(nSpec);
+    std::vector<double> new_fwhm = getAttribute("FWHMs").asVector();
+    const auto nWidths = new_fwhm.size();
+    if (nWidths != nSpec) {
+      new_fwhm.resize(nSpec);
+      if (nWidths > nSpec) {
+        for (size_t iSpec = nWidths; iSpec < nSpec; ++iSpec) {
+          new_fwhm[iSpec] = new_fwhm[0];
+        }
+      }
+    }
+    FunctionGenerator::setAttribute("FWHMs", Attribute(new_fwhm));
     for (size_t iSpec = 0; iSpec < nSpec; ++iSpec) {
       const auto suffix = std::to_string(iSpec);
       declareAttribute("FWHMX" + suffix, Attribute(m_fwhmX[iSpec]));
       declareAttribute("FWHMY" + suffix, Attribute(m_fwhmY[iSpec]));
     }
-  }
-  if (name == "PhysicalProperties") {
+  } else if (name == "PhysicalProperties") {
     const auto physpropId = attr.asVector();
     const auto nSpec = physpropId.size();
     auto &source = dynamic_cast<Peaks &>(*m_source);
@@ -162,6 +190,12 @@ void CrystalFieldMultiSpectrum::setAttribute(const std::string &name,
         m_nOwnParams = m_source->nParams();
       }
     }
+  } else if (boost::regex_match(name, match, FWHMX_ATTR_REGEX)) {
+    auto iSpec = std::stoul(match[1]);
+    m_fwhmX[iSpec].clear();
+  } else if (boost::regex_match(name, match, FWHMY_ATTR_REGEX)) {
+    auto iSpec = std::stoul(match[1]);
+    m_fwhmY[iSpec].clear();
   }
   FunctionGenerator::setAttribute(name, attr);
 }
@@ -332,6 +366,8 @@ API::IFunction_sptr CrystalFieldMultiSpectrum::buildPhysprop(
     spectrum.setAttribute("powder", getAttribute("powder" + suffix));
     dynamic_cast<Peaks &>(*m_source).m_PPLambdaIdxChild[iSpec] =
         spectrum.parameterIndex("Lambda");
+    dynamic_cast<Peaks &>(*m_source).m_PPChi0IdxChild[iSpec] =
+        spectrum.parameterIndex("Chi0");
     return retval;
   }
   case Magnetisation: {
@@ -408,6 +444,8 @@ void CrystalFieldMultiSpectrum::updateSpectrum(
     auto &source = dynamic_cast<Peaks &>(*m_source);
     suscept.setParameter(source.m_PPLambdaIdxChild[iSpec],
                          getParameter(source.m_PPLambdaIdxSelf[iSpec]));
+    suscept.setParameter(source.m_PPChi0IdxChild[iSpec],
+                         getParameter(source.m_PPChi0IdxSelf[iSpec]));
     break;
   }
   case Magnetisation: {
diff --git a/Framework/CurveFitting/src/Functions/CrystalFieldSusceptibility.cpp b/Framework/CurveFitting/src/Functions/CrystalFieldSusceptibility.cpp
index 123c6ef59a6ce8622eee2bfe981d38957fa62ac5..26d83385c0c99c0183cbd8861b513543c087d8a8 100644
--- a/Framework/CurveFitting/src/Functions/CrystalFieldSusceptibility.cpp
+++ b/Framework/CurveFitting/src/Functions/CrystalFieldSusceptibility.cpp
@@ -139,6 +139,19 @@ void CrystalFieldSusceptibilityBase::function1D(double *out,
   } else {
     calculate(out, xValues, nData, m_en, m_wf, m_nre, H, convfact);
   }
+  const double lambda = getParameter("Lambda");
+  const double EPS = 1.e-6;
+  if (fabs(lambda) > EPS) {
+    for (size_t i = 0; i < nData; i++) {
+      out[i] /= (1. - lambda * out[i]); // chi = chi_cf/(1 - lambda.chi_cf)
+    }
+  }
+  const double chi0 = getParameter("Chi0");
+  if (fabs(lambda) > 1.e-6) {
+    for (size_t i = 0; i < nData; i++) {
+      out[i] += chi0;
+    }
+  }
   if (getAttribute("inverse").asBool()) {
     for (size_t i = 0; i < nData; i++) {
       out[i] = 1. / out[i];
@@ -150,12 +163,6 @@ void CrystalFieldSusceptibilityBase::function1D(double *out,
       out[i] *= fact;
     }
   }
-  const double lambda = getParameter("Lambda");
-  if (fabs(lambda) > 1.e-6) {
-    for (size_t i = 0; i < nData; i++) {
-      out[i] /= (1. - lambda * out[i]); // chi = chi0/(1 - lambda.chi0)
-    }
-  }
 }
 
 DECLARE_FUNCTION(CrystalFieldSusceptibility)
@@ -164,6 +171,7 @@ CrystalFieldSusceptibility::CrystalFieldSusceptibility()
     : CrystalFieldPeaksBase(), CrystalFieldSusceptibilityBase(),
       m_setDirect(false) {
   declareParameter("Lambda", 0.0, "Effective exchange interaction");
+  declareParameter("Chi0", 0.0, "Background or remnant susceptibility");
 }
 
 // Sets the eigenvectors / values directly
@@ -186,6 +194,7 @@ void CrystalFieldSusceptibility::function1D(double *out, const double *xValues,
 
 CrystalFieldSusceptibilityCalculation::CrystalFieldSusceptibilityCalculation() {
   declareParameter("Lambda", 0.0, "Effective exchange interaction");
+  declareParameter("Chi0", 0.0, "Background or remnant susceptibility");
 }
 
 // Sets the eigenvectors / values directly
diff --git a/Framework/CurveFitting/test/Algorithms/FitTest.h b/Framework/CurveFitting/test/Algorithms/FitTest.h
index 389349da3e97e5a525f9d53e986c7d8544d9d26f..a02d1d169d0a883ef7cd9a1ebe6f52e3dec5106e 100644
--- a/Framework/CurveFitting/test/Algorithms/FitTest.h
+++ b/Framework/CurveFitting/test/Algorithms/FitTest.h
@@ -33,7 +33,6 @@ using namespace Mantid::DataObjects;
 
 using Mantid::TestHelpers::MultiDomainFunctionTest_Function;
 
-namespace {
 class TestMinimizer : public API::IFuncMinimizer {
 public:
   /// Constructor setting a value for the relative error acceptance
@@ -76,7 +75,6 @@ private:
 };
 
 DECLARE_FUNCMINIMIZER(TestMinimizer, TestMinimizer)
-}
 
 class FitTest : public CxxTest::TestSuite {
 public:
diff --git a/Framework/CurveFitting/test/Algorithms/PlotPeakByLogValueTest.h b/Framework/CurveFitting/test/Algorithms/PlotPeakByLogValueTest.h
index de2bc59b44bcd82d1aee0559ec75719bcf30d10e..9c1d7d0d1ed4ad65cd0de9f0784df2667d054f7c 100644
--- a/Framework/CurveFitting/test/Algorithms/PlotPeakByLogValueTest.h
+++ b/Framework/CurveFitting/test/Algorithms/PlotPeakByLogValueTest.h
@@ -36,7 +36,6 @@ using Mantid::HistogramData::LinearGenerator;
 typedef Mantid::DataObjects::Workspace2D_sptr WS_type;
 typedef Mantid::DataObjects::TableWorkspace_sptr TWS_type;
 
-namespace {
 struct Fun {
   double operator()(double, int i) { return double(i + 1); }
 };
@@ -72,7 +71,6 @@ public:
 private:
   std::string m_name;
 };
-}
 
 class PlotPeak_Expression {
 public:
diff --git a/Framework/DataHandling/CMakeLists.txt b/Framework/DataHandling/CMakeLists.txt
index 166f7bf3d97f6b8852f7caab0b2ce631cfbf5d77..fafc115113f161a5f6c6a3ce11b45d6f592b4664 100644
--- a/Framework/DataHandling/CMakeLists.txt
+++ b/Framework/DataHandling/CMakeLists.txt
@@ -541,7 +541,9 @@ set_target_properties ( DataHandling PROPERTIES OUTPUT_NAME MantidDataHandling
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( DataHandling PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
+  set_target_properties(DataHandling PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(DataHandling PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 # Add to the 'Framework' group in VS
diff --git a/Framework/DataHandling/inc/MantidDataHandling/DetermineChunking.h b/Framework/DataHandling/inc/MantidDataHandling/DetermineChunking.h
index d6a3b8c8f0e6fa501394716047cd623f7ac0ed8d..8b8daff910fa329f2cba92de20cadec42ac7b70c 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/DetermineChunking.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/DetermineChunking.h
@@ -4,7 +4,7 @@
 #include <string>
 #include <vector>
 #include "MantidKernel/System.h"
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/ParallelAlgorithm.h"
 #include "MantidAPI/IEventWorkspace_fwd.h"
 
 namespace Mantid {
@@ -58,7 +58,7 @@ enum FileType {
   RAW_FILE          ///< ISIS raw files
 };
 
-class DLLExport DetermineChunking : public API::Algorithm {
+class DLLExport DetermineChunking : public API::ParallelAlgorithm {
 public:
   const std::string name() const override;
   /// Summary of algorithms purpose
diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadCalFile.h b/Framework/DataHandling/inc/MantidDataHandling/LoadCalFile.h
index 3c6ec5a945145188da844632efcf4b9aca343caf..c303d3859e2a3a8927d848979bafea799edfeb85 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/LoadCalFile.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/LoadCalFile.h
@@ -47,6 +47,11 @@ public:
                           Mantid::DataObjects::OffsetsWorkspace_sptr offsetsWS,
                           Mantid::DataObjects::MaskWorkspace_sptr maskWS);
 
+protected:
+  Parallel::ExecutionMode getParallelExecutionMode(
+      const std::map<std::string, Parallel::StorageMode> &storageModes)
+      const override;
+
 private:
   /// Initialise the properties
   void init() override;
diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadDiffCal.h b/Framework/DataHandling/inc/MantidDataHandling/LoadDiffCal.h
index 39ae0b1f03c26b711011c47d2c6c4d11f6ee805f..a2a205d2264f62eeaaae42819d1e47fbbc348e32 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/LoadDiffCal.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/LoadDiffCal.h
@@ -43,6 +43,11 @@ public:
   const std::string category() const override;
   const std::string summary() const override;
 
+protected:
+  Parallel::ExecutionMode getParallelExecutionMode(
+      const std::map<std::string, Parallel::StorageMode> &storageModes)
+      const override;
+
 private:
   void init() override;
   void exec() override;
diff --git a/Framework/DataHandling/inc/MantidDataHandling/PDLoadCharacterizations.h b/Framework/DataHandling/inc/MantidDataHandling/PDLoadCharacterizations.h
index 7e0b8e11fce5300c8547c11f03cbcde637975b9a..59358d651965eadd46d6660c3c0ef93bf3c5a746 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/PDLoadCharacterizations.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/PDLoadCharacterizations.h
@@ -4,7 +4,7 @@
 #include <iosfwd>
 
 #include "MantidKernel/System.h"
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/ParallelAlgorithm.h"
 #include "MantidAPI/ITableWorkspace_fwd.h"
 
 namespace Mantid {
@@ -34,7 +34,7 @@ namespace DataHandling {
   File change history is stored at: <https://github.com/mantidproject/mantid>
   Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
-class DLLExport PDLoadCharacterizations : public API::Algorithm {
+class DLLExport PDLoadCharacterizations : public API::ParallelAlgorithm {
 public:
   const std::string name() const override;
   int version() const override;
diff --git a/Framework/DataHandling/inc/MantidDataHandling/SaveFocusedXYE.h b/Framework/DataHandling/inc/MantidDataHandling/SaveFocusedXYE.h
index b980b8be1664e240e67cfa41402288bf2472a409..7da884dc5cc1a1ccb0a96fc34134e3610d61e89f 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/SaveFocusedXYE.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/SaveFocusedXYE.h
@@ -1,10 +1,7 @@
 #ifndef DATAHANDING_SAVEFOCUSEDXYE_H_
 #define DATAHANDING_SAVEFOCUSEDXYE_H_
 
-//---------------------------------------------------
-// Includes
-//---------------------------------------------------
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/SerialAlgorithm.h"
 
 namespace Mantid {
 namespace DataHandling {
@@ -58,11 +55,9 @@ namespace DataHandling {
      File change history is stored at: <https://github.com/mantidproject/mantid>
      Code Documentation is available at: <http://doxygen.mantidproject.org>
   */
-class DLLExport SaveFocusedXYE : public API::Algorithm {
+class DLLExport SaveFocusedXYE : public API::SerialAlgorithm {
 public:
   enum HeaderType { XYE, MAUD, TOPAS };
-  /// Constructor
-  SaveFocusedXYE();
   /// Algorithm's name
   const std::string name() const override { return "SaveFocusedXYE"; }
   /// Summary of algorithms purpose
@@ -116,7 +111,7 @@ private:
                           int perioidNum) override;
 
   /// Header type
-  HeaderType m_headerType;
+  HeaderType m_headerType{XYE};
   /// Comment character
   std::string m_comment;
 };
diff --git a/Framework/DataHandling/inc/MantidDataHandling/SaveGSS.h b/Framework/DataHandling/inc/MantidDataHandling/SaveGSS.h
index 712f64c0bcdb63c50020b5ba33d5a5dc0c10c7c0..adb8cdd17c6b216b5aa54f78d3a0ec6be1038f8c 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/SaveGSS.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/SaveGSS.h
@@ -1,10 +1,7 @@
 #ifndef DATAHANDING_SAVEGSS_H_
 #define DATAHANDING_SAVEGSS_H_
 
-//---------------------------------------------------
-// Includes
-//---------------------------------------------------
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/SerialAlgorithm.h"
 #include "MantidAPI/SpectrumInfo.h"
 #include "MantidAPI/Run.h"
 #include "MantidKernel/System.h"
@@ -71,10 +68,8 @@ namespace DataHandling {
      File change history is stored at: <https://github.com/mantidproject/mantid>
      Code Documentation is available at: <http://doxygen.mantidproject.org>
   */
-class DLLExport SaveGSS : public Mantid::API::Algorithm {
+class DLLExport SaveGSS : public Mantid::API::SerialAlgorithm {
 public:
-  /// Constructor
-  SaveGSS();
   /// Algorithm's name
   const std::string name() const override { return "SaveGSS"; }
   /// Summary of algorithms purpose
diff --git a/Framework/DataHandling/inc/MantidDataHandling/SetSampleMaterial.h b/Framework/DataHandling/inc/MantidDataHandling/SetSampleMaterial.h
index cc9a5006a3a160903545066457ba842ecf1581b6..a4e0461fdbeb365bb0973f075337011b1297afab 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/SetSampleMaterial.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/SetSampleMaterial.h
@@ -1,10 +1,7 @@
 #ifndef MANTID_DATAHANDLING_SetSampleMaterial_H_
 #define MANTID_DATAHANDLING_SetSampleMaterial_H_
 
-//--------------------------------
-// Includes
-//--------------------------------
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/DistributedAlgorithm.h"
 #include "MantidKernel/NeutronAtom.h"
 
 namespace Mantid {
@@ -39,7 +36,7 @@ namespace DataHandling {
     File change history is stored at: <https://github.com/mantidproject/mantid>
     Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
-class DLLExport SetSampleMaterial : public Mantid::API::Algorithm {
+class DLLExport SetSampleMaterial : public Mantid::API::DistributedAlgorithm {
 public:
   /// Algorithm's name
   const std::string name() const override;
diff --git a/Framework/DataHandling/inc/MantidDataHandling/SortTableWorkspace.h b/Framework/DataHandling/inc/MantidDataHandling/SortTableWorkspace.h
index 01d35ccfafa532b7dea5e946679effbded433022..22ac676da171c5e81cc96f8df3e754ef244e93ec 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/SortTableWorkspace.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/SortTableWorkspace.h
@@ -2,7 +2,7 @@
 #define MANTID_DATAHANDLING_SORTTABLEWORKSPACE_H_
 
 #include "MantidKernel/System.h"
-#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/ParallelAlgorithm.h"
 
 namespace Mantid {
 namespace DataHandling {
@@ -30,7 +30,7 @@ namespace DataHandling {
   File change history is stored at: <https://github.com/mantidproject/mantid>
   Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
-class DLLExport SortTableWorkspace : public API::Algorithm {
+class DLLExport SortTableWorkspace : public API::ParallelAlgorithm {
 public:
   const std::string name() const override { return "SortTableWorkspace"; }
   int version() const override;
@@ -45,4 +45,4 @@ private:
 } // namespace DataHandling
 } // namespace Mantid
 
-#endif /* MANTID_DATAHANDLING_SORTTABLEWORKSPACE_H_ */
\ No newline at end of file
+#endif /* MANTID_DATAHANDLING_SORTTABLEWORKSPACE_H_ */
diff --git a/Framework/DataHandling/src/LoadCalFile.cpp b/Framework/DataHandling/src/LoadCalFile.cpp
index 3cb9b75c8ac635b076d15822164973e8aaa3232a..1f5ea0e122f51c12a7be9eb30363a950a73704c8 100644
--- a/Framework/DataHandling/src/LoadCalFile.cpp
+++ b/Framework/DataHandling/src/LoadCalFile.cpp
@@ -359,5 +359,15 @@ bool LoadCalFile::idIsMonitor(Instrument_const_sptr inst, int detID) {
   return (it != monitorList.end());
 }
 
+Parallel::ExecutionMode LoadCalFile::getParallelExecutionMode(
+    const std::map<std::string, Parallel::StorageMode> &storageModes) const {
+  // There is an optional input workspace which may have
+  // StorageMode::Distributed but it is merely used for passing an instrument.
+  // Output should always have StorageMode::Cloned, so we run with
+  // ExecutionMode::Identical.
+  static_cast<void>(storageModes);
+  return Parallel::ExecutionMode::Identical;
+}
+
 } // namespace Mantid
 } // namespace DataHandling
diff --git a/Framework/DataHandling/src/LoadDiffCal.cpp b/Framework/DataHandling/src/LoadDiffCal.cpp
index 7ff915eb1cae71e0ef57896a09fe0f31594d40d0..d4a9929d42162ce62af6035424f32b89817bbef3 100644
--- a/Framework/DataHandling/src/LoadDiffCal.cpp
+++ b/Framework/DataHandling/src/LoadDiffCal.cpp
@@ -458,5 +458,15 @@ void LoadDiffCal::exec() {
   makeCalWorkspace(detids, difc, difa, tzero, dasids, offset, use);
 }
 
+Parallel::ExecutionMode LoadDiffCal::getParallelExecutionMode(
+    const std::map<std::string, Parallel::StorageMode> &storageModes) const {
+  // There is an optional input workspace which may have
+  // StorageMode::Distributed but it is merely used for passing an instrument.
+  // Output should always have StorageMode::Cloned, so we run with
+  // ExecutionMode::Identical.
+  static_cast<void>(storageModes);
+  return Parallel::ExecutionMode::Identical;
+}
+
 } // namespace DataHandling
 } // namespace Mantid
diff --git a/Framework/DataHandling/src/LoadNXSPE.cpp b/Framework/DataHandling/src/LoadNXSPE.cpp
index d481c220d9301101f7badc192830ad67839ad73f..5379e7ac00e67f6d81c52033a824bb8ba7e5e322 100644
--- a/Framework/DataHandling/src/LoadNXSPE.cpp
+++ b/Framework/DataHandling/src/LoadNXSPE.cpp
@@ -338,7 +338,8 @@ void LoadNXSPE::exec() {
       }
     }
   }
-
+  // For NXSPE files generated by Mantid data is actually a distribution.
+  outputWS->setDistribution(true);
   setProperty("OutputWorkspace", outputWS);
 }
 
diff --git a/Framework/DataHandling/src/SaveFocusedXYE.cpp b/Framework/DataHandling/src/SaveFocusedXYE.cpp
index 2375ad1321604940bd32e49e496826ce26f452fd..cd63e3462396de38db1139e1b49ce61caa6cfc5a 100644
--- a/Framework/DataHandling/src/SaveFocusedXYE.cpp
+++ b/Framework/DataHandling/src/SaveFocusedXYE.cpp
@@ -18,8 +18,6 @@ using namespace Mantid::DataHandling;
 // Register the algorithm into the AlgorithmFactory
 DECLARE_ALGORITHM(SaveFocusedXYE)
 
-SaveFocusedXYE::SaveFocusedXYE() : API::Algorithm(), m_headerType(XYE) {}
-
 /**
  * Initialise the algorithm
  */
diff --git a/Framework/DataHandling/src/SaveGSS.cpp b/Framework/DataHandling/src/SaveGSS.cpp
index 66a5ec500d2ed59e5aa232dc95b6054f40b7a46d..8ae01d013e12c6bad427b958b486af2f94f43cee 100644
--- a/Framework/DataHandling/src/SaveGSS.cpp
+++ b/Framework/DataHandling/src/SaveGSS.cpp
@@ -96,9 +96,6 @@ void writeBankHeader(std::stringstream &out, const std::string &bintype,
 }
 } // End of anonymous namespace
 
-// Constructor
-SaveGSS::SaveGSS() : Mantid::API::Algorithm() {}
-
 // Initialise the algorithm
 void SaveGSS::init() {
   declareProperty(Kernel::make_unique<API::WorkspaceProperty<>>(
diff --git a/Framework/DataObjects/CMakeLists.txt b/Framework/DataObjects/CMakeLists.txt
index b9a065d1afe30590005f047b1be54ceff64adc68..473e431a63ef38275c811c3c33180ca2cf021851 100644
--- a/Framework/DataObjects/CMakeLists.txt
+++ b/Framework/DataObjects/CMakeLists.txt
@@ -51,7 +51,7 @@ set ( SRC_FILES
 	src/WorkspaceSingleValue.cpp
 )
 
-set ( SRC_UNITY_IGNORE_FILES 
+set ( SRC_UNITY_IGNORE_FILES
 	src/Workspace2D.cpp
 	src/WorkspaceSingleValue.cpp
 	src/EventWorkspace.cpp
@@ -209,11 +209,13 @@ enable_precompiled_headers( inc/MantidDataObjects/PrecompiledHeader.h SRC_FILES
 # Add the target for this directory
 add_library ( DataObjects ${SRC_FILES} ${INC_FILES})
 # Set the name of the generated library
-set_target_properties ( DataObjects PROPERTIES OUTPUT_NAME MantidDataObjects 
+set_target_properties ( DataObjects PROPERTIES OUTPUT_NAME MantidDataObjects
                                                COMPILE_DEFINITIONS IN_MANTID_DATAOBJECTS )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( DataObjects PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+  set_target_properties(DataObjects PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(DataObjects PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 # Intensive use of templated libaries can cause large objects to be generated. These require
@@ -235,4 +237,3 @@ add_subdirectory ( test )
 ###########################################################################
 
 install ( TARGETS DataObjects ${SYSTEM_PACKAGE_TARGET} DESTINATION ${LIB_DIR} )
-
diff --git a/Framework/DataObjects/inc/MantidDataObjects/FractionalRebinning.h b/Framework/DataObjects/inc/MantidDataObjects/FractionalRebinning.h
index 0d691c8181a6c87cf39bf2b856e85c70817b15a7..480e9035b547223cc449e6b0e1c20f8ec525009e 100644
--- a/Framework/DataObjects/inc/MantidDataObjects/FractionalRebinning.h
+++ b/Framework/DataObjects/inc/MantidDataObjects/FractionalRebinning.h
@@ -52,7 +52,7 @@ namespace FractionalRebinning {
 
 /// Find the intersect region on the output grid
 MANTID_DATAOBJECTS_DLL bool
-getIntersectionRegion(API::MatrixWorkspace_const_sptr outputWS,
+getIntersectionRegion(const std::vector<double> &xAxis,
                       const std::vector<double> &verticalAxis,
                       const Geometry::Quadrilateral &inputQ, size_t &qstart,
                       size_t &qend, size_t &x_start, size_t &x_end);
diff --git a/Framework/DataObjects/src/EventList.cpp b/Framework/DataObjects/src/EventList.cpp
index 7a58b4f70e9f68729e70192a0ed3207359433004..ee8fb7c957b239e4d3750a15f2af53faf8f53a31 100644
--- a/Framework/DataObjects/src/EventList.cpp
+++ b/Framework/DataObjects/src/EventList.cpp
@@ -540,21 +540,17 @@ void EventList::minusHelper(std::vector<T1> &events,
                             const std::vector<T2> &more_events) {
   // Make the end vector big enough in one go (avoids repeated re-allocations).
   events.reserve(events.size() + more_events.size());
-  typename std::vector<T2>::const_iterator itev;
   /* In the event of subtracting in place, calling the end() vector would make
    * it point
    * at the wrong place
    * Using it caused a segault, Ticket #2306.
    * So we cache the end (this speeds up too).
    */
-  auto more_begin = more_events.cbegin();
-  auto more_end = more_events.cend();
-
-  for (itev = more_begin; itev != more_end; itev++) {
+  for (const auto &ev : more_events) {
     // We call the constructor for T1. In the case of WeightedEventNoTime, the
     // pulse time will just be ignored.
-    events.emplace_back(itev->tof(), itev->pulseTime(), itev->weight() * (-1.0),
-                        itev->errorSquared());
+    events.emplace_back(ev.tof(), ev.pulseTime(), ev.weight() * (-1.0),
+                        ev.errorSquared());
   }
 }
 
@@ -1608,7 +1604,7 @@ void EventList::compressEventsParallelHelper(
         events.begin() + (thread + 1) * numPerBlock; // cache for speed
     if (thread == numThreads - 1)
       it_end = events.end();
-    for (; it != it_end; it++) {
+    for (; it != it_end; ++it) {
       if ((it->m_tof - lastTof) <= tolerance) {
         // Carry the error and weight
         weight += it->weight();
@@ -2479,8 +2475,7 @@ void EventList::integrateHelper(std::vector<T> &events, const double minX,
   }
 
   // Sum up all the weights
-  typename std::vector<T>::iterator it;
-  for (it = lowit; it != highit; it++) {
+  for (auto it = lowit; it != highit; ++it) {
     sum += it->weight();
     error += it->errorSquared();
   }
@@ -2589,10 +2584,8 @@ template <class T>
 void EventList::convertTofHelper(std::vector<T> &events,
                                  std::function<double(double)> func) {
   // iterate through all events
-  typename std::vector<T>::iterator itev;
-  auto itev_end = events.end(); // cache for speed
-  for (itev = events.begin(); itev != itev_end; itev++)
-    itev->m_tof = func(itev->m_tof);
+  for (auto &ev : events)
+    ev.m_tof = func(ev.m_tof);
 }
 
 // --------------------------------------------------------------------------
diff --git a/Framework/DataObjects/src/FractionalRebinning.cpp b/Framework/DataObjects/src/FractionalRebinning.cpp
index e44a5244c70760f7affdac7d67b8b60840c3602e..1047b3fd3f5bfd887b7a75bb07886f85065efc52 100644
--- a/Framework/DataObjects/src/FractionalRebinning.cpp
+++ b/Framework/DataObjects/src/FractionalRebinning.cpp
@@ -7,6 +7,7 @@
 #include "MantidKernel/V2D.h"
 
 #include <cmath>
+#include <limits>
 
 namespace Mantid {
 
@@ -18,24 +19,49 @@ namespace DataObjects {
 
 namespace FractionalRebinning {
 
+const double POS_TOLERANCE = 1.e-10;
+
+enum class QuadrilateralType { Rectangle, TrapezoidX, TrapezoidY, General };
+
+/**
+ * Determine the (axis-aligned) quadrilateral type of the input polygon
+ * @param inputQ Input polygon (assumes vertices are in order: ll, ul, ur, lr)
+ */
+QuadrilateralType getQuadrilateralType(const Quadrilateral &inputQ) {
+  const bool inputQHasXParallel =
+      fabs(inputQ[0].Y() - inputQ[3].Y()) < POS_TOLERANCE &&
+      fabs(inputQ[1].Y() - inputQ[2].Y()) < POS_TOLERANCE;
+  const bool inputQHasYParallel =
+      fabs(inputQ[0].X() - inputQ[1].X()) < POS_TOLERANCE &&
+      fabs(inputQ[2].X() - inputQ[3].X()) < POS_TOLERANCE;
+  if (inputQHasXParallel && inputQHasYParallel) {
+    return QuadrilateralType::Rectangle;
+  } else if (inputQHasXParallel) {
+    return QuadrilateralType::TrapezoidX;
+  } else if (inputQHasYParallel) {
+    return QuadrilateralType::TrapezoidY;
+  } else {
+    return QuadrilateralType::General;
+  }
+}
+
 /**
  * Find the possible region of intersection on the output workspace for the
  * given polygon. The given polygon must have a CLOCKWISE winding and the
  * first vertex must be the "lowest left" point.
- * @param outputWS A pointer to the output workspace
+ * @param xAxis A vector containing the output horizontal axis edges
  * @param verticalAxis A vector containing the output vertical axis edges
  * @param inputQ The input polygon (Polygon winding must be clockwise)
  * @param qstart An output giving the starting index in the Q direction
  * @param qend An output giving the end index in the Q direction
  * @param x_start An output giving the start index in the dX direction
  * @param x_end An output giving the end index in the dX direction
- * @return True if an intersecition is possible
+ * @return True if an intersection is possible
  */
-bool getIntersectionRegion(MatrixWorkspace_const_sptr outputWS,
+bool getIntersectionRegion(const std::vector<double> &xAxis,
                            const std::vector<double> &verticalAxis,
                            const Quadrilateral &inputQ, size_t &qstart,
                            size_t &qend, size_t &x_start, size_t &x_end) {
-  const auto &xAxis = outputWS->x(0);
   const double xn_lo(inputQ.minX()), xn_hi(inputQ.maxX());
   const double yn_lo(inputQ.minY()), yn_hi(inputQ.maxY());
 
@@ -69,6 +95,379 @@ bool getIntersectionRegion(MatrixWorkspace_const_sptr outputWS,
   return true;
 }
 
+/**
+ * Computes the output grid bins which intersect the input quad and their
+ * overlapping areas assuming both input and output grids are rectangular
+ * @param xAxis A vector containing the output horizontal axis edges
+ * @param yAxis The output data vertical axis
+ * @param inputQ The input quadrilateral
+ * @param y_start The starting y-axis index
+ * @param y_end The starting y-axis index
+ * @param x_start The starting x-axis index
+ * @param x_end The starting x-axis index
+ * @param areaInfo Output vector of indices and areas of overlapping bins
+ */
+void calcRectangleIntersections(
+    const std::vector<double> &xAxis, const std::vector<double> &yAxis,
+    const Quadrilateral &inputQ, const size_t y_start, const size_t y_end,
+    const size_t x_start, const size_t x_end,
+    std::vector<std::tuple<size_t, size_t, double>> &areaInfo) {
+  std::vector<double> width;
+  width.reserve(x_end - x_start);
+  for (size_t xi = x_start; xi < x_end; ++xi) {
+    const double x0 = (xi == x_start) ? inputQ.minX() : xAxis[xi];
+    const double x1 = (xi == x_end - 1) ? inputQ.maxX() : xAxis[xi + 1];
+    width.push_back(x1 - x0);
+  }
+  for (size_t yi = y_start; yi < y_end; ++yi) {
+    const double y0 = (yi == y_start) ? inputQ.minY() : yAxis[yi];
+    const double y1 = (yi == y_end - 1) ? inputQ.maxY() : yAxis[yi + 1];
+    const double height = y1 - y0;
+    auto width_it = width.begin();
+    for (size_t xi = x_start; xi < x_end; ++xi) {
+      areaInfo.emplace_back(xi, yi, height * (*width_it++));
+    }
+  }
+}
+
+namespace {
+/**
+ * Private function to calculate polygon area directly to avoid the overhead
+ * of initializing a ConvexPolygon instance or a V2D vector. This recursive
+ * implementation uses the shoelace formula but requires the last element to
+ * be the same as the first element. Also note that it returns 2x the area!
+ */
+template <class T> double polyArea(T &) { return 0.; }
+template <class T, class... Ts>
+double polyArea(T &v1, T &v2, Ts &&... vertices) {
+  return v2.X() * v1.Y() - v2.Y() * v1.X() +
+         polyArea(v2, std::forward<Ts>(vertices)...);
+}
+} // Private namespace
+
+/**
+ * Computes the output grid bins which intersect the input quad and their
+ * overlapping areas assuming input quad is a y-axis aligned trapezoid.
+ * @param xAxis A vector containing the output horizontal axis edges
+ * @param yAxis The output data vertical axis
+ * @param inputQ The input quadrilateral
+ * @param y_start The starting y-axis index
+ * @param y_end The starting y-axis index
+ * @param x_start The starting x-axis index
+ * @param x_end The starting x-axis index
+ * @param areaInfo Output vector of indices and areas of overlapping bins
+ */
+void calcTrapezoidYIntersections(
+    const std::vector<double> &xAxis, const std::vector<double> &yAxis,
+    const Quadrilateral &inputQ, const size_t y_start, const size_t y_end,
+    const size_t x_start, const size_t x_end,
+    std::vector<std::tuple<size_t, size_t, double>> &areaInfo) {
+  // The algorithm proceeds as follows:
+  // 1. Determine the left/right bin boundaries on the x- (horizontal)-grid.
+  // 2. Loop along x, for each 1-output-bin wide strip construct a new input Q.
+  // 3. Loop along y, in each bin determine if any vertex of the new input Q
+  //    lies within the bin. Construct an overlap polygon depending on which
+  //    vertices are in. The polygon will include these vertices of inputQ
+  //    and left/right points previously calc.
+  V2D ll = inputQ[0], ul = inputQ[1], ur = inputQ[2], lr = inputQ[3];
+  const double mBot = (lr.Y() - ll.Y()) / (lr.X() - ll.X());
+  const double cBot = ll.Y() - mBot * ll.X();
+  const double mTop = (ur.Y() - ul.Y()) / (ur.X() - ul.X());
+  const double cTop = ul.Y() - mTop * ul.X();
+  // Checks that the x-edges of the input quadrilateral is in the grid
+  // If not, put it on the grid line. Otherwise, get buffer overflow error
+  if (ll.X() < xAxis[x_start]) {
+    ll = V2D(xAxis[x_start], mBot * xAxis[x_start] + cBot);
+    ul = V2D(xAxis[x_start], mTop * xAxis[x_start] + cTop);
+  }
+  if (lr.X() > xAxis[x_end]) {
+    lr = V2D(xAxis[x_end], mBot * xAxis[x_end] + cBot);
+    ur = V2D(xAxis[x_end], mTop * xAxis[x_end] + cTop);
+  }
+
+  const size_t nx = x_end - x_start;
+  const size_t ny = y_end - y_start;
+  const double ll_x(ll.X()), ll_y(ll.Y()), ul_y(ul.Y());
+  const double lr_x(lr.X()), lr_y(lr.Y()), ur_y(ur.Y());
+  // Check if there is only a output single bin and inputQ is fully enclosed
+  if (nx == 1 && ny == 1 &&
+      (ll_y >= yAxis[y_start] && ll_y <= yAxis[y_start + 1]) &&
+      (ul_y >= yAxis[y_start] && ul_y <= yAxis[y_start + 1]) &&
+      (ur_y >= yAxis[y_start] && ur_y <= yAxis[y_start + 1]) &&
+      (lr_y >= yAxis[y_start] && lr_y <= yAxis[y_start + 1])) {
+    areaInfo.emplace_back(x_start, y_start, 0.5 * polyArea(ll, ul, ur, lr, ll));
+    return;
+  }
+
+  // Step 1 - construct the left/right bin lims on the lines of the y-grid.
+  const double NaN = std::numeric_limits<double>::quiet_NaN();
+  const double DBL_EPS = std::numeric_limits<double>::epsilon();
+  std::vector<double> leftLim(nx * (ny + 1), NaN);
+  std::vector<double> rightLim(nx * (ny + 1), NaN);
+  auto x0_it = xAxis.begin() + x_start;
+  auto x1_it = xAxis.begin() + x_end + 1;
+  auto y0_it = yAxis.begin() + y_start;
+  auto y1_it = yAxis.begin() + y_end + 1;
+  const size_t ymax = (y_end == yAxis.size()) ? ny : (ny + 1);
+  if ((mTop >= 0 && mBot >= 0) || (mTop < 0 && mBot < 0)) {
+    // Diagonals in same direction: For a given x-parallel line,
+    // Left limit given by one diagonal, right limit given by other
+    double left_val, right_val;
+    for (size_t yj = 0; yj < ymax; ++yj) {
+      const size_t yjx = yj * nx;
+      // First, find the far left/right limits, given by the inputQ
+      if (mTop >= 0) {
+        left_val = (yAxis[yj + y_start] - cTop) / mTop;
+        right_val = (yAxis[yj + y_start] - cBot) / mBot;
+      } else {
+        left_val = (yAxis[yj + y_start] - cBot) / mBot;
+        right_val = (yAxis[yj + y_start] - cTop) / mTop;
+      }
+      if (left_val < ll_x || left_val > lr_x)
+        left_val = NaN;
+      if (right_val < ll_x || right_val > lr_x)
+        right_val = NaN;
+      auto left_it = std::upper_bound(x0_it, x1_it, left_val);
+      size_t li = 0;
+      if (left_it != x1_it) {
+        li = left_it - x0_it - 1;
+        leftLim[li + yjx] = left_val;
+      } else if (yAxis[yj + y_start] < ul_y) {
+        left_it = x0_it + 1;
+        leftLim[li + yjx] = ll_x;
+      }
+      auto right_it = std::upper_bound(x0_it, x1_it, right_val);
+      if (right_it != x1_it) {
+        rightLim[right_it - x0_it - 1 + yjx] = right_val;
+      } else if (yAxis[yj + y_start] < ur_y) {
+        right_it = x1_it - 1;
+        rightLim[nx - 1 + yjx] = lr_x;
+      }
+      // Now populate the bin boundaries in between
+      if (left_it < right_it && right_it != x1_it) {
+        for (auto x_it = left_it; x_it != right_it; ++x_it) {
+          leftLim[li + 1 + yjx] = *x_it;
+          rightLim[li++ + yjx] = *x_it;
+        }
+      }
+    }
+  } else {
+    // In this case, the diagonals are all on one side or the other.
+    const size_t y2 =
+        std::upper_bound(y0_it, y1_it, (mTop >= 0) ? ll_y : lr_y) - y0_it;
+    const size_t y3 =
+        std::upper_bound(y0_it, y1_it, (mTop >= 0) ? ul_y : ur_y) - y0_it;
+    double val;
+    for (size_t yj = 0; yj < ymax; ++yj) {
+      const size_t yjx = yj * nx;
+      if (yj < y2) {
+        val = (yAxis[yj + y_start] - cBot) / mBot;
+      } else if (yj < y3) {
+        val = (mTop >= 0) ? ll_x : lr_x;
+      } else {
+        val = (yAxis[yj + y_start] - cTop) / mTop;
+      }
+      if (val < ll_x || val > lr_x)
+        val = NaN;
+      auto left_it =
+          (mTop >= 0) ? std::upper_bound(x0_it, x1_it, val) : (x0_it + 1);
+      auto right_it =
+          (mTop >= 0) ? (x1_it - 1) : std::upper_bound(x0_it, x1_it, val);
+      if (left_it == x1_it)
+        left_it--;
+      if (right_it == x1_it)
+        right_it--;
+      size_t li = left_it - x0_it - 1;
+      leftLim[li + yjx] = (mTop >= 0) ? val : ll_x;
+      rightLim[right_it - x0_it - 1 + yjx] = (mTop >= 0) ? lr_x : val;
+      for (auto x_it = left_it; x_it != right_it; x_it++) {
+        leftLim[li + 1 + yjx] = *x_it;
+        rightLim[li++ + yjx] = *x_it;
+      }
+    }
+  }
+
+  // Define constants for bitmask to indicate which vertices are in.
+  const size_t LL_IN = 1 << 1;
+  const size_t UL_IN = 1 << 2;
+  const size_t UR_IN = 1 << 3;
+  const size_t LR_IN = 1 << 4;
+
+  // Step 2 - loop over x, creating one-bin wide strips
+  V2D nll(ll), nul(ul), nur, nlr, l0, r0, l1, r1;
+  double area(0.);
+  ConvexPolygon poly;
+  areaInfo.reserve(nx * ny);
+  size_t vertBits = 0;
+  size_t yj0, yj1;
+  for (size_t xi = x_start; xi < x_end; ++xi) {
+    const size_t xj = xi - x_start;
+    // Define new 1-bin wide input quadrilateral
+    if (xi > x_start) {
+      nll = nlr;
+      nul = nur;
+    }
+    if (xi == (x_end - 1)) {
+      nlr = lr;
+      nur = ur;
+    } else {
+      nlr = V2D(xAxis[xi + 1], mBot * xAxis[xi + 1] + cBot);
+      nur = V2D(xAxis[xi + 1], mTop * xAxis[xi + 1] + cTop);
+    }
+    // Step 3 - loop over y, find poly. area depending on which vertices are in
+    for (size_t yi = y_start; yi < y_end; ++yi) {
+      yj0 = (yi - y_start) * nx;
+      yj1 = (yi - y_start + 1) * nx;
+      // Checks if this bin is completely inside new quadrilateral
+      if (yAxis[yi] > std::max(nll.Y(), nlr.Y()) &&
+          yAxis[yi + 1] < std::min(nul.Y(), nur.Y())) {
+        areaInfo.emplace_back(xi, yi, (nlr.X() - nll.X()) *
+                                          (yAxis[yi + 1] - yAxis[yi]));
+        // Checks if this bin is not completely outside new quadrilateral
+      } else if (yAxis[yi + 1] >= std::min(nll.Y(), nlr.Y()) &&
+                 yAxis[yi] <= std::max(nul.Y(), nur.Y())) {
+        vertBits = 0;
+        if (nll.Y() >= yAxis[yi] && nll.Y() <= yAxis[yi + 1])
+          vertBits |= LL_IN;
+        if (nul.Y() >= yAxis[yi] && nul.Y() <= yAxis[yi + 1])
+          vertBits |= UL_IN;
+        if (nur.Y() >= yAxis[yi] && nur.Y() <= yAxis[yi + 1])
+          vertBits |= UR_IN;
+        if (nlr.Y() >= yAxis[yi] && nlr.Y() <= yAxis[yi + 1])
+          vertBits |= LR_IN;
+        l0 = V2D(leftLim[xj + yj0], yAxis[yi]);
+        r0 = V2D(rightLim[xj + yj0], yAxis[yi]);
+        l1 = V2D(leftLim[xj + yj1], yAxis[yi + 1]);
+        r1 = V2D(rightLim[xj + yj1], yAxis[yi + 1]);
+        // Now calculate the area based on which vertices are in this bin.
+        // Note that a recursive function is used so it can be unrolled and
+        // inlined but it means that the first element has to also be put
+        // into the final position, because otherwise the recursion cannot
+        // implement the shoelace formula (which is circular).
+        switch (vertBits) {
+        // First check cases where no vertices are in this bin
+        case 0:
+          area = polyArea(l1, r1, r0, l0, l1);
+          break;
+        // Now check cases where only one vertex is in. We either have
+        // a triangle, or a pentagon depending on the diagonal slope
+        case LL_IN:
+          if (mBot < 0)
+            area = polyArea(nll, l1, r1, r0, l0, nll);
+          else
+            area = polyArea(nll, l1, r1, nll);
+          break;
+        case UL_IN:
+          if (mTop >= 0)
+            area = polyArea(l1, r1, r0, l0, nul, l1);
+          else
+            area = polyArea(r0, l0, nul, r0);
+          break;
+        case UR_IN:
+          if (mTop < 0)
+            area = polyArea(nur, r0, l0, l1, r1, nur);
+          else
+            area = polyArea(nur, r0, l0, nur);
+          break;
+        case LR_IN:
+          if (mBot >= 0)
+            area = polyArea(r0, l0, l1, r1, nlr, r0);
+          else
+            area = polyArea(l1, r1, nlr, l1);
+          break;
+        // Now check cases where two vertices are in.
+        case (LL_IN | UL_IN):
+          if (mTop >= 0) {
+            if (mBot < 0)
+              area = polyArea(nll, nul, l1, r1, r0, l0, nll);
+            else
+              area = polyArea(nll, nul, l1, r1, nll);
+          } else if (mBot < 0) {
+            area = polyArea(nll, nul, r0, l0, nll);
+          }
+          break;
+        case (UR_IN | LR_IN):
+          if (mBot >= 0) {
+            if (mTop < 0)
+              area = polyArea(nur, nlr, r0, l0, l1, r1, nur);
+            else
+              area = polyArea(nur, nlr, r0, l0, nur);
+          } else if (mTop < 0) {
+            area = polyArea(nur, nlr, l1, r1, nur);
+          }
+          break;
+        case (UL_IN | UR_IN):
+          area = polyArea(nul, nur, r0, l0, nul);
+          break;
+        case (LL_IN | LR_IN):
+          area = polyArea(nlr, nll, l1, r1, nlr);
+          break;
+        case (LL_IN | UR_IN):
+          area = polyArea(nll, l1, r1, nur, r0, l0, nll);
+          break;
+        case (UL_IN | LR_IN):
+          area = polyArea(nul, l1, r1, nlr, r0, l0, nul);
+          break;
+        // Now check cases where three vertices are in.
+        case (UL_IN | UR_IN | LR_IN):
+          area = polyArea(nul, nur, nlr, r0, l0, nul);
+          break;
+        case (LL_IN | UR_IN | LR_IN):
+          area = polyArea(nll, l1, r1, nur, nlr, nll);
+          break;
+        case (LL_IN | UL_IN | LR_IN):
+          area = polyArea(nlr, nll, nul, l1, r1, nlr);
+          break;
+        case (LL_IN | UL_IN | UR_IN):
+          area = polyArea(nul, nur, r0, l0, nll, nul);
+          break;
+        // Finally, the case where all vertices are in.
+        case (LL_IN | UL_IN | UR_IN | LR_IN):
+          area = polyArea(nll, nul, nur, nlr, nll);
+          break;
+        }
+        if (area > DBL_EPS)
+          areaInfo.emplace_back(xi, yi, 0.5 * area);
+      }
+    }
+  }
+}
+
+/**
+ * Computes the output grid bins which intersect the input quad and their
+ * overlapping areas for arbitrary shaped input grids
+ * @param xAxis A vector containing the output horizontal axis edges
+ * @param yAxis The output data vertical axis
+ * @param inputQ The input quadrilateral
+ * @param qstart The starting y-axis index
+ * @param qend The starting y-axis index
+ * @param x_start The starting x-axis index
+ * @param x_end The starting x-axis index
+ * @param areaInfo Output vector of indices and areas of overlapping bins
+ */
+void calcGeneralIntersections(
+    const std::vector<double> &xAxis, const std::vector<double> &yAxis,
+    const Quadrilateral &inputQ, const size_t qstart, const size_t qend,
+    const size_t x_start, const size_t x_end,
+    std::vector<std::tuple<size_t, size_t, double>> &areaInfo) {
+  ConvexPolygon intersectOverlap;
+  for (size_t yi = qstart; yi < qend; ++yi) {
+    const double vlo = yAxis[yi];
+    const double vhi = yAxis[yi + 1];
+    for (size_t xi = x_start; xi < x_end; ++xi) {
+      const V2D ll(xAxis[xi], vlo);
+      const V2D lr(xAxis[xi + 1], vlo);
+      const V2D ur(xAxis[xi + 1], vhi);
+      const V2D ul(xAxis[xi], vhi);
+      const Quadrilateral outputQ(ll, lr, ur, ul);
+      intersectOverlap.clear();
+      if (intersection(outputQ, inputQ, intersectOverlap)) {
+        areaInfo.emplace_back(xi, yi, intersectOverlap.area());
+      }
+    }
+  }
+}
+
 /**
  * Computes the square root of the errors and if the input was a distribution
  * this divides by the new bin-width
@@ -87,11 +486,9 @@ void normaliseOutput(MatrixWorkspace_sptr outputWS,
     for (size_t j = 0; j < outputY.size(); ++j) {
       if (progress)
         progress->report("Calculating errors");
-      const double binWidth = outputX[j + 1] - outputX[j];
       double eValue = std::sqrt(outputE[j]);
-      // Don't do this for a RebinnedOutput workspace. The fractions
-      // take care of such things.
-      if (inputWS->isDistribution() && inputWS->id() != "RebinnedOutput") {
+      if (inputWS->isDistribution()) {
+        const double binWidth = outputX[j + 1] - outputX[j];
         outputY[j] /= binWidth;
         eValue /= binWidth;
       }
@@ -116,18 +513,17 @@ void rebinToOutput(const Quadrilateral &inputQ,
                    MatrixWorkspace_const_sptr inputWS, const size_t i,
                    const size_t j, MatrixWorkspace_sptr outputWS,
                    const std::vector<double> &verticalAxis) {
-  const auto &X = outputWS->x(0);
+  const auto &X = outputWS->x(0).rawData();
   size_t qstart(0), qend(verticalAxis.size() - 1), x_start(0),
       x_end(X.size() - 1);
-  if (!getIntersectionRegion(outputWS, verticalAxis, inputQ, qstart, qend,
-                             x_start, x_end))
+  if (!getIntersectionRegion(X, verticalAxis, inputQ, qstart, qend, x_start,
+                             x_end))
     return;
 
   const auto &inY = inputWS->y(i);
   const auto &inE = inputWS->e(i);
   // It seems to be more efficient to construct this once and clear it before
-  // each calculation
-  // in the loop
+  // each calculation in the loop
   ConvexPolygon intersectOverlap;
   for (size_t y = qstart; y < qend; ++y) {
     const double vlo = verticalAxis[y];
@@ -172,6 +568,8 @@ void rebinToOutput(const Quadrilateral &inputQ,
  * @param i The indexiin the vertical axis direction that inputQ references
  * @param j The index in the horizontal axis direction that inputQ references
  * @param outputWS A pointer to the output workspace that accumulates the data
+ *        Note that the error array of the output workspace contains the
+ *        **variance** and not the errors (standard deviations).
  * @param verticalAxis A vector containing the output vertical axis bin
  * boundaries
  */
@@ -179,58 +577,55 @@ void rebinToFractionalOutput(const Quadrilateral &inputQ,
                              MatrixWorkspace_const_sptr inputWS, const size_t i,
                              const size_t j, RebinnedOutput_sptr outputWS,
                              const std::vector<double> &verticalAxis) {
-  const auto &X = outputWS->x(0);
+  const auto &inX = inputWS->x(i);
+  const auto &inY = inputWS->y(i);
+  const auto &inE = inputWS->e(i);
+  double signal = inY[j];
+  if (std::isnan(signal))
+    return;
+
+  const auto &X = outputWS->x(0).rawData();
   size_t qstart(0), qend(verticalAxis.size() - 1), x_start(0),
       x_end(X.size() - 1);
-  if (!getIntersectionRegion(outputWS, verticalAxis, inputQ, qstart, qend,
-                             x_start, x_end))
+  if (!getIntersectionRegion(X, verticalAxis, inputQ, qstart, qend, x_start,
+                             x_end))
     return;
 
-  const auto &inX = inputWS->x(i);
-  const auto &inY = inputWS->y(i);
-  const auto &inE = inputWS->e(i);
-  // Don't do the overlap removal if already RebinnedOutput.
-  // This wreaks havoc on the data.
-  const bool removeBinWidth(inputWS->isDistribution() &&
-                            inputWS->id() != "RebinnedOutput");
-  // It seems to be more efficient to construct this once and clear it before
-  // each calculation
-  // in the loop
-  ConvexPolygon intersectOverlap;
-  for (size_t yi = qstart; yi < qend; ++yi) {
-    const double vlo = verticalAxis[yi];
-    const double vhi = verticalAxis[yi + 1];
-    for (size_t xi = x_start; xi < x_end; ++xi) {
-      const V2D ll(X[xi], vlo);
-      const V2D lr(X[xi + 1], vlo);
-      const V2D ur(X[xi + 1], vhi);
-      const V2D ul(X[xi], vhi);
-      const Quadrilateral outputQ(ll, lr, ur, ul);
+  // If the input workspace was normalized by the bin width, we need to
+  // recover the original Y value, we do it by 'removing' the bin width
+  double error = inE[j];
+  if (inputWS->isDistribution()) {
+    const double overlapWidth = inX[j + 1] - inX[j];
+    signal *= overlapWidth;
+    error *= overlapWidth;
+  }
 
-      double yValue = inY[j];
-      if (std::isnan(yValue)) {
-        continue;
-      }
-      intersectOverlap.clear();
-      if (intersection(outputQ, inputQ, intersectOverlap)) {
-        const double weight = intersectOverlap.area() / inputQ.area();
-        yValue *= weight;
-        double eValue = inE[j] * weight;
-        if (removeBinWidth) {
-          // If the input workspace was normalized by the bin width, we need to
-          // recover the original Y value, we do it by 'removing' the bin
-          // width
-          const double overlapWidth = inX[j + 1] - inX[j];
-          yValue *= overlapWidth;
-          eValue *= overlapWidth;
-        }
-        eValue *= eValue;
-        PARALLEL_CRITICAL(overlap) {
-          outputWS->mutableY(yi)[xi] += yValue;
-          outputWS->mutableE(yi)[xi] += eValue;
-          outputWS->dataF(yi)[xi] += weight;
-        }
-      }
+  // The intersection overlap algorithm is relatively costly. The outputQ is
+  // defined as rectangular. If the inputQ is is also rectangular or
+  // trapezoidal, a simpler/faster way of calculating the intersection area
+  // of all or some bins can be used.
+  std::vector<std::tuple<size_t, size_t, double>> areaInfo;
+  const double inputQArea = inputQ.area();
+  const QuadrilateralType inputQType = getQuadrilateralType(inputQ);
+  if (inputQType == QuadrilateralType::Rectangle) {
+    calcRectangleIntersections(X, verticalAxis, inputQ, qstart, qend, x_start,
+                               x_end, areaInfo);
+  } else if (inputQType == QuadrilateralType::TrapezoidY) {
+    calcTrapezoidYIntersections(X, verticalAxis, inputQ, qstart, qend, x_start,
+                                x_end, areaInfo);
+  } else {
+    calcGeneralIntersections(X, verticalAxis, inputQ, qstart, qend, x_start,
+                             x_end, areaInfo);
+  }
+
+  for (const auto &ai : areaInfo) {
+    const size_t xi = std::get<0>(ai);
+    const size_t yi = std::get<1>(ai);
+    const double weight = std::get<2>(ai) / inputQArea;
+    PARALLEL_CRITICAL(overlap) {
+      outputWS->mutableY(yi)[xi] += signal * weight;
+      outputWS->mutableE(yi)[xi] += pow(error * weight, 2);
+      outputWS->dataF(yi)[xi] += weight;
     }
   }
 }
diff --git a/Framework/Geometry/CMakeLists.txt b/Framework/Geometry/CMakeLists.txt
index 3b142099c6db0fa9ffbf3a0e0ec27319f7839548..365ea79647e770eebdb981f260e32d71ec4b6474 100644
--- a/Framework/Geometry/CMakeLists.txt
+++ b/Framework/Geometry/CMakeLists.txt
@@ -466,7 +466,9 @@ set_target_properties ( Geometry PROPERTIES OUTPUT_NAME MantidGeometry
                                             COMPILE_DEFINITIONS IN_MANTID_GEOMETRY )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( Geometry PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+  set_target_properties(Geometry PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(Geometry PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 if (ENABLE_OPENCASCADE)
diff --git a/Framework/HistogramData/CMakeLists.txt b/Framework/HistogramData/CMakeLists.txt
index fdaf4a9d8f5e1079f25206c1768493673211e172..a0d9d4b59bce765c95929156f490074bf132301f 100644
--- a/Framework/HistogramData/CMakeLists.txt
+++ b/Framework/HistogramData/CMakeLists.txt
@@ -113,12 +113,15 @@ set_target_properties ( HistogramData PROPERTIES OUTPUT_NAME MantidHistogramData
   COMPILE_DEFINITIONS IN_MANTID_HISTOGRAMDATA )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( HistogramData PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+  set_target_properties(HistogramData PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(HistogramData PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 # Add to the 'Framework' group in VS
 set_property ( TARGET HistogramData PROPERTY FOLDER "MantidFramework" )
 
+target_include_directories ( HistogramData PUBLIC ${Boost_INCLUDE_DIRS})
 target_link_libraries ( HistogramData LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} 
                         ${GSL_LIBRARIES} ${MANTIDLIBS} )
 
diff --git a/Framework/ICat/CMakeLists.txt b/Framework/ICat/CMakeLists.txt
index 9cf18d5c27b9488f7e46e271e3d72b2e8ab45eb4..310192d2c714b1ccdf8d76ca0dda067b8b75a6c9 100644
--- a/Framework/ICat/CMakeLists.txt
+++ b/Framework/ICat/CMakeLists.txt
@@ -85,13 +85,15 @@ enable_precompiled_headers( inc/MantidICat/PrecompiledHeader.h SRC_FILES )
 # Add the target for this directory
 add_library ( ICat ${SRC_FILES} ${INC_FILES})
 # Set the name of the generated library
-set_target_properties ( ICat PROPERTIES OUTPUT_NAME MantidICat 
+set_target_properties ( ICat PROPERTIES OUTPUT_NAME MantidICat
   COMPILE_DEFINITIONS IN_MANTID_ICAT
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( ICat PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
-endif () 
+  set_target_properties(ICat PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(ICat PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
+endif ()
 
 # Add to the 'Framework' group in VS
 set_property ( TARGET ICat PROPERTY FOLDER "MantidFramework" )
diff --git a/Framework/Indexing/CMakeLists.txt b/Framework/Indexing/CMakeLists.txt
index 33fc4cf478c8e7f3b4bc0bbc1c926f33bde33f77..ab99025def540a58d9f039f1cb1b3c4d69e17256 100644
--- a/Framework/Indexing/CMakeLists.txt
+++ b/Framework/Indexing/CMakeLists.txt
@@ -66,12 +66,15 @@ set_target_properties ( Indexing PROPERTIES OUTPUT_NAME MantidIndexing
   COMPILE_DEFINITIONS IN_MANTID_INDEXING )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( Indexing PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+  set_target_properties(Indexing PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(Indexing PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 # Add to the 'Framework' group in VS
 set_property ( TARGET Indexing PROPERTY FOLDER "MantidFramework" )
 
+target_include_directories ( Indexing PUBLIC ${Boost_INCLUDE_DIRS})
 target_link_libraries ( Indexing LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} ${MANTIDLIBS}
   Parallel
   )
diff --git a/Framework/Kernel/CMakeLists.txt b/Framework/Kernel/CMakeLists.txt
index 56ab06f8885661c12027d8c84fda57409fc4032a..830960caf7ceb49f36451c24efd3a80307594736 100644
--- a/Framework/Kernel/CMakeLists.txt
+++ b/Framework/Kernel/CMakeLists.txt
@@ -489,7 +489,9 @@ target_include_directories ( Kernel SYSTEM PUBLIC ${Boost_INCLUDE_DIRS} ${POCO_I
   PRIVATE ${NEXUS_INCLUDE_DIR} ${GSL_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR} )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( Kernel PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+  set_target_properties(Kernel PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(Kernel PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 # Add to the 'Framework' group in VS
diff --git a/Framework/Kernel/inc/MantidKernel/VMD.h b/Framework/Kernel/inc/MantidKernel/VMD.h
index ec5f594c445800288f9f79a58f28841949ef1126..4cfce09de7a64fb4f59405af31167f6881bb2698 100644
--- a/Framework/Kernel/inc/MantidKernel/VMD.h
+++ b/Framework/Kernel/inc/MantidKernel/VMD.h
@@ -64,7 +64,6 @@ public:
   TYPE &operator[](const size_t index);
   const TYPE *getBareArray() const;
   std::string toString(const std::string &separator = " ") const;
-  template <class T> std::vector<T> toVector() const;
   bool operator==(const VMDBase &v) const;
   bool operator!=(const VMDBase &v) const;
   VMDBase operator+(const VMDBase &v) const;
diff --git a/Framework/Kernel/src/ConfigService.cpp b/Framework/Kernel/src/ConfigService.cpp
index 0a789fa2b418aacd0bacd75480028a6ca8a578ac..53385cfa7e4e2200f622af62cf69d2851f8b3946 100644
--- a/Framework/Kernel/src/ConfigService.cpp
+++ b/Framework/Kernel/src/ConfigService.cpp
@@ -127,20 +127,6 @@ public:
     m_pPtr = static_cast<T *>(this);
   }
 
-  /// Copy constructor
-  WrappedObject(const WrappedObject<T> &A) : T(A) {
-    m_pPtr = static_cast<T *>(this);
-  }
-
-  /// Overloaded = operator sets the pointer to the wrapped class
-  /// and copies over the contents
-  WrappedObject<T> &operator=(const WrappedObject<T> &rhs) {
-    if (this != &rhs) {
-      m_pPtr = static_cast<T *>(this);
-      *m_pPtr = rhs;
-    }
-    return *this;
-  }
   /// Overloaded * operator returns the wrapped object pointer
   const T &operator*() const { return *m_pPtr; }
   /// Overloaded * operator returns the wrapped object pointer
diff --git a/Framework/Kernel/src/Matrix.cpp b/Framework/Kernel/src/Matrix.cpp
index cdd07279dd59db457f98450a56ef920eb055f2c0..acb51865083949be4fe50bbbdb6ea71e3c2bf3b3 100644
--- a/Framework/Kernel/src/Matrix.cpp
+++ b/Framework/Kernel/src/Matrix.cpp
@@ -1454,9 +1454,8 @@ std::vector<T> Matrix<T>::toRotation()
   }
   // step 2: get scales and rescsale the matrix
   std::vector<T> scale(this->m_numRows);
-  T currentScale;
   for (size_t i = 0; i < this->m_numColumns; ++i) {
-    currentScale = T(0.);
+    T currentScale{0};
     for (size_t j = 0; j < this->m_numRows; ++j)
       currentScale += (m_rawData[j][i] * m_rawData[j][i]);
     currentScale = static_cast<T>(sqrt(static_cast<double>(currentScale)));
diff --git a/Framework/Kernel/src/MultiFileNameParser.cpp b/Framework/Kernel/src/MultiFileNameParser.cpp
index bed6ddf43b7e8fc73c8b259abaf1b6d3ccdb9c36..b313fccc33f10f5ecd0b58cf2d6b11f44fb18f6b 100644
--- a/Framework/Kernel/src/MultiFileNameParser.cpp
+++ b/Framework/Kernel/src/MultiFileNameParser.cpp
@@ -10,10 +10,10 @@
 #include <numeric>
 #include <sstream>
 
-#include <boost/regex.hpp>
 #include <boost/algorithm/string.hpp>
-#include <boost/lexical_cast.hpp>
 #include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/regex.hpp>
 
 namespace Mantid {
 namespace Kernel {
@@ -39,14 +39,18 @@ const std::string STEP_RANGE =
     "(" + SINGLE + COLON + SINGLE + COLON + SINGLE + ")";
 const std::string ADD_LIST = "(" + SINGLE + "(" + PLUS + SINGLE + ")+" + ")";
 const std::string ADD_RANGE = "(" + SINGLE + MINUS + SINGLE + ")";
+const std::string ADD_RANGES = "(" + ADD_RANGE + PLUS + ADD_RANGE + ")";
+const std::string ADD_SINGLE_TO_RANGE = "(" + SINGLE + PLUS + ADD_RANGE + ")";
+const std::string ADD_RANGE_TO_SINGLE = "(" + ADD_RANGE + PLUS + SINGLE + ")";
 const std::string ADD_STEP_RANGE =
     "(" + SINGLE + MINUS + SINGLE + COLON + SINGLE + ")";
 
-const std::string ANY = "(" + ADD_STEP_RANGE + "|" + ADD_RANGE + "|" +
-                        ADD_LIST + "|" + STEP_RANGE + "|" + RANGE + "|" +
-                        SINGLE + ")";
+const std::string ANY = "(" + ADD_STEP_RANGE + "|" + ADD_RANGES + "|" +
+                        ADD_SINGLE_TO_RANGE + "|" + ADD_RANGE_TO_SINGLE + "|" +
+                        ADD_RANGE + "|" + ADD_LIST + "|" + STEP_RANGE + "|" +
+                        RANGE + "|" + SINGLE + ")";
 const std::string LIST = "(" + ANY + "(" + COMMA + ANY + ")*" + ")";
-}
+} // namespace Regexs
 
 /////////////////////////////////////////////////////////////////////////////
 // Forward declarations.
@@ -84,7 +88,7 @@ struct RangeContainsRun {
 std::string toString(const RunRangeList &runRangeList);
 std::string &accumulateString(std::string &output,
                               std::pair<unsigned int, unsigned int> runRange);
-}
+} // namespace
 
 /////////////////////////////////////////////////////////////////////////////
 // Scoped, global functions.
@@ -508,6 +512,31 @@ parseToken(std::vector<std::vector<unsigned int>> &parsedRuns,
   else if (matchesFully(token, Regexs::ADD_RANGE)) {
     runs = generateRange(rangeDetails[0], rangeDetails[1], 1, true);
   }
+  // E.g. "2018-2020+2022-2023"
+  else if (matchesFully(token, Regexs::ADD_RANGES)) {
+    const auto lhs = generateRange(rangeDetails[0], rangeDetails[1], 1, true);
+    const auto rhs = generateRange(rangeDetails[2], rangeDetails[3], 1, true);
+    runs.resize(1);
+    auto it = std::back_inserter(runs.front());
+    std::copy(lhs.front().cbegin(), lhs.front().cend(), it);
+    std::copy(rhs.front().cbegin(), rhs.front().cend(), it);
+  }
+  // E.g. "2018+2020-2023"
+  else if (matchesFully(token, Regexs::ADD_SINGLE_TO_RANGE)) {
+    runs.resize(1);
+    runs.front().emplace_back(rangeDetails[0]);
+    const auto rhs = generateRange(rangeDetails[1], rangeDetails[2], 1, true);
+    auto it = std::back_inserter(runs.front());
+    std::copy(rhs.front().cbegin(), rhs.front().cend(), it);
+  }
+  // E.g. "2018-2020+2023"
+  else if (matchesFully(token, Regexs::ADD_RANGE_TO_SINGLE)) {
+    runs.resize(1);
+    const auto lhs = generateRange(rangeDetails[0], rangeDetails[1], 1, true);
+    auto it = std::back_inserter(runs.front());
+    std::copy(lhs.front().cbegin(), lhs.front().cend(), it);
+    runs.front().emplace_back(rangeDetails[2]);
+  }
   // E.g. "2012-2020:4".
   else if (matchesFully(token, Regexs::ADD_STEP_RANGE)) {
     runs =
diff --git a/Framework/Kernel/src/VMD.cpp b/Framework/Kernel/src/VMD.cpp
index a3925ff1987e8f9f9cde6306a1fbbd1e399e507a..1cafd0aa50385e128b228a4d303761161239a9ac 100644
--- a/Framework/Kernel/src/VMD.cpp
+++ b/Framework/Kernel/src/VMD.cpp
@@ -288,19 +288,6 @@ std::string VMDBase<TYPE>::toString(const std::string &separator) const {
   return mess.str();
 }
 
-/** Get the vector as a vector
- * @tparam T :: type to convert to (double/float)
- * @return the vector as a std::vector
- */
-template <typename TYPE>
-template <class T>
-std::vector<T> VMDBase<TYPE>::toVector() const {
-  typename std::vector<T> out;
-  for (size_t d = 0; d < nd; d++)
-    out.push_back(T(data[d]));
-  return out;
-}
-
 /** Equals operator with tolerance factor
   @param v :: VMDBase for comparison
   @return true if the items are equal
diff --git a/Framework/Kernel/test/MultiFileNameParserTest.h b/Framework/Kernel/test/MultiFileNameParserTest.h
index 8780310f36c6c9326028c07e0e0ea7f2f3870562..32038147bfbd0fee1d20aa96afe9431743c8c541 100644
--- a/Framework/Kernel/test/MultiFileNameParserTest.h
+++ b/Framework/Kernel/test/MultiFileNameParserTest.h
@@ -170,10 +170,39 @@ public:
     TS_ASSERT_EQUALS(result[9][0], 4);
   }
 
+  void test_singlePlusAddRange() {
+    ParsedRuns result = parseMultiRunString("1+3-6");
+
+    TS_ASSERT_EQUALS(result[0][0], 1);
+    TS_ASSERT_EQUALS(result[0][1], 3);
+    TS_ASSERT_EQUALS(result[0][2], 4);
+    TS_ASSERT_EQUALS(result[0][3], 5);
+    TS_ASSERT_EQUALS(result[0][4], 6);
+  }
+
+  void test_addRangePlusSingle() {
+    ParsedRuns result = parseMultiRunString("1-3+5");
+
+    TS_ASSERT_EQUALS(result[0][0], 1);
+    TS_ASSERT_EQUALS(result[0][1], 2);
+    TS_ASSERT_EQUALS(result[0][2], 3);
+    TS_ASSERT_EQUALS(result[0][3], 5);
+  }
+
   void test_nothingReturnedWhenPassedEmptyString() {
     TS_ASSERT_EQUALS(parseMultiRunString("").size(), 0);
   }
 
+  void test_sumTwoAddRanges() {
+    ParsedRuns result = parseMultiRunString("1-2+4-6");
+
+    TS_ASSERT_EQUALS(result[0][0], 1);
+    TS_ASSERT_EQUALS(result[0][1], 2);
+    TS_ASSERT_EQUALS(result[0][2], 4);
+    TS_ASSERT_EQUALS(result[0][3], 5);
+    TS_ASSERT_EQUALS(result[0][4], 6);
+  }
+
   void test_errorThrownWhenPassedUnexpectedChar() {
     std::string message =
         "Non-numeric or otherwise unaccetable character(s) detected.";
diff --git a/Framework/Kernel/test/TypedValidatorTest.h b/Framework/Kernel/test/TypedValidatorTest.h
index 1f30741db24eb6ec59a98131c1977ab74200e4da..086cd33f0464613496e4bbc55350e5a82fd87d7a 100644
--- a/Framework/Kernel/test/TypedValidatorTest.h
+++ b/Framework/Kernel/test/TypedValidatorTest.h
@@ -7,7 +7,6 @@
 #include <boost/make_shared.hpp>
 #include <cxxtest/TestSuite.h>
 
-namespace {
 #define DECLARE_TEST_VALIDATOR(ClassName, HeldType)                            \
   class ClassName : public Mantid::Kernel::TypedValidator<HeldType> {          \
   public:                                                                      \
@@ -33,7 +32,6 @@ private:
 };
 DECLARE_TEST_VALIDATOR(DataItemSptrTypedValidator,
                        boost::shared_ptr<FakeDataItem>)
-}
 
 class TypedValidatorTest : public CxxTest::TestSuite {
 public:
diff --git a/Framework/LiveData/CMakeLists.txt b/Framework/LiveData/CMakeLists.txt
index c4c01fcc85f397234de6c3ef0cf57aa2d1a8db29..092a44aa84b6b2322d45990434ebe7bdd2ecd308 100644
--- a/Framework/LiveData/CMakeLists.txt
+++ b/Framework/LiveData/CMakeLists.txt
@@ -106,7 +106,9 @@ set_target_properties ( LiveData PROPERTIES OUTPUT_NAME MantidLiveData
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( LiveData PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
+  set_target_properties(LiveData PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(LiveData PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 # Add to the 'Framework' group in VS
diff --git a/Framework/MDAlgorithms/CMakeLists.txt b/Framework/MDAlgorithms/CMakeLists.txt
index eaabe33c98e4c9bf03d755364a3cf19cc57a982f..20b3cfbe911f6b4164ca141a535f12f669ca97f6 100644
--- a/Framework/MDAlgorithms/CMakeLists.txt
+++ b/Framework/MDAlgorithms/CMakeLists.txt
@@ -400,13 +400,15 @@ enable_precompiled_headers( inc/MantidMDAlgorithms/PrecompiledHeader.h SRC_FILES
 # Add the target for this directory
 add_library ( MDAlgorithms ${SRC_FILES} ${INC_FILES})
 # Set the name of the generated library
-set_target_properties ( MDAlgorithms PROPERTIES OUTPUT_NAME MantidMDAlgorithms 
+set_target_properties ( MDAlgorithms PROPERTIES OUTPUT_NAME MantidMDAlgorithms
     COMPILE_DEFINITIONS IN_MANTID_MDALGORITHMS
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-    set_target_properties ( MDAlgorithms PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
-endif () 
+  set_target_properties(MDAlgorithms PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(MDAlgorithms PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
+endif ()
 
 # Add to the 'Framework' group in VS
 set_property ( TARGET MDAlgorithms PROPERTY FOLDER "MantidFramework" )
diff --git a/Framework/MDAlgorithms/src/FindPeaksMD.cpp b/Framework/MDAlgorithms/src/FindPeaksMD.cpp
index 0258468f693e84f30d30805d1a2f07c96adc2474..931cb45d3d9e3b72c006e247dc0e8de8cc13349f 100644
--- a/Framework/MDAlgorithms/src/FindPeaksMD.cpp
+++ b/Framework/MDAlgorithms/src/FindPeaksMD.cpp
@@ -439,7 +439,7 @@ void FindPeaksMD::findPeaks(typename MDEventWorkspace<MDE, nd>::sptr ws) {
     // e.g. from highest density down to lowest density.
     typename std::multimap<double, boxPtr>::reverse_iterator it2;
     auto it2_end = sortedBoxes.rend();
-    for (it2 = sortedBoxes.rbegin(); it2 != it2_end; it2++) {
+    for (it2 = sortedBoxes.rbegin(); it2 != it2_end; ++it2) {
       signal_t density = it2->first;
       boxPtr box = it2->second;
 #ifndef MDBOX_TRACK_CENTROID
diff --git a/Framework/Nexus/CMakeLists.txt b/Framework/Nexus/CMakeLists.txt
index b4ec93352419ee8dd2309e96477993f48fd183fe..6a4b974cfdbfd8fd644a7f691c32d07a2026fe61 100644
--- a/Framework/Nexus/CMakeLists.txt
+++ b/Framework/Nexus/CMakeLists.txt
@@ -25,12 +25,14 @@ add_definitions ( -DIN_NEXUS_CPP_LIBRARY )
 # Add the target for this directory
 add_library ( Nexus ${SRC_FILES} ${INC_FILES})
 # Set the name of the generated library
-set_target_properties ( Nexus PROPERTIES OUTPUT_NAME MantidNexus 
+set_target_properties ( Nexus PROPERTIES OUTPUT_NAME MantidNexus
                                          COMPILE_DEFINITIONS IN_MANTID_NEXUS )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( Nexus PROPERTIES INSTALL_RPATH "@loader_path/../MacOS" )
-endif () 
+  set_target_properties(Nexus PROPERTIES INSTALL_RPATH "@loader_path/../MacOS" )
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(Nexus PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
+endif ()
 
 # Add to the 'Framework' group in VS
 set_property ( TARGET Nexus PROPERTY FOLDER "MantidFramework" )
diff --git a/Framework/Nexus/src/NexusFileIO.cpp b/Framework/Nexus/src/NexusFileIO.cpp
index e373e252dbb4b65913fe202969850358a9c6a311..5c7f89e87b578b07f4cc6e8cc13a78fa8ad69a9d 100644
--- a/Framework/Nexus/src/NexusFileIO.cpp
+++ b/Framework/Nexus/src/NexusFileIO.cpp
@@ -838,12 +838,9 @@ void NexusFileIO::writeEventListData(std::vector<T> events, bool writeTOF,
   auto errorSquareds = new double[num];
   auto pulsetimes = new int64_t[num];
 
-  typename std::vector<T>::const_iterator it;
-  typename std::vector<T>::const_iterator it_end = events.end();
   size_t i = 0;
-
   // Fill the C-arrays with the fields from all the events, as requested.
-  for (it = events.begin(); it != it_end; it++) {
+  for (auto it = events.cbegin(); it != events.cend(); ++it) {
     if (writeTOF)
       tofs[i] = it->tof();
     if (writePulsetime)
@@ -852,7 +849,7 @@ void NexusFileIO::writeEventListData(std::vector<T> events, bool writeTOF,
       weights[i] = it->weight();
     if (writeError)
       errorSquareds[i] = it->errorSquared();
-    i++;
+    ++i;
   }
 
   // Write out all the required arrays.
diff --git a/Framework/Parallel/CMakeLists.txt b/Framework/Parallel/CMakeLists.txt
index 24668373bbe2a0674a3d95a2a4ba650297fc7cee..61602eb53605cbfeaf09a34a2bc670bb7c173520 100644
--- a/Framework/Parallel/CMakeLists.txt
+++ b/Framework/Parallel/CMakeLists.txt
@@ -57,13 +57,15 @@ set_target_properties ( Parallel PROPERTIES OUTPUT_NAME MantidParallel
   COMPILE_DEFINITIONS IN_MANTID_PARALLEL )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( Parallel PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+  set_target_properties(Parallel PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(Parallel PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 # Add to the 'Framework' group in VS
 set_property ( TARGET Parallel PROPERTY FOLDER "MantidFramework" )
 
-target_include_directories ( Parallel SYSTEM PRIVATE ${HDF5_INCLUDE_DIRS} )
+target_include_directories ( Parallel SYSTEM PRIVATE ${HDF5_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
 target_link_libraries ( Parallel LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME}
                         ${GSL_LIBRARIES} ${MANTIDLIBS} ${HDF5_LIBRARIES} )
 
diff --git a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/GetPointer.h b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/GetPointer.h
index 1f6475a98b1e10f81ed806643ada714ef81ce591..d70f13eae3594e8528a11bac084b8c52ab624fdf 100644
--- a/Framework/PythonInterface/inc/MantidPythonInterface/kernel/GetPointer.h
+++ b/Framework/PythonInterface/inc/MantidPythonInterface/kernel/GetPointer.h
@@ -1,7 +1,8 @@
 #ifndef MANTID_PYTHONINTERFACE_KERNEL_GETPOINTER_H_
 #define MANTID_PYTHONINTERFACE_KERNEL_GETPOINTER_H_
 
-#if defined(_MSC_FULL_VER) && _MSC_FULL_VER > 190023918
+#if defined(_MSC_FULL_VER) && _MSC_FULL_VER > 190023918 &&                     \
+    _MSC_FULL_VER < 191125506
 // Visual Studio Update 3 refuses to link boost python exports that use
 // register_ptr_to_python with a virtual base. This is a work around
 #define GET_POINTER_SPECIALIZATION(TYPE)                                       \
diff --git a/Framework/PythonInterface/mantid/_plugins/CMakeLists.txt b/Framework/PythonInterface/mantid/_plugins/CMakeLists.txt
index 8425cf15d05172774f4b2d2c360f500c883b91bf..8a7afcaeb06c630e0aa29d1b29fb259b95e9414a 100644
--- a/Framework/PythonInterface/mantid/_plugins/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/_plugins/CMakeLists.txt
@@ -67,6 +67,9 @@ target_link_libraries ( PythonCurveFittingModule LINK_PRIVATE ${TCMALLOC_LIBRARI
 if (OSX_VERSION VERSION_GREATER 10.8)
   set_target_properties( PythonCurveFittingModule PROPERTIES
                          INSTALL_RPATH "@loader_path/../../../MacOS;@loader_path/../kernel/;@loader_path/../geometry/;@loader_path/../api/")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties( PythonCurveFittingModule PROPERTIES
+                         INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR};\$ORIGIN/../kernel/;\$ORIGIN/../geometry/;\$ORIGIN/../api/")
 endif ()
 ###########################################################################
 # Installation settings
diff --git a/Framework/PythonInterface/mantid/api/CMakeLists.txt b/Framework/PythonInterface/mantid/api/CMakeLists.txt
index e9f764bd072c2655d72a593b3e793562ab53a78b..ceff8f34f5952a884bdab6b4a3647aab6589758b 100644
--- a/Framework/PythonInterface/mantid/api/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/api/CMakeLists.txt
@@ -156,7 +156,11 @@ target_link_libraries ( PythonAPIModule LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTI
             ${Boost_LIBRARIES} )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties( PythonAPIModule PROPERTIES INSTALL_RPATH "@loader_path/../../../MacOS;@loader_path/../kernel/;@loader_path/../geometry/")
+  set_target_properties( PythonAPIModule PROPERTIES
+                         INSTALL_RPATH "@loader_path/../../../MacOS;@loader_path/../kernel/;@loader_path/../geometry/")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties( PythonAPIModule PROPERTIES
+                         INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR};\$ORIGIN/../kernel/;\$ORIGIN/../geometry/")
 endif ()
 ###########################################################################
 # Installation settings
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp b/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp
index 7e132662437cd9b759b352623aa24684a488dc16..0406931e6735830975e7cd13522739a56909c6a0 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp
@@ -209,6 +209,9 @@ void export_IFunction() {
            (void (IFunction::*)(const std::string &)) & IFunction::removeTie,
            (arg("self"), arg("name")), "Remove the tie of the named parameter")
 
+      .def("getTies", &IFunction::writeTies, arg("self"),
+           "Returns the list of current ties as a string")
+
       .def("addConstraints", &IFunction::addConstraints,
            addConstraints_Overloads(
                (arg("self"), arg("constraints"), arg("isDefault")),
@@ -218,6 +221,9 @@ void export_IFunction() {
            (arg("self"), arg("name")),
            "Remove the constraint on the named parameter")
 
+      .def("getConstraints", &IFunction::writeConstraints, arg("self"),
+           "Returns the list of current constraints as a string")
+
       .def("getNumberDomains", &IFunction::getNumberDomains, (arg("self")),
            "Get number of domains of a multi-domain function")
 
diff --git a/Framework/PythonInterface/mantid/dataobjects/CMakeLists.txt b/Framework/PythonInterface/mantid/dataobjects/CMakeLists.txt
index fd060ab0bf17b35f0202f07bcf17b689160fcbb6..c62dff44d9f4bfff8a37f70c17122a61324f4f97 100644
--- a/Framework/PythonInterface/mantid/dataobjects/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/dataobjects/CMakeLists.txt
@@ -84,6 +84,9 @@ target_link_libraries ( PythonDataObjectsModule LINK_PRIVATE ${TCMALLOC_LIBRARIE
 if (OSX_VERSION VERSION_GREATER 10.8)
   set_target_properties( PythonDataObjectsModule PROPERTIES
                          INSTALL_RPATH "@loader_path/../../../MacOS;@loader_path/../kernel/;@loader_path/../geometry/;@loader_path/../api/")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties( PythonDataObjectsModule PROPERTIES
+                         INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR};\$ORIGIN/../kernel/;\$ORIGIN/../geometry/;\$ORIGIN/../api/")
 endif ()
 ###########################################################################
 # Installation settings
diff --git a/Framework/PythonInterface/mantid/fitfunctions.py b/Framework/PythonInterface/mantid/fitfunctions.py
index 7aff83761ed778625b9a79cc817f5b677078a133..9ab8d0bdaccb09a2a0bb4b694dedd1532d9513e4 100644
--- a/Framework/PythonInterface/mantid/fitfunctions.py
+++ b/Framework/PythonInterface/mantid/fitfunctions.py
@@ -746,6 +746,10 @@ def _create_wrapper_function(name):
     wrapper_function.__name__ = name
     globals()[name] = wrapper_function
 
-fnames = FunctionFactory.getFunctionNames()
-for i, val in enumerate(fnames):
-    _create_wrapper_function(val)
+
+def _create_wrappers_for_all_fit_functions():
+    for name in FunctionFactory.getFunctionNames():
+        _create_wrapper_function(name)
+
+
+_create_wrappers_for_all_fit_functions()
diff --git a/Framework/PythonInterface/mantid/geometry/CMakeLists.txt b/Framework/PythonInterface/mantid/geometry/CMakeLists.txt
index fa91f99ab62d6d008cb92cbf1c54f746d3cffa6e..7034d098719d25ec1f94e1ce2e685de3c94b29db 100644
--- a/Framework/PythonInterface/mantid/geometry/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/geometry/CMakeLists.txt
@@ -89,12 +89,16 @@ target_link_libraries ( PythonGeometryModule LINK_PRIVATE ${TCMALLOC_LIBRARIES_L
             ${PYTHON_LIBRARIES}
             ${POCO_LIBRARIES}
             ${Boost_LIBRARIES}
-			${TBB_LIBRARIES} 
+			${TBB_LIBRARIES}
 			${TBB_MALLOC_LIBRARIES}
             )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( PythonGeometryModule PROPERTIES INSTALL_RPATH "@loader_path/../../../MacOS;@loader_path/../kernel/")
+  set_target_properties ( PythonGeometryModule PROPERTIES
+                          INSTALL_RPATH "@loader_path/../../../MacOS;@loader_path/../kernel/")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties ( PythonGeometryModule PROPERTIES
+                         INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR};\$ORIGIN/../kernel/")
 endif ()
 
 ###########################################################################
diff --git a/Framework/PythonInterface/mantid/kernel/CMakeLists.txt b/Framework/PythonInterface/mantid/kernel/CMakeLists.txt
index 9b540e18692ffd5dfc8ece5dab2c773d49896332..b7f0589931fca6b907c0ed4b3037ae394ee9a4fd 100644
--- a/Framework/PythonInterface/mantid/kernel/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/kernel/CMakeLists.txt
@@ -200,7 +200,9 @@ target_link_libraries ( PythonKernelModule LINK_PRIVATE ${TCMALLOC_LIBRARIES_LIN
 			${TBB_MALLOC_LIBRARIES} )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( PythonKernelModule PROPERTIES INSTALL_RPATH "@loader_path/../../../MacOS")
+  set_target_properties(PythonKernelModule PROPERTIES INSTALL_RPATH "@loader_path/../../../MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(PythonKernelModule PROPERTIES INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR}")
 endif ()
 
 ###########################################################################
diff --git a/Framework/PythonInterface/mantid/plots/CMakeLists.txt b/Framework/PythonInterface/mantid/plots/CMakeLists.txt
index babdb8d2927f334552ae0033401c1915b4fb5017..935a2498aac8a02f55c60b5280eea1643fafa603 100644
--- a/Framework/PythonInterface/mantid/plots/CMakeLists.txt
+++ b/Framework/PythonInterface/mantid/plots/CMakeLists.txt
@@ -1,6 +1,8 @@
 set ( PY_FILES
   __init__.py
-  functions.py
+  helperfunctions.py
+  plotfunctions.py
+  plotfunctions3D.py
 )
 
 
diff --git a/Framework/PythonInterface/mantid/plots/__init__.py b/Framework/PythonInterface/mantid/plots/__init__.py
index dc51b532d6c02c5d04dc8e8c32d86387316d95a4..41b0282b48a7a6db85a34e53308a0ccdf78ba10a 100644
--- a/Framework/PythonInterface/mantid/plots/__init__.py
+++ b/Framework/PythonInterface/mantid/plots/__init__.py
@@ -22,15 +22,17 @@ Functionality for unpacking mantid objects for plotting with matplotlib.
 # of the main package.
 from __future__ import (absolute_import, division, print_function)
 
-import mantid.plots.functions
-from mantid.dataobjects import EventWorkspace,Workspace2D,MDHistoWorkspace
+import mantid.kernel
+import mantid.plots.plotfunctions
+import mantid.plots.plotfunctions3D
 from matplotlib.axes import Axes
 from matplotlib.projections import register_projection
-import mantid.kernel
+from mpl_toolkits.mplot3d.axes3d import Axes3D
+
 
 class MantidAxes(Axes):
     '''
-    This class defines the **mantid** projection. One chooses
+    This class defines the **mantid** projection for 2d plotting. One chooses
     this projection using::
 
         import matplotlib.pyplot as plt
@@ -47,14 +49,9 @@ class MantidAxes(Axes):
     The mantid projection allows replacing the array objects with mantid workspaces.
     '''
 
-    name='mantid'
-
-    def validate_args(self,*args):
-        return len(args)>0 and (isinstance(args[0],EventWorkspace) or
-                                isinstance(args[0],Workspace2D) or
-                                isinstance(args[0],MDHistoWorkspace))
+    name = 'mantid'
 
-    def plot(self,*args,**kwargs):
+    def plot(self, *args, **kwargs):
         '''
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.plot` for arrays,
@@ -71,15 +68,15 @@ class MantidAxes(Axes):
             ax.plot(x,y,'bo')                 #for arrays
             fig.show()
 
-        For keywords related to workspaces, see :func:`mantid.plots.functions.plot`.
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.plot`.
         '''
-        if self.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.functions')     
-            return mantid.plots.functions.plot(self,*args,**kwargs)
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
+            return mantid.plots.plotfunctions.plot(self, *args, **kwargs)
         else:
-            return Axes.plot(self,*args,**kwargs)
+            return Axes.plot(self, *args, **kwargs)
 
-    def scatter(self,*args,**kwargs):
+    def scatter(self, *args, **kwargs):
         '''
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.scatter` for arrays,
@@ -96,15 +93,15 @@ class MantidAxes(Axes):
             ax.scatter(x,y,'bo')                 #for arrays
             fig.show()
         
-        For keywords related to workspaces, see :func:`mantid.plots.functions.scatter`             
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.scatter`
         '''
-        if self.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.functions')     
-            return mantid.plots.functions.scatter(self,*args,**kwargs)
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
+            return mantid.plots.plotfunctions.scatter(self, *args, **kwargs)
         else:
-            return Axes.scatter(self,*args,**kwargs)
+            return Axes.scatter(self, *args, **kwargs)
 
-    def errorbar(self,*args,**kwargs):
+    def errorbar(self, *args, **kwargs):
         '''
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.errorbar` for arrays,
@@ -121,15 +118,15 @@ class MantidAxes(Axes):
             ax.errorbar(x,y,yerr,'bo')            #for arrays
             fig.show()
         
-        For keywords related to workspaces, see :func:`mantid.plots.functions.errorbar`             
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.errorbar`
         '''
-        if self.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.functions')     
-            return mantid.plots.functions.errorbar(self,*args,**kwargs)
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
+            return mantid.plots.plotfunctions.errorbar(self, *args, **kwargs)
         else:
-            return Axes.errorbar(self,*args,**kwargs)
+            return Axes.errorbar(self, *args, **kwargs)
 
-    def pcolor(self,*args,**kwargs):
+    def pcolor(self, *args, **kwargs):
         '''
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.pcolor` for arrays,
@@ -146,15 +143,15 @@ class MantidAxes(Axes):
             ax.pcolor(x,y,C)     #for arrays
             fig.show()
         
-        For keywords related to workspaces, see :func:`mantid.plots.functions.pcolor`             
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.pcolor`
         '''
-        if self.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.functions')     
-            return mantid.plots.functions.pcolor(self,*args,**kwargs)
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
+            return mantid.plots.plotfunctions.pcolor(self, *args, **kwargs)
         else:
-            return Axes.pcolor(self,*args,**kwargs)
+            return Axes.pcolor(self, *args, **kwargs)
 
-    def pcolorfast(self,*args,**kwargs):
+    def pcolorfast(self, *args, **kwargs):
         '''
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.pcolorfast` for arrays,
@@ -171,15 +168,15 @@ class MantidAxes(Axes):
             ax.pcolorfast(x,y,C)     #for arrays
             fig.show()
         
-        For keywords related to workspaces, see :func:`mantid.plots.functions.pcolorfast`             
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.pcolorfast`
         '''
-        if self.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.functions')     
-            return mantid.plots.functions.pcolorfast(self,*args,**kwargs)
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
+            return mantid.plots.plotfunctions.pcolorfast(self, *args, **kwargs)
         else:
-            return Axes.pcolorfast(self,*args,**kwargs)
+            return Axes.pcolorfast(self, *args, **kwargs)
 
-    def pcolormesh(self,*args,**kwargs):
+    def pcolormesh(self, *args, **kwargs):
         '''
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.pcolormesh` for arrays,
@@ -196,15 +193,15 @@ class MantidAxes(Axes):
             ax.pcolormesh(x,y,C)     #for arrays
             fig.show()
         
-        For keywords related to workspaces, see :func:`mantid.plots.functions.pcolormesh`             
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.pcolormesh`
         '''
-        if self.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.functions')     
-            return mantid.plots.functions.pcolormesh(self,*args,**kwargs)
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
+            return mantid.plots.plotfunctions.pcolormesh(self, *args, **kwargs)
         else:
-            return Axes.pcolormesh(self,*args,**kwargs)
+            return Axes.pcolormesh(self, *args, **kwargs)
 
-    def contour(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,
@@ -221,15 +218,15 @@ class MantidAxes(Axes):
             ax.contour(x,y,z)     #for arrays
             fig.show()
         
-        For keywords related to workspaces, see :func:`mantid.plots.functions.contour`             
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.contour`
         '''
-        if self.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.functions')     
-            return mantid.plots.functions.contour(self,*args,**kwargs)
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
+            return mantid.plots.plotfunctions.contour(self, *args, **kwargs)
         else:
-            return Axes.contour(self,*args,**kwargs)
+            return Axes.contour(self, *args, **kwargs)
 
-    def contourf(self,*args,**kwargs):
+    def contourf(self, *args, **kwargs):
         '''
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.contourf` for arrays,
@@ -246,15 +243,15 @@ class MantidAxes(Axes):
             ax.contourf(x,y,z)     #for arrays
             fig.show()
         
-        For keywords related to workspaces, see :func:`mantid.plots.functions.contourf`             
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.contourf`
         '''
-        if self.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.functions')     
-            return mantid.plots.functions.contourf(self,*args,**kwargs)
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
+            return mantid.plots.plotfunctions.contourf(self, *args, **kwargs)
         else:
-            return Axes.contourf(self,*args,**kwargs)
+            return Axes.contourf(self, *args, **kwargs)
 
-    def tripcolor(self,*args,**kwargs):
+    def tripcolor(self, *args, **kwargs):
         '''
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.tripcolor` for arrays,
@@ -271,15 +268,15 @@ class MantidAxes(Axes):
             ax.tripcolor(x,y,C)     #for arrays
             fig.show()
         
-        For keywords related to workspaces, see :func:`mantid.plots.functions.tripcolor`             
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.tripcolor`
         '''
-        if self.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.functions')     
-            return mantid.plots.functions.tripcolor(self,*args,**kwargs)
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
+            return mantid.plots.plotfunctions.tripcolor(self, *args, **kwargs)
         else:
-            return Axes.tripcolor(self,*args,**kwargs)
+            return Axes.tripcolor(self, *args, **kwargs)
 
-    def tricontour(self,*args,**kwargs):
+    def tricontour(self, *args, **kwargs):
         '''
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.tricontour` for arrays,
@@ -296,15 +293,15 @@ class MantidAxes(Axes):
             ax.tricontour(x,y,z)     #for arrays
             fig.show()
         
-        For keywords related to workspaces, see :func:`mantid.plots.functions.tricontour`             
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.tricontour`
         '''
-        if self.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.functions')     
-            return mantid.plots.functions.tricontour(self,*args,**kwargs)
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
+            return mantid.plots.plotfunctions.tricontour(self, *args, **kwargs)
         else:
-            return Axes.tricontour(self,*args,**kwargs)
+            return Axes.tricontour(self, *args, **kwargs)
 
-    def tricontourf(self,*args,**kwargs):
+    def tricontourf(self, *args, **kwargs):
         '''
         If the **mantid** projection is chosen, it can be
         used the same as :py:meth:`matplotlib.axes.Axes.tricontourf` for arrays,
@@ -321,16 +318,186 @@ class MantidAxes(Axes):
             ax.tricontourf(x,y,z)     #for arrays
             fig.show()
         
-        For keywords related to workspaces, see :func:`mantid.plots.functions.tricontourf`             
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions.tricontourf`
         '''
-        if self.validate_args(*args):
-            mantid.kernel.logger.debug('using mantid.plots.functions')     
-            return mantid.plots.functions.tricontourf(self,*args,**kwargs)
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions')
+            return mantid.plots.plotfunctions.tricontourf(self, *args, **kwargs)
         else:
-            return Axes.tricontourf(self,*args,**kwargs)
+            return Axes.tricontourf(self, *args, **kwargs)
 
 
-register_projection(MantidAxes)
+class MantidAxes3D(Axes3D):
+    '''
+    This class defines the **mantid3d** projection for 3d plotting. One chooses
+    this projection using::
+
+        import matplotlib.pyplot as plt
+        from mantid import plots
+        fig, ax = plt.subplots(subplot_kw={'projection':'mantid3d'})
+
+    or::
+
+        import matplotlib.pyplot as plt
+        from mantid import plots
+        fig = plt.figure()
+        ax = fig.add_subplot(111,projection='mantid3d')
+
+    The mantid3d projection allows replacing the array objects with mantid workspaces.
+    '''
+
+    name = 'mantid3d'
+
+    def plot(self, *args, **kwargs):
+        '''
+        If the **mantid3d** projection is chosen, it can be
+        used the same as :py:meth:`matplotlib.axes.Axes3D.plot` 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':'mantid3d'})
+            ax.plot(workspace) #for workspaces
+            ax.plot(x,y,z)     #for arrays
+            fig.show()
+
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions3D.plot3D`
+        '''
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions3D')
+            return mantid.plots.plotfunctions3D.plot(self, *args, **kwargs)
+        else:
+            return Axes3D.plot(self, *args, **kwargs)
+
+    def scatter(self, *args, **kwargs):
+        '''
+        If the **mantid3d** projection is chosen, it can be
+        used the same as :py:meth:`matplotlib.axes.Axes3D.scatter` 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':'mantid3d'})
+            ax.scatter(workspace) #for workspaces
+            ax.scatter(x,y,z)     #for arrays
+            fig.show()
+
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions3D.scatter`
+        '''
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions3D')
+            return mantid.plots.plotfunctions3D.scatter(self, *args, **kwargs)
+        else:
+            return Axes3D.scatter(self, *args, **kwargs)
+
+    def plot_wireframe(self, *args, **kwargs):
+        '''
+        If the **mantid3d** projection is chosen, it can be
+        used the same as :py:meth:`matplotlib.axes.Axes3D.plot_wireframe` 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':'mantid3d'})
+            ax.plot_wireframe(workspace) #for workspaces
+            ax.plot_wireframe(x,y,z)     #for arrays
+            fig.show()
+
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions3D.wireframe`
+        '''
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions3D')
+            return mantid.plots.plotfunctions3D.plot_wireframe(self, *args, **kwargs)
+        else:
+            return Axes3D.plot_wireframe(self, *args, **kwargs)
+
+    def plot_surface(self, *args, **kwargs):
+        '''
+        If the **mantid3d** projection is chosen, it can be
+        used the same as :py:meth:`matplotlib.axes.Axes3D.plot_surface` 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':'mantid3d'})
+            ax.plot_surface(workspace) #for workspaces
+            ax.plot_surface(x,y,z)     #for arrays
+            fig.show()
+
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions3D.plot_surface`
+        '''
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions3D')
+            return mantid.plots.plotfunctions3D.plot_surface(self, *args, **kwargs)
+        else:
+            return Axes3D.plot_surface(self, *args, **kwargs)
+
+    def contour(self, *args, **kwargs):
+        '''
+        If the **mantid3d** projection is chosen, it can be
+        used the same as :py:meth:`matplotlib.axes.Axes3D.contour` 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':'mantid3d'})
+            ax.contour(workspace) #for workspaces
+            ax.contour(x,y,z)     #for arrays
+            fig.show()
+
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions3D.contour`
+        '''
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions3D')
+            return mantid.plots.plotfunctions3D.contour(self, *args, **kwargs)
+        else:
+            return Axes3D.contour(self, *args, **kwargs)
+
+    def contourf(self, *args, **kwargs):
+        '''
+        If the **mantid3d** projection is chosen, it can be
+        used the same as :py:meth:`matplotlib.axes.Axes3D.contourf` 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':'mantid3d'})
+            ax.contourf(workspace) #for workspaces
+            ax.contourf(x,y,z)     #for arrays
+            fig.show()
+
+        For keywords related to workspaces, see :func:`mantid.plots.plotfunctions3D.contourf`
+        '''
+        if mantid.plots.helperfunctions.validate_args(*args):
+            mantid.kernel.logger.debug('using mantid.plots.plotfunctions3D')
+            return mantid.plots.plotfunctions3D.contourf(self, *args, **kwargs)
+        else:
+            return Axes3D.contourf(self, *args, **kwargs)
+
+
+register_projection(MantidAxes)
+register_projection(MantidAxes3D)
diff --git a/Framework/PythonInterface/mantid/plots/functions.py b/Framework/PythonInterface/mantid/plots/functions.py
deleted file mode 100644
index 7f578b8e6a551baeb56e5a8eff3560f1e100c9aa..0000000000000000000000000000000000000000
--- a/Framework/PythonInterface/mantid/plots/functions.py
+++ /dev/null
@@ -1,776 +0,0 @@
-#  This file is part of the mantid package
-#
-#  Copyright (C) 2017 mantidproject
-#
-#  This program 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.
-#
-#  This program 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/>.
-from __future__ import (absolute_import, division, print_function)
-
-import numpy
-import mantid.kernel
-import mantid.api
-import mantid.dataobjects
-import matplotlib.colors
-
-
-def _getDistribution(workspace, **kwargs):
-    '''Determine whether or not the data is a distribution. The value in
-    the kwargs wins. Applies to Matrix workspaces only
-    :param workspace: :class:`mantid.api.MatrixWorkspace` to extract the data from'''
-    distribution = kwargs.pop('distribution', workspace.isDistribution())
-    return (bool(distribution), kwargs)
-
-
-def _getNormalization(mdworkspace, **kwargs):
-    '''gets the normalization flag of an MDHistoWorkspace. For workspaces
-    derived similar to MSlice/Horace, one needs to average data, the so-called
-    "number of events" normalization.
-    :param mdworkspace: :class:`mantid.api.IMDHistoWorkspace` to extract the data from'''
-    normalization = kwargs.pop('normalization', mdworkspace.displayNormalizationHisto())
-    return (normalization,kwargs)
-
-
-def getAxesLabels(workspace):
-    ''' get axis labels from a Workspace2D or an MDHistoWorkspace
-    Returns a tuple. The first element is the quantity label, such as "Intensity" or "Counts".
-    All other elements in the tuple are labels for axes.
-    Some of them are latex formatted already.
-    :param workspace: :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace`
-    '''
-    if isinstance(workspace,mantid.dataobjects.MDHistoWorkspace):
-        axes = ['Intensity']
-        dims = workspace.getNonIntegratedDimensions()
-        for d in dims:
-            axis_title = d.name.replace('DeltaE','$\Delta E$')
-            axis_unit = d.getUnits().replace('Angstrom^-1','$\AA^{-1}$')
-            axis_unit = axis_unit.replace('DeltaE','meV')
-            axis_unit = axis_unit.replace('Angstrom','$\AA$')
-            axis_unit = axis_unit.replace('MomentumTransfer','$\AA^{-1}$')
-            axes.append('{0} ({1})'.format(axis_title,axis_unit))
-    else:
-        '''For matrix workspaces, return a tuple of ``(YUnit, <other units>)``'''
-        axes = [workspace.YUnit()] #TODO: deal with distribution
-        for index in range(workspace.axes()):
-            axis = workspace.getAxis(index)
-            unit = axis.getUnit()
-            if len(str(unit.symbol())) > 0:
-                unit = '{} (${}$)'.format(unit.caption(), unit.symbol().latex())
-            else:
-                unit = unit.caption()
-            axes.append(unit)
-    return tuple(axes)
-
-
-def _getWkspIndexDistAndLabel(workspace, **kwargs):
-    '''
-    Get workspace index, whether the workspace is a distribution,
-    and label for the spectrum
-
-    :param workspace: a Workspace2D or an EventWorkspace
-    '''
-    # get the special arguments out of kwargs
-    specNum = kwargs.pop('specNum', None)
-    wkspIndex = kwargs.pop('wkspIndex', None)
-
-    # don't worry if there is only one spectrum
-    if workspace.getNumberHistograms() == 1:
-        specNum = None
-        wkspIndex = 0
-
-    # error check input parameters
-    if (specNum is not None) and (wkspIndex is not None):
-        raise RuntimeError('Must specify only specNum or wkspIndex')
-    if (specNum is None) and (wkspIndex is None):
-        raise RuntimeError('Must specify either specNum or wkspIndex')
-
-    # convert the spectrum number to a workspace index and vice versa
-    if specNum is not None:
-        wkspIndex = workspace.getIndexFromSpectrumNumber(int(specNum))
-    else:
-        specNum = workspace.getSpectrum(wkspIndex).getSpectrumNo()
-
-    # create a label if it isn't already specified
-    if 'label' not in kwargs:
-        wsName = workspace.name()
-        if wsName:
-            kwargs['label'] = '{0}: spec {1}'.format(wsName, specNum)
-        else:
-            kwargs['label'] = 'spec {0}'.format(specNum)
-
-    (distribution, kwargs) = _getDistribution(workspace, **kwargs)
-
-    return (wkspIndex, distribution, kwargs)
-
-
-def _getSpectrum(workspace, wkspIndex, distribution, withDy=False, withDx=False):
-    '''Extract a single spectrum and process the data into a frequency
-    :param workspace: a Workspace2D or an EventWorkspace
-    :param wkspIndex: workspace index
-    :param distribution: flag to divide the data by bin width. It happens only
-        when this flag is False, the workspace contains histogram data, and
-        the mantid configuration is set up to divide such workspaces by bin
-        width. The same effect can be obtained by running the
-        :ref:`algm-ConvertToDistribution` algorithm
-    :param withDy: if True, it will return the error in the "counts", otherwise None
-    :param with Dx: if True, and workspace has them, it will return errors
-        in the x coordinate, otherwise None
-    Note that for workspaces containing bin boundaries, this function will return
-    the bin centers for x.
-    To be used in 1D plots (plot, scatter, errorbar)
-    '''
-    x = workspace.readX(wkspIndex)
-    y = workspace.readY(wkspIndex)
-    dy = None
-    dx = None
-
-    if withDy:
-        dy = workspace.readE(wkspIndex)
-    if withDx and workspace.getSpectrum(wkspIndex).hasDx():
-        dx = workspace.readDx(wkspIndex)
-
-    if workspace.isHistogramData():
-        if (not distribution) and (mantid.kernel.config['graph1d.autodistribution']=='On'):
-            y = y / (x[1:] - x[0:-1])
-            if dy is not None:
-                dy = dy / (x[1:] - x[0:-1])
-        x = points_from_boundaries(x)
-    y=numpy.ma.masked_invalid(y)
-    if dy is not None:
-        dy=numpy.ma.masked_invalid(dy)
-    return (x,y,dy,dx)
-
-
-def _commonX(arr):
-    '''
-    Helper function to check if all rows in a 2d :class:`numpy.ndarray` are identical
-    '''
-    return numpy.all(arr==arr[0,:],axis=(1,0))
-
-
-def _getMatrix2DData(workspace, distribution,histogram2D=False):
-    '''
-    Get all data from a Matrix workspace that has the same number of bins
-    in every spectrum. It is used for 2D plots
-    :param workspace: Matrix workspace to extract the data from
-    :param distribution: if False, and the workspace contains histogram data,
-        the intensity will be divided by the x bin width
-    :param histogram2D: flag that specifies if the coordinates in the output are
-        -bin centers (such as for contour) for False, or
-        -bin edges (such as for pcolor) for True.
-    Returns x,y,z 2D arrays
-    '''
-    try:
-        _=workspace.blocksize()
-    except RuntimeError:
-        raise ValueError('The spectra are not the same length. Try using pcolor, pcolorfast, or pcolormesh instead')
-    x = workspace.extractX()
-    y = workspace.getAxis(1).extractValues()
-    z = workspace.extractY()
-
-    if workspace.isHistogramData():
-        if not distribution:
-            z = z / (x[:,1:] - x[:,0:-1])
-        if histogram2D:
-            if len(y)==z.shape[0]:
-                y = boundaries_from_points(y)
-            x = numpy.vstack((x,x[-1]))
-        else:
-            x = .5*(x[:,0:-1]+x[:,1:])
-            if len(y)==z.shape[0]+1:
-                y=points_from_boundaries(y)
-    else:
-        if histogram2D:
-            if _commonX(x):
-                x=numpy.tile(boundaries_from_points(x[0]),z.shape[0]+1).reshape(z.shape[0]+1,-1)
-            else:
-                x = numpy.vstack((x,x[-1]))
-                x = numpy.array([boundaries_from_points(xi) for xi in x])
-            if len(y)==z.shape[0]:
-                y=boundaries_from_points(y)
-        else:
-            if len(y)==z.shape[0]+1:
-                y=points_from_boundaries(y)
-    y = numpy.tile(y,x.shape[1]).reshape(x.shape[1],x.shape[0]).transpose()
-    z=numpy.ma.masked_invalid(z)
-    return (x,y,z)
-
-
-def _getDataUnevenFlag(workspace,**kwargs):
-    '''
-    Helper function that allows :meth:`matplotlib.axes.Axes.pcolor`,
-    :meth:`matplotlib.axes.Axes.pcolorfast`, and :meth:`matplotlib.axes.Axes.pcolormesh`
-    to plot rectangles parallel to the axes even if the data is not
-    on a regular grid.
-    :param workspace: a workspace2d
-    if axisaligned keyword is available and True or if the workspace does
-    not have a constant number of bins, it will return true, otherwise false
-    '''
-    aligned = kwargs.pop('axisaligned', False)
-    try:
-        _=workspace.blocksize()
-    except RuntimeError:
-        aligned=True
-    return(aligned,kwargs)
-
-
-def _getUnevenData(workspace, distribution):
-    '''
-    Function to get data for uneven workspace2Ds, such as
-    that pcolor, pcolorfast, and pcolormesh will plot axis aligned rectangles
-    :param workspace: a workspace2d
-    :param distribution: if False, and the workspace contains histogram data,
-        the intensity will be divided by the x bin width
-    Returns three lists. Each element in the x list is an array of boundaries
-    for a spectra. Each element in the y list is a 2 element array with the extents
-    of a particular spectra. The z list contains arrays of intensities at bin centers
-    '''
-    z=[]
-    x=[]
-    y=[]
-    nhist=workspace.getNumberHistograms()
-    yvals=workspace.getAxis(1).extractValues()
-    if len(yvals)==(nhist):
-        yvals=boundaries_from_points(yvals)
-    for index in range(nhist):
-        xvals=workspace.readX(index)
-        zvals=workspace.readY(index)
-        if workspace.isHistogramData():
-            if not distribution:
-                zvals = zvals / (xvals[1:] - xvals[0:-1])
-        else:
-            xvals=boundaries_from_points(xvals)
-        zvals=numpy.ma.masked_invalid(zvals)
-        z.append(zvals)
-        x.append(xvals)
-        y.append([yvals[index],yvals[index+1]])
-    return(x,y,z)
-
-
-def _dim2array(d):
-    '''
-    Create a numpy array containing bin centers along the dimension d
-    :param d: an :class:`mantid.geometry.IMDDimension` object
-
-    returns: bin boundaries for dimension d
-    '''
-    dmin=d.getMinimum()
-    dmax=d.getMaximum()
-    return numpy.linspace(dmin,dmax,d.getNBins()+1)
-
-
-def boundaries_from_points(input_array):
-    '''
-    The function tries to guess bin boundaries from bin centers
-    :param input_array: a :class:`numpy.ndarray` of bin centers
-    '''
-    assert isinstance(input_array,numpy.ndarray),'Not a numpy array'
-    if len(input_array)==0:
-        raise ValueError('could not extend array with no elements')
-    if len(input_array)==1:
-        return numpy.array([input_array[0]-0.5,input_array[0]+0.5])
-    return numpy.concatenate(([(3*input_array[0]-input_array[1])*0.5],
-                           (input_array[1:]+input_array[:-1])*0.5,
-                           [(3*input_array[-1]-input_array[-2])*0.5]))
-
-
-def points_from_boundaries(input_array):
-    '''
-    The function returns bin centers from bin boundaries
-    :param input_array: a :class:`numpy.ndarray` of bin boundaries
-    '''
-    assert isinstance(input_array,numpy.ndarray),'Not a numpy array'
-    if len(input_array)<2:
-        raise ValueError('could not get centers from less than two boundaries')
-    return (.5*(input_array[0:-1]+input_array[1:]))
-
-
-def _getMDData(workspace,normalization,withError=False):
-    '''
-    generic function to extract data from an MDHisto workspace
-    :param workspace: :class:`mantid.api.IMDHistoWorkspace` containing data
-    :param normalization: if :class:`mantid.api.MDNormalization.NumEventsNormalization`
-        it will divide intensity by the number of corresponding MDEvents
-    returns a tuple containing bin boundaries for each dimension, the (maybe normalized)
-    signal and error arrays
-    '''
-    dims=workspace.getNonIntegratedDimensions()
-    dimarrays=[_dim2array(d) for d in dims]
-    #get data
-    data=workspace.getSignalArray()*1.
-    if normalization==mantid.api.MDNormalization.NumEventsNormalization:
-        nev=workspace.getNumEventsArray()
-        data/=nev
-    err=None
-    if withError:
-        err2=workspace.getErrorSquaredArray()*1.
-        if normalization==mantid.api.MDNormalization.NumEventsNormalization:
-            err2/=(nev*nev)
-        err=numpy.sqrt(err2)
-    data=data.squeeze().T
-    data=numpy.ma.masked_invalid(data)
-    if err is not None:
-        err=err.squeeze().T
-        err=numpy.ma.masked_invalid(err)       
-    return (dimarrays,data,err)
-
-
-def _getMDData1D(workspace,normalization):
-    '''
-    Function to transform data in an MDHisto workspace with exactly
-    one non-integrated dimension into arrays of bin centers, data,
-    and error, to be used in 1D plots (plot, scatter, errorbar)
-    '''
-    coordinate,data,err=_getMDData(workspace,normalization,withError=True)
-    assert len(coordinate)==1, 'The workspace is not 1D'
-    coordinate=points_from_boundaries(coordinate[0])
-    return (coordinate,data,err)
-
-
-def _getMDData2D_bin_bounds(workspace,normalization):
-    '''
-    Function to transform data in an MDHisto workspace with exactly
-    two non-integrated dimension into arrays of bin boundaries in each
-    dimension, and data. To be used in 2D plots (pcolor, pcolorfast, pcolormesh)
-    Note return coordinates are 1d vectors. Use numpy.meshgrid to generate 2d versions
-    '''
-    coordinate,data,_=_getMDData(workspace,normalization,withError=False)
-    assert len(coordinate)==2, 'The workspace is not 2D'
-    return (coordinate[0],coordinate[1],data)
-
-
-def _getMDData2D_bin_centers(workspace,normalization):
-    '''
-    Function to transform data in an MDHisto workspace with exactly
-    two non-integrated dimension into arrays of bin centers in each
-    dimension, and data. To be used in 2D plots (contour, contourf,
-    tricontour, tricontourf, tripcolor)
-    Note return coordinates are 1d vectors. Use numpy.meshgrid to generate 2d versions
-    '''
-    x,y,data=_getMDData2D_bin_bounds(workspace,normalization)
-    x=points_from_boundaries(x)
-    y=points_from_boundaries(y)
-    return (x,y,data)
-
-
-def _setLabels1D(axes, workspace):
-    '''
-    helper function to automatically set axes labels for 1D plots
-    '''
-    labels = getAxesLabels(workspace)
-    axes.set_xlabel(labels[1])
-    axes.set_ylabel(labels[0])
-
-
-def _setLabels2D(axes, workspace):
-    '''
-    helper function to automatically set axes labels for 2D plots
-    '''
-    labels = getAxesLabels(workspace)
-    axes.set_xlabel(labels[1])
-    axes.set_ylabel(labels[2])
-
-
-def plot(axes, workspace, *args, **kwargs):
-    '''
-    Unpack mantid workspace and render it with matplotlib. ``args`` and
-    ``kwargs`` are passed to :py:meth:`matplotlib.axes.Axes.plot` after special
-    keyword arguments are removed. This will automatically label the
-    line according to the spectrum number unless specified otherwise.
-
-    :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 specNum:   spectrum number to plot if MatrixWorkspace
-    :param wkspIndex: workspace index to plot if MatrixWorkspace
-    :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 workspace is a MatrixWorkspace 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
-
-    For matrix workspaces with more than one spectra, either ``specNum`` or ``wkspIndex``
-    needs to be specified. Giving both will generate a :class:`RuntimeError`. There is no similar
-    keyword for MDHistoWorkspaces. These type of workspaces have to have exactly one non integrated
-    dimension
-    '''
-    if isinstance(workspace,mantid.dataobjects.MDHistoWorkspace):
-        (normalization,kwargs)=_getNormalization(workspace, **kwargs)
-        (x,y,_)=_getMDData1D(workspace,normalization)
-    else:
-        (wkspIndex, distribution, kwargs) = _getWkspIndexDistAndLabel(workspace, **kwargs)
-        (x, y, _, _) = _getSpectrum(workspace, wkspIndex, distribution, withDy=False, withDx=False)
-    _setLabels1D(axes, workspace)
-    return axes.plot(x, y, *args, **kwargs)
-
-
-def errorbar(axes, workspace, *args, **kwargs):
-    '''
-    Unpack mantid workspace and render it with matplotlib. ``args`` and
-    ``kwargs`` are passed to :py:meth:`matplotlib.axes.Axes.errorbar` after special
-    keyword arguments are removed. This will automatically label the
-    line according to the spectrum number unless specified otherwise.
-
-    :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 specNum:   spectrum number to plot if MatrixWorkspace
-    :param wkspIndex: workspace index to plot if MatrixWorkspace
-    :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 workspace is a MatrixWorkspace 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
-
-    For matrix workspaces with more than one spectra, either ``specNum`` or ``wkspIndex``
-    needs to be specified. Giving both will generate a :class:`RuntimeError`. There is no similar
-    keyword for MDHistoWorkspaces. These type of workspaces have to have exactly one non integrated
-    dimension
-    '''
-    if isinstance(workspace,mantid.dataobjects.MDHistoWorkspace):
-        (normalization,kwargs)=_getNormalization(workspace, **kwargs)
-        (x,y,dy)=_getMDData1D(workspace,normalization)
-        dx=None
-    else:
-        (wkspIndex, distribution, kwargs) = _getWkspIndexDistAndLabel(workspace, **kwargs)
-        (x, y, dy, dx) = _getSpectrum(workspace, wkspIndex, distribution, withDy=True, withDx=True)
-    _setLabels1D(axes, workspace)
-    return axes.errorbar(x, y, dy, dx, *args, **kwargs)
-
-
-def scatter(axes, workspace, *args, **kwargs):
-    '''
-    Unpack mantid workspace and render it with matplotlib. ``args`` and
-    ``kwargs`` are passed to :py:meth:`matplotlib.axes.Axes.scatter` after special
-    keyword arguments are removed. This will automatically label the
-    line according to the spectrum number unless specified otherwise.
-
-    :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 specNum:   spectrum number to plot if MatrixWorkspace
-    :param wkspIndex: workspace index to plot if MatrixWorkspace
-    :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 workspace is a MatrixWorkspace 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
-
-    For matrix workspaces with more than one spectra, either ``specNum`` or ``wkspIndex``
-    needs to be specified. Giving both will generate a :class:`RuntimeError`. There is no similar
-    keyword for MDHistoWorkspaces. These type of workspaces have to have exactly one non integrated
-    dimension
-    '''
-    if isinstance(workspace,mantid.dataobjects.MDHistoWorkspace):
-        (normalization,kwargs)=_getNormalization(workspace, **kwargs)
-        (x,y,_)=_getMDData1D(workspace,normalization)
-    else:
-        (wkspIndex, distribution, kwargs) = _getWkspIndexDistAndLabel(workspace, **kwargs)
-        (x, y, _, _) = _getSpectrum(workspace, wkspIndex, distribution)
-    _setLabels1D(axes, workspace)
-    return axes.scatter(x, y, *args, **kwargs)
-
-
-def contour(axes, workspace, *args, **kwargs):
-    '''
-    Essentially the same as :meth:`matplotlib.axes.Axes.contour`
-    but calculates the countour levels. Currently this only works with
-    workspaces that have a constant number of bins between spectra.
-
-    :param axes:      :class:`matplotlib.axes.Axes` object that will do the plotting
-    :param workspace: :class:`mantid.api.MatrixWorkspace` or 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
-    '''
-    if isinstance(workspace,mantid.dataobjects.MDHistoWorkspace):
-        (normalization,kwargs)=_getNormalization(workspace, **kwargs)
-        x,y,z=_getMDData2D_bin_centers(workspace,normalization)
-    else:
-        (distribution, kwargs) = _getDistribution(workspace, **kwargs)
-        (x,y,z) = _getMatrix2DData(workspace, distribution,histogram2D=False)
-    _setLabels2D(axes, workspace)
-    return axes.contour(x, y, z, *args, **kwargs)
-
-
-def contourf(axes, workspace, *args, **kwargs):
-    '''
-    Essentially the same as :meth:`matplotlib.axes.Axes.contourf`
-    but calculates the countour levels. Currently this only works with
-    workspaces that have a constant number of bins between spectra.
-
-    :param axes:      :class:`matplotlib.axes.Axes` object that will do the plotting
-    :param workspace: :class:`mantid.api.MatrixWorkspace` or 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
-    '''
-    if isinstance(workspace,mantid.dataobjects.MDHistoWorkspace):
-        (normalization,kwargs)=_getNormalization(workspace, **kwargs)
-        x,y,z=_getMDData2D_bin_centers(workspace,normalization)
-    else:
-        (distribution, kwargs) = _getDistribution(workspace, **kwargs)
-        (x,y,z) = _getMatrix2DData(workspace, distribution,histogram2D=False)
-    _setLabels2D(axes, workspace)
-    return axes.contourf(x, y, z, *args, **kwargs)
-
-
-def _pcolorpieces(axes, workspace, distribution, *args,**kwargs):
-    '''
-    Helper function for pcolor, pcolorfast, and pcolormesh that will
-    plot a 2d representation of each spectra. The polycollections or meshes
-    will be normalized to the same intensity limits.
-    :param axes: :class:`matplotlib.axes.Axes` object that will do the plotting
-    :param workspace: :class:`mantid.api.MatrixWorkspace` 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 pcolortype: this keyword allows the plotting to be one of pcolormesh or
-        pcolorfast if there is "mesh" or "fast" in the value of the keyword, or
-        pcolor by default
-    Note: the return is the pcolor, pcolormesh, or pcolorfast of the last spectrum
-    '''
-    (x,y,z)=_getUnevenData(workspace, distribution)
-    pcolortype=kwargs.pop('pcolortype','')
-    mini=numpy.min([numpy.min(i) for i in z])
-    maxi=numpy.max([numpy.max(i) for i in z])
-    if 'vmin' in kwargs:
-        mini=kwargs['vmin']
-    if 'vmax' in kwargs:
-        maxi=kwargs['vmax']
-    if 'norm' not in kwargs:
-        kwargs['norm']=matplotlib.colors.Normalize(vmin=mini, vmax=maxi)
-    else:
-        if kwargs['norm'].vmin==None:
-            kwargs['norm'].vmin=mini
-        if kwargs['norm'].vmax==None:
-            kwargs['norm'].vmax=maxi
-    for xi,yi,zi in zip(x,y,z):
-        XX,YY=numpy.meshgrid(xi,yi,indexing='ij')
-        if 'mesh' in pcolortype.lower():
-            cm=axes.pcolormesh(XX,YY,zi.reshape(-1,1),**kwargs)
-        elif 'fast' in pcolortype.lower():
-            cm=axes.pcolorfast(XX,YY,zi.reshape(-1,1),**kwargs)
-        else:
-            cm=axes.pcolor(XX,YY,zi.reshape(-1,1),**kwargs)
-    return cm
-
-
-def pcolor(axes, workspace, *args, **kwargs):
-    '''
-    Essentially the same as :meth:`matplotlib.axes.Axes.pcolor`
-
-    :param axes:      :class:`matplotlib.axes.Axes` object that will do the plotting
-    :param workspace: :class:`mantid.api.MatrixWorkspace` or 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)=_getNormalization(workspace, **kwargs)
-        x,y,z=_getMDData2D_bin_bounds(workspace,normalization)
-    else:
-        (aligned, kwargs)=_getDataUnevenFlag(workspace,**kwargs)
-        (distribution, kwargs) = _getDistribution(workspace, **kwargs)
-        if aligned:
-            kwargs['pcolortype']=''
-            return _pcolorpieces(axes, workspace, distribution, *args,**kwargs)
-        else:
-            (x,y,z) = _getMatrix2DData(workspace, distribution,histogram2D=True)
-    return axes.pcolor(x, y, z, *args, **kwargs)
-
-def pcolorfast(axes, workspace, *args, **kwargs):
-    '''
-    Essentially the same as :meth:`matplotlib.axes.Axes.pcolorfast`
-
-    :param axes:      :class:`matplotlib.axes.Axes` object that will do the plotting
-    :param workspace: :class:`mantid.api.MatrixWorkspace` or 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)=_getNormalization(workspace, **kwargs)
-        x,y,z=_getMDData2D_bin_bounds(workspace,normalization)
-    else:
-        (aligned, kwargs)=_getDataUnevenFlag(workspace,**kwargs)
-        (distribution, kwargs) = _getDistribution(workspace, **kwargs)
-        if aligned:
-            kwargs['pcolortype']='fast'
-            return _pcolorpieces(axes, workspace, distribution, *args,**kwargs)
-        else:
-            (x,y,z) = _getMatrix2DData(workspace, distribution,histogram2D=True)
-    return axes.pcolorfast(x, y, z, *args, **kwargs)
-
-
-def pcolormesh(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 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)=_getNormalization(workspace, **kwargs)
-        x,y,z=_getMDData2D_bin_bounds(workspace,normalization)
-    else:
-        (aligned, kwargs)=_getDataUnevenFlag(workspace,**kwargs)
-        (distribution, kwargs) = _getDistribution(workspace, **kwargs)
-        if aligned:
-            kwargs['pcolortype']='mesh'
-            return _pcolorpieces(axes, workspace, distribution, *args,**kwargs)
-        else:
-            (x,y,z) = _getMatrix2DData(workspace, distribution,histogram2D=True)
-
-    return axes.pcolormesh(x, y, z, *args, **kwargs)
-
-
-def tripcolor(axes, workspace, *args, **kwargs):
-    '''
-    To be used with non-uniform grids. Currently this only works with workspaces
-    that have a constant number of bins between spectra or with
-    MDHistoWorkspaces.
-
-    :param axes:      :class:`matplotlib.axes.Axes` object that will do the plotting
-    :param workspace: :class:`mantid.api.MatrixWorkspace` or 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
-
-    See :meth:`matplotlib.axes.Axes.tripcolor` for more information.
-    '''
-    if isinstance(workspace,mantid.dataobjects.MDHistoWorkspace):
-        (normalization,kwargs)=_getNormalization(workspace, **kwargs)
-        xtemp,ytemp,z=_getMDData2D_bin_centers(workspace,normalization)
-        x,y=numpy.meshgrid(xtemp,ytemp)
-    else:
-        (distribution, kwargs) = _getDistribution(workspace, **kwargs)
-        (x,y,z) = _getMatrix2DData(workspace, distribution,histogram2D=False)
-    _setLabels2D(axes, workspace)
-
-    return axes.tripcolor(x.ravel(), y.ravel(), z.ravel(), *args, **kwargs)
-
-def tricontour(axes, workspace, *args, **kwargs):
-    '''
-    Essentially the same as :meth:`mantid.plots.contour`, but works
-    for non-uniform grids. Currently this only works with workspaces
-    that have a constant number of bins between spectra or with
-    MDHistoWorkspaces.
-
-    :param axes:      :class:`matplotlib.axes.Axes` object that will do the plotting
-    :param workspace: :class:`mantid.api.MatrixWorkspace` or 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
-
-    See :meth:`matplotlib.axes.Axes.tricontour` for more information.
-    '''
-    if isinstance(workspace,mantid.dataobjects.MDHistoWorkspace):
-        (normalization,kwargs)=_getNormalization(workspace, **kwargs)
-        xtemp,ytemp,z=_getMDData2D_bin_centers(workspace,normalization)
-        x,y=numpy.meshgrid(xtemp,ytemp)
-    else:
-        (distribution, kwargs) = _getDistribution(workspace, **kwargs)
-        (x,y,z) = _getMatrix2DData(workspace, distribution,histogram2D=False)
-    _setLabels2D(axes, workspace)
-    #tricontour segfaults if many z values are not finite
-    #https://github.com/matplotlib/matplotlib/issues/10167
-    x=x.ravel()
-    y=y.ravel()
-    z=z.ravel()
-    condition=numpy.isfinite(z)
-    x=x[condition]
-    y=y[condition]
-    z=z[condition]
-    return axes.tricontour(x, y, z, *args, **kwargs)
-
-
-def tricontourf(axes, workspace, *args, **kwargs):
-    '''
-    Essentially the same as :meth:`mantid.plots.contourf`, but works
-    for non-uniform grids. Currently this only works with workspaces
-    that have a constant number of bins between spectra or with
-    MDHistoWorkspaces.
-
-    :param axes:      :class:`matplotlib.axes.Axes` object that will do the plotting
-    :param workspace: :class:`mantid.api.MatrixWorkspace` or 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
-
-    See :meth:`matplotlib.axes.Axes.tricontourf` for more information.
-    '''
-    if isinstance(workspace, mantid.dataobjects.MDHistoWorkspace):
-        (normalization, kwargs) = _getNormalization(workspace, **kwargs)
-        (xtemp, ytemp, z) = _getMDData2D_bin_centers(workspace, normalization)
-        (x, y) = numpy.meshgrid(xtemp, ytemp)
-    else:
-        (distribution, kwargs) = _getDistribution(workspace, **kwargs)
-        (x, y, z) = _getMatrix2DData(workspace, distribution,histogram2D=False)
-    _setLabels2D(axes, workspace)
-    #tricontourf segfaults if many z values are not finite
-    #https://github.com/matplotlib/matplotlib/issues/10167
-    x = x.ravel()
-    y = y.ravel()
-    z = z.ravel()
-    condition = numpy.isfinite(z)
-    x = x[condition]
-    y = y[condition]
-    z = z[condition]
-    return axes.tricontourf(x, y, z, *args, **kwargs)
diff --git a/Framework/PythonInterface/mantid/plots/helperfunctions.py b/Framework/PythonInterface/mantid/plots/helperfunctions.py
new file mode 100644
index 0000000000000000000000000000000000000000..f576696e5d899b8de1bc144cc5bc5818c9e9aac7
--- /dev/null
+++ b/Framework/PythonInterface/mantid/plots/helperfunctions.py
@@ -0,0 +1,422 @@
+#  This file is part of the mantid package
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program 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.
+#
+#  This program 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/>.
+from __future__ import (absolute_import, division, print_function)
+
+import numpy
+import datetime
+from mantid.dataobjects import EventWorkspace, Workspace2D, MDHistoWorkspace
+import mantid.kernel, mantid.api
+
+
+# Helper functions for data extraction from a Mantid workspace and plot functionality
+# These functions are common between plotfunctions.py and plotfunctions3D.py
+
+# ====================================================
+# Validation
+# ====================================================
+def validate_args(*args):
+    return len(args) > 0 and (isinstance(args[0], EventWorkspace) or
+                              isinstance(args[0], Workspace2D) or
+                              isinstance(args[0], MDHistoWorkspace))
+
+
+# ====================================================
+# Data extraction and manipulation
+# ====================================================
+
+def get_distribution(workspace, **kwargs):
+    """
+    Determine whether or not the data is a distribution. The value in
+    the kwargs wins. Applies to Matrix workspaces only
+    :param workspace: :class:`mantid.api.MatrixWorkspace` to extract the data from
+    """
+    distribution = kwargs.pop('distribution', workspace.isDistribution())
+    return bool(distribution), kwargs
+
+
+def get_normalization(md_workspace, **kwargs):
+    """
+    Gets the normalization flag of an MDHistoWorkspace. For workspaces
+    derived similar to MSlice/Horace, one needs to average data, the so-called
+    "number of events" normalization.
+    :param md_workspace: :class:`mantid.api.IMDHistoWorkspace` to extract the data from
+    """
+    normalization = kwargs.pop('normalization', md_workspace.displayNormalizationHisto())
+    return normalization, kwargs
+
+
+def points_from_boundaries(input_array):
+    """
+    The function returns bin centers from bin boundaries
+    :param input_array: a :class:`numpy.ndarray` of bin boundaries
+    """
+    assert isinstance(input_array, numpy.ndarray), 'Not a numpy array'
+    if len(input_array) < 2:
+        raise ValueError('could not get centers from less than two boundaries')
+    return .5*(input_array[0:-1]+input_array[1:])
+
+
+def _dim2array(d):
+    """
+    Create a numpy array containing bin centers along the dimension d
+    :param d: an :class:`mantid.geometry.IMDDimension` object
+
+    returns: bin boundaries for dimension d
+    """
+    dmin = d.getMinimum()
+    dmax = d.getMaximum()
+    return numpy.linspace(dmin, dmax, d.getNBins()+1)
+
+
+def get_wksp_index_dist_and_label(workspace, **kwargs):
+    """
+    Get workspace index, whether the workspace is a distribution,
+    and label for the spectrum
+
+    :param workspace: a Workspace2D or an EventWorkspace
+    """
+    # get the special arguments out of kwargs
+    specNum = kwargs.pop('specNum', None)
+    wkspIndex = kwargs.pop('wkspIndex', None)
+
+    # don't worry if there is only one spectrum
+    if workspace.getNumberHistograms() == 1:
+        specNum = None
+        wkspIndex = 0
+
+    # error check input parameters
+    if (specNum is not None) and (wkspIndex is not None):
+        raise RuntimeError('Must specify only specNum or wkspIndex')
+    if (specNum is None) and (wkspIndex is None):
+        raise RuntimeError('Must specify either specNum or wkspIndex')
+
+    # convert the spectrum number to a workspace index and vice versa
+    if specNum is not None:
+        wkspIndex = workspace.getIndexFromSpectrumNumber(int(specNum))
+    else:
+        specNum = workspace.getSpectrum(wkspIndex).getSpectrumNo()
+
+    # create a label if it isn't already specified
+    if 'label' not in kwargs:
+        wsName = workspace.name()
+        if wsName:
+            kwargs['label'] = '{0}: spec {1}'.format(wsName, specNum)
+        else:
+            kwargs['label'] = 'spec {0}'.format(specNum)
+
+    (distribution, kwargs) = get_distribution(workspace, **kwargs)
+
+    return wkspIndex, distribution, kwargs
+
+
+def get_md_data1d(workspace, normalization):
+    """
+    Function to transform data in an MDHisto workspace with exactly
+    one non-integrated dimension into arrays of bin centers, data,
+    and error, to be used in 1D plots (plot, scatter, errorbar)
+    """
+    coordinate, data, err = get_md_data(workspace, normalization, withError=True)
+    assert len(coordinate) == 1, 'The workspace is not 1D'
+    coordinate = points_from_boundaries(coordinate[0])
+    return coordinate, data, err
+
+
+def get_md_data(workspace, normalization, withError=False):
+    """
+    Generic function to extract data from an MDHisto workspace
+    :param workspace: :class:`mantid.api.IMDHistoWorkspace` containing data
+    :param normalization: if :class:`mantid.api.MDNormalization.NumEventsNormalization`
+        it will divide intensity by the number of corresponding MDEvents
+    returns a tuple containing bin boundaries for each dimension, the (maybe normalized)
+    signal and error arrays
+    :param withError: flag for if the error is calculated. If False, err is returned as None
+    """
+    dims = workspace.getNonIntegratedDimensions()
+    dim_arrays = [_dim2array(d) for d in dims]
+    # get data
+    data = workspace.getSignalArray()*1.
+    if normalization == mantid.api.MDNormalization.NumEventsNormalization:
+        nev = workspace.getNumEventsArray()
+        data /= nev
+    err = None
+    if withError:
+        err2 = workspace.getErrorSquaredArray()*1.
+        if normalization == mantid.api.MDNormalization.NumEventsNormalization:
+            err2 /= (nev * nev)
+        err = numpy.sqrt(err2)
+    data = data.squeeze().T
+    data = numpy.ma.masked_invalid(data)
+    if err is not None:
+        err = err.squeeze().T
+        err = numpy.ma.masked_invalid(err)
+    return dim_arrays, data, err
+
+
+def get_spectrum(workspace, wkspIndex, distribution, withDy=False, withDx=False):
+    """
+    Extract a single spectrum and process the data into a frequency
+    :param workspace: a Workspace2D or an EventWorkspace
+    :param wkspIndex: workspace index
+    :param distribution: flag to divide the data by bin width. It happens only
+        when this flag is False, the workspace contains histogram data, and
+        the mantid configuration is set up to divide such workspaces by bin
+        width. The same effect can be obtained by running the
+        :ref:`algm-ConvertToDistribution` algorithm
+    :param withDy: if True, it will return the error in the "counts", otherwise None
+    :param with Dx: if True, and workspace has them, it will return errors
+        in the x coordinate, otherwise None
+    Note that for workspaces containing bin boundaries, this function will return
+    the bin centers for x.
+    To be used in 1D plots (plot, scatter, errorbar)
+    """
+    x = workspace.readX(wkspIndex)
+    y = workspace.readY(wkspIndex)
+    dy = None
+    dx = None
+
+    if withDy:
+        dy = workspace.readE(wkspIndex)
+    if withDx and workspace.getSpectrum(wkspIndex).hasDx():
+        dx = workspace.readDx(wkspIndex)
+
+    if workspace.isHistogramData():
+        if (not distribution) and (mantid.kernel.config['graph1d.autodistribution'] == 'On'):
+            y = y / (x[1:] - x[0:-1])
+            if dy is not None:
+                dy = dy / (x[1:] - x[0:-1])
+        x = points_from_boundaries(x)
+    y = numpy.ma.masked_invalid(y)
+    if dy is not None:
+        dy = numpy.ma.masked_invalid(dy)
+    return x, y, dy, dx
+
+
+def get_md_data2d_bin_bounds(workspace, normalization):
+    """
+    Function to transform data in an MDHisto workspace with exactly
+    two non-integrated dimension into arrays of bin boundaries in each
+    dimension, and data. To be used in 2D plots (pcolor, pcolorfast, pcolormesh)
+    Note return coordinates are 1d vectors. Use numpy.meshgrid to generate 2d versions
+    """
+    coordinate, data, _ = get_md_data(workspace, normalization, withError=False)
+    assert len(coordinate) == 2, 'The workspace is not 2D'
+    return coordinate[0], coordinate[1], data
+
+
+def get_md_data2d_bin_centers(workspace, normalization):
+    """
+    Function to transform data in an MDHisto workspace with exactly
+    two non-integrated dimension into arrays of bin centers in each
+    dimension, and data. To be used in 2D plots (contour, contourf,
+    tricontour, tricontourf, tripcolor)
+    Note return coordinates are 1d vectors. Use numpy.meshgrid to generate 2d versions
+    """
+    x, y, data = get_md_data2d_bin_bounds(workspace, normalization)
+    x = points_from_boundaries(x)
+    y = points_from_boundaries(y)
+    return x, y, data
+
+
+def boundaries_from_points(input_array):
+    """"
+    The function tries to guess bin boundaries from bin centers
+    :param input_array: a :class:`numpy.ndarray` of bin centers
+    """
+    assert isinstance(input_array, numpy.ndarray), 'Not a numpy array'
+    if len(input_array) == 0:
+        raise ValueError('could not extend array with no elements')
+    if len(input_array) == 1:
+        return numpy.array([input_array[0]-0.5, input_array[0]+0.5])
+    return numpy.concatenate(([(3*input_array[0]-input_array[1])*0.5],
+                             (input_array[1:]+input_array[:-1])*0.5,
+                             [(3*input_array[-1]-input_array[-2])*0.5]))
+
+
+def common_x(arr):
+    """
+    Helper function to check if all rows in a 2d :class:`numpy.ndarray` are identical
+    """
+    return numpy.all(arr == arr[0, :], axis=(1, 0))
+
+
+def get_matrix_2d_data(workspace, distribution, histogram2D=False):
+    '''
+    Get all data from a Matrix workspace that has the same number of bins
+    in every spectrum. It is used for 2D plots
+    :param workspace: Matrix workspace to extract the data from
+    :param distribution: if False, and the workspace contains histogram data,
+        the intensity will be divided by the x bin width
+    :param histogram2D: flag that specifies if the coordinates in the output are
+        -bin centers (such as for contour) for False, or
+        -bin edges (such as for pcolor) for True.
+    Returns x,y,z 2D arrays
+    '''
+    try:
+        _ = workspace.blocksize()
+    except RuntimeError:
+        raise ValueError('The spectra are not the same length. Try using pcolor, pcolorfast, or pcolormesh instead')
+    x = workspace.extractX()
+    y = workspace.getAxis(1).extractValues()
+    z = workspace.extractY()
+
+    if workspace.isHistogramData():
+        if not distribution:
+            z = z / (x[:, 1:] - x[:, 0:-1])
+        if histogram2D:
+            if len(y) == z.shape[0]:
+                y = boundaries_from_points(y)
+            x = numpy.vstack((x, x[-1]))
+        else:
+            x = .5*(x[:, 0:-1]+x[:, 1:])
+            if len(y) == z.shape[0]+1:
+                y = points_from_boundaries(y)
+    else:
+        if histogram2D:
+            if common_x(x):
+                x = numpy.tile(boundaries_from_points(x[0]), z.shape[0]+1).reshape(z.shape[0]+1, -1)
+            else:
+                x = numpy.vstack((x, x[-1]))
+                x = numpy.array([boundaries_from_points(xi) for xi in x])
+            if len(y) == z.shape[0]:
+                y = boundaries_from_points(y)
+        else:
+            if len(y) == z.shape[0]+1:
+                y = points_from_boundaries(y)
+    y = numpy.tile(y, x.shape[1]).reshape(x.shape[1], x.shape[0]).transpose()
+    z = numpy.ma.masked_invalid(z)
+    return x, y, z
+
+
+def get_uneven_data(workspace, distribution):
+    '''
+    Function to get data for uneven workspace2Ds, such as
+    that pcolor, pcolorfast, and pcolormesh will plot axis aligned rectangles
+    :param workspace: a workspace2d
+    :param distribution: if False, and the workspace contains histogram data,
+        the intensity will be divided by the x bin width
+    Returns three lists. Each element in the x list is an array of boundaries
+    for a spectra. Each element in the y list is a 2 element array with the extents
+    of a particular spectra. The z list contains arrays of intensities at bin centers
+    '''
+    z = []
+    x = []
+    y = []
+    nhist = workspace.getNumberHistograms()
+    yvals = workspace.getAxis(1).extractValues()
+    if len(yvals) == nhist:
+        yvals = boundaries_from_points(yvals)
+    for index in range(nhist):
+        xvals = workspace.readX(index)
+        zvals = workspace.readY(index)
+        if workspace.isHistogramData():
+            if not distribution:
+                zvals = zvals / (xvals[1:] - xvals[0:-1])
+        else:
+            xvals = boundaries_from_points(xvals)
+        zvals = numpy.ma.masked_invalid(zvals)
+        z.append(zvals)
+        x.append(xvals)
+        y.append([yvals[index], yvals[index+1]])
+    return x, y, z
+
+
+def get_data_uneven_flag(workspace, **kwargs):
+    '''
+    Helper function that allows :meth:`matplotlib.axes.Axes.pcolor`,
+    :meth:`matplotlib.axes.Axes.pcolorfast`, and :meth:`matplotlib.axes.Axes.pcolormesh`
+    to plot rectangles parallel to the axes even if the data is not
+    on a regular grid.
+    :param workspace: a workspace2d
+    if axisaligned keyword is available and True or if the workspace does
+    not have a constant number of bins, it will return true, otherwise false
+    '''
+    aligned = kwargs.pop('axisaligned', False)
+    try:
+        _ = workspace.blocksize()
+    except RuntimeError:
+        aligned = True
+    return aligned, kwargs
+
+# ====================================================
+# extract logs
+# ====================================================
+
+def get_sample_log(workspace, **kwargs):
+    LogName = kwargs.pop('LogName')
+    ExperimentInfo = kwargs.pop('ExperimentInfo',0)
+    if isinstance(workspace, MDHistoWorkspace):
+        run = workspace.getExperimentInfo(ExperimentInfo).run()
+    else:
+        run = workspace.run()
+    if not run.hasProperty(LogName):
+        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')
+    times = tsp.times.astype('datetime64[us]')
+    y = tsp.value
+    FullTime = kwargs.pop('FullTime', False)
+    StartFromLog = kwargs.pop('StartFromLog', False)
+    if FullTime:
+        x = times.astype(datetime.datetime)
+    else:
+        t0 = times[0]
+        if not StartFromLog:
+            try:
+                t0 = run['proton_charge'].times.astype('datetime64[us]')[0]
+            except:
+                pass #TODO: Maybe raise a warning?
+        x = (times - t0).astype(float) * 1e-6
+    return x, y, FullTime, LogName, units, kwargs
+
+
+# ====================================================
+# Plot functionality
+# ====================================================
+
+
+def get_axes_labels(workspace):
+    """
+    Get axis labels from a Workspace2D or an MDHistoWorkspace
+    Returns a tuple. The first element is the quantity label, such as "Intensity" or "Counts".
+    All other elements in the tuple are labels for axes.
+    Some of them are latex formatted already.
+    :param workspace: :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace`
+    """
+    if isinstance(workspace, MDHistoWorkspace):
+        axes = ['Intensity']
+        dims = workspace.getNonIntegratedDimensions()
+        for d in dims:
+            axis_title = d.name.replace('DeltaE', '$\Delta E$')
+            axis_unit = d.getUnits().replace('Angstrom^-1', '$\AA^{-1}$')
+            axis_unit = axis_unit.replace('DeltaE', 'meV')
+            axis_unit = axis_unit.replace('Angstrom', '$\AA$')
+            axis_unit = axis_unit.replace('MomentumTransfer', '$\AA^{-1}$')
+            axes.append('{0} ({1})'.format(axis_title, axis_unit))
+    else:
+        '''For matrix workspaces, return a tuple of ``(YUnit, <other units>)``'''
+        axes = [workspace.YUnit()]  # TODO: deal with distribution
+        for index in range(workspace.axes()):
+            axis = workspace.getAxis(index)
+            unit = axis.getUnit()
+            if len(str(unit.symbol())) > 0:
+                unit = '{} (${}$)'.format(unit.caption(), unit.symbol().latex())
+            else:
+                unit = unit.caption()
+            axes.append(unit)
+    return tuple(axes)
diff --git a/Framework/PythonInterface/mantid/plots/plotfunctions.py b/Framework/PythonInterface/mantid/plots/plotfunctions.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c16d3a9ce8e45743103d0583bd3bc23bd3638d9
--- /dev/null
+++ b/Framework/PythonInterface/mantid/plots/plotfunctions.py
@@ -0,0 +1,469 @@
+#  This file is part of the mantid package
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program 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.
+#
+#  This program 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/>.
+from __future__ import (absolute_import, division, print_function)
+
+import numpy
+import mantid.kernel
+import mantid.api
+from mantid.plots.helperfunctions import *
+import matplotlib.colors
+import matplotlib.dates as mdates
+
+# ================================================
+# Private 2D Helper functions
+# ================================================
+
+
+def _setLabels1D(axes, workspace):
+    '''
+    helper function to automatically set axes labels for 1D plots
+    '''
+    labels = get_axes_labels(workspace)
+    axes.set_xlabel(labels[1])
+    axes.set_ylabel(labels[0])
+
+
+def _setLabels2D(axes, workspace):
+    '''
+    helper function to automatically set axes labels for 2D plots
+    '''
+    labels = get_axes_labels(workspace)
+    axes.set_xlabel(labels[1])
+    axes.set_ylabel(labels[2])
+
+# ========================================================
+# Plot functions
+# ========================================================
+
+
+def plot(axes, workspace, *args, **kwargs):
+    '''
+    Unpack mantid workspace and render it with matplotlib. ``args`` and
+    ``kwargs`` are passed to :py:meth:`matplotlib.axes.Axes.plot` after special
+    keyword arguments are removed. This will automatically label the
+    line according to the spectrum number unless specified otherwise.
+
+    :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 specNum:   spectrum number to plot if MatrixWorkspace
+    :param wkspIndex: workspace index to plot if MatrixWorkspace
+    :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 workspace is a MatrixWorkspace 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 LogName:   if specified, it will plot the corresponding sample log. The x-axis
+                      of the plot is the time difference between the log time and the first
+                      value of the `proton_charge` log (if available) or the sample log's
+                      first time.
+    :param StartFromLog: False by default. If True the time difference will be from the sample log's
+                      first time, even if `proton_charge` log is available.
+    :param FullTime:  False by default. If true, the full date and time will be plotted on the axis
+                      instead of the time difference
+    :param ExperimentInfo: for MD Workspaces with multiple :class:`mantid.api.ExperimentInfo` is the
+                           ExperimentInfo object from which to extract the log. It's 0 by default
+
+    For matrix workspaces with more than one spectra, either ``specNum`` or ``wkspIndex``
+    needs to be specified. Giving both will generate a :class:`RuntimeError`. There is no similar
+    keyword for MDHistoWorkspaces. These type of workspaces have to have exactly one non integrated
+    dimension
+    '''
+    if 'LogName' in kwargs:
+        (x, y, FullTime, LogName, units, kwargs) = get_sample_log(workspace, **kwargs)
+        axes.set_ylabel('{0} ({1})'.format(LogName, units))
+        axes.set_xlabel('Time (s)')
+        if FullTime:
+            axes.xaxis_date()
+            axes.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S\n%b-%d'))
+            axes.set_xlabel('Time')
+        kwargs['linestyle']='steps-post'
+        return axes.plot(x, y, *args, **kwargs)
+    if isinstance(workspace, mantid.dataobjects.MDHistoWorkspace):
+        (normalization, kwargs) = get_normalization(workspace, **kwargs)
+        (x, y, dy) = get_md_data1d(workspace, normalization)
+    else:
+        (wkspIndex, distribution, kwargs) = get_wksp_index_dist_and_label(workspace, **kwargs)
+        (x, y, dy, dx) = get_spectrum(workspace, wkspIndex, distribution, withDy=False, withDx=False)
+    _setLabels1D(axes, workspace)
+    return axes.plot(x, y, *args, **kwargs)
+
+
+def errorbar(axes, workspace, *args, **kwargs):
+    '''
+    Unpack mantid workspace and render it with matplotlib. ``args`` and
+    ``kwargs`` are passed to :py:meth:`matplotlib.axes.Axes.errorbar` after special
+    keyword arguments are removed. This will automatically label the
+    line according to the spectrum number unless specified otherwise.
+
+    :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 specNum:   spectrum number to plot if MatrixWorkspace
+    :param wkspIndex: workspace index to plot if MatrixWorkspace
+    :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 workspace is a MatrixWorkspace 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
+
+    For matrix workspaces with more than one spectra, either ``specNum`` or ``wkspIndex``
+    needs to be specified. Giving both will generate a :class:`RuntimeError`. There is no similar
+    keyword for MDHistoWorkspaces. These type of workspaces have to have exactly one non integrated
+    dimension
+    '''
+    if isinstance(workspace, mantid.dataobjects.MDHistoWorkspace):
+        (normalization, kwargs) = get_normalization(workspace, **kwargs)
+        (x, y, dy) = get_md_data1d(workspace, normalization)
+        dx = None
+    else:
+        (wkspIndex, distribution, kwargs) = get_wksp_index_dist_and_label(workspace, **kwargs)
+        (x, y, dy, dx) = get_spectrum(workspace, wkspIndex, distribution, withDy=True, withDx=True)
+    _setLabels1D(axes, workspace)
+    return axes.errorbar(x, y, dy, dx, *args, **kwargs)
+
+
+def scatter(axes, workspace, *args, **kwargs):
+    '''
+    Unpack mantid workspace and render it with matplotlib. ``args`` and
+    ``kwargs`` are passed to :py:meth:`matplotlib.axes.Axes.scatter` after special
+    keyword arguments are removed. This will automatically label the
+    line according to the spectrum number unless specified otherwise.
+
+    :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 specNum:   spectrum number to plot if MatrixWorkspace
+    :param wkspIndex: workspace index to plot if MatrixWorkspace
+    :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 workspace is a MatrixWorkspace 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
+
+    For matrix workspaces with more than one spectra, either ``specNum`` or ``wkspIndex``
+    needs to be specified. Giving both will generate a :class:`RuntimeError`. There is no similar
+    keyword for MDHistoWorkspaces. These type of workspaces have to have exactly one non integrated
+    dimension
+    '''
+    if isinstance(workspace, mantid.dataobjects.MDHistoWorkspace):
+        (normalization, kwargs) = get_normalization(workspace, **kwargs)
+        (x, y, _) = get_md_data1d(workspace, normalization)
+    else:
+        (wkspIndex, distribution, kwargs) = get_wksp_index_dist_and_label(workspace, **kwargs)
+        (x, y, _, _) = get_spectrum(workspace, wkspIndex, distribution)
+    _setLabels1D(axes, workspace)
+    return axes.scatter(x, y, *args, **kwargs)
+
+
+def contour(axes, workspace, *args, **kwargs):
+    '''
+    Essentially the same as :meth:`matplotlib.axes.Axes.contour`
+    but calculates the countour levels. Currently this only works with
+    workspaces that have a constant number of bins between spectra.
+
+    :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
+    '''
+    if isinstance(workspace, mantid.dataobjects.MDHistoWorkspace):
+        (normalization, kwargs) = get_normalization(workspace, **kwargs)
+        x, y, z = get_md_data2d_bin_centers(workspace, normalization)
+    else:
+        (distribution, kwargs) = get_distribution(workspace, **kwargs)
+        (x, y, z) = get_matrix_2d_data(workspace, distribution, histogram2D=False)
+    _setLabels2D(axes, workspace)
+    return axes.contour(x, y, z, *args, **kwargs)
+
+
+def contourf(axes, workspace, *args, **kwargs):
+    '''
+    Essentially the same as :meth:`matplotlib.axes.Axes.contourf`
+    but calculates the countour levels. Currently this only works with
+    workspaces that have a constant number of bins between spectra.
+
+    :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
+    '''
+    if isinstance(workspace, mantid.dataobjects.MDHistoWorkspace):
+        (normalization, kwargs) = get_normalization(workspace, **kwargs)
+        x, y, z = get_md_data2d_bin_centers(workspace, normalization)
+    else:
+        (distribution, kwargs) = get_distribution(workspace, **kwargs)
+        (x, y, z) = get_matrix_2d_data(workspace, distribution, histogram2D=False)
+    _setLabels2D(axes, workspace)
+    return axes.contourf(x, y, z, *args, **kwargs)
+
+
+def _pcolorpieces(axes, workspace, distribution, *args, **kwargs):
+    '''
+    Helper function for pcolor, pcolorfast, and pcolormesh that will
+    plot a 2d representation of each spectra. The polycollections or meshes
+    will be normalized to the same intensity limits.
+    :param axes: :class:`matplotlib.axes.Axes` object that will do the plotting
+    :param workspace: :class:`mantid.api.MatrixWorkspace` 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 pcolortype: this keyword allows the plotting to be one of pcolormesh or
+        pcolorfast if there is "mesh" or "fast" in the value of the keyword, or
+        pcolor by default
+    Note: the return is the pcolor, pcolormesh, or pcolorfast of the last spectrum
+    '''
+    (x, y, z) = get_uneven_data(workspace, distribution)
+    pcolortype = kwargs.pop('pcolortype', '')
+    mini = numpy.min([numpy.min(i) for i in z])
+    maxi = numpy.max([numpy.max(i) for i in z])
+    if 'vmin' in kwargs:
+        mini = kwargs['vmin']
+    if 'vmax' in kwargs:
+        maxi = kwargs['vmax']
+    if 'norm' not in kwargs:
+        kwargs['norm'] = matplotlib.colors.Normalize(vmin=mini, vmax=maxi)
+    else:
+        if kwargs['norm'].vmin is None:
+            kwargs['norm'].vmin = mini
+        if kwargs['norm'].vmax is None:
+            kwargs['norm'].vmax = maxi
+    for xi, yi, zi in zip(x, y, z):
+        XX, YY = numpy.meshgrid(xi, yi, indexing='ij')
+        if 'mesh' in pcolortype.lower():
+            cm = axes.pcolormesh(XX, YY, zi.reshape(-1, 1), **kwargs)
+        elif 'fast' in pcolortype.lower():
+            cm = axes.pcolorfast(XX, YY, zi.reshape(-1, 1), **kwargs)
+        else:
+            cm = axes.pcolor(XX, YY, zi.reshape(-1, 1), **kwargs)
+    return cm
+
+
+def pcolor(axes, workspace, *args, **kwargs):
+    '''
+    Essentially the same as :meth:`matplotlib.axes.Axes.pcolor`
+
+    :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:
+        (aligned, kwargs) = get_data_uneven_flag(workspace, **kwargs)
+        (distribution, kwargs) = get_distribution(workspace, **kwargs)
+        if aligned:
+            kwargs['pcolortype'] = ''
+            return _pcolorpieces(axes, workspace, distribution, *args, **kwargs)
+        else:
+            (x, y, z) = get_matrix_2d_data(workspace, distribution, histogram2D=True)
+    return axes.pcolor(x, y, z, *args, **kwargs)
+
+
+def pcolorfast(axes, workspace, *args, **kwargs):
+    '''
+    Essentially the same as :meth:`matplotlib.axes.Axes.pcolorfast`
+
+    :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:
+        (aligned, kwargs) = get_data_uneven_flag(workspace, **kwargs)
+        (distribution, kwargs) = get_distribution(workspace, **kwargs)
+        if aligned:
+            kwargs['pcolortype'] = 'fast'
+            return _pcolorpieces(axes, workspace, distribution, *args, **kwargs)
+        else:
+            (x, y, z) = get_matrix_2d_data(workspace, distribution, histogram2D=True)
+    return axes.pcolorfast(x, y, z, *args, **kwargs)
+
+
+def pcolormesh(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:
+        (aligned, kwargs) = get_data_uneven_flag(workspace, **kwargs)
+        (distribution, kwargs) = get_distribution(workspace, **kwargs)
+        if aligned:
+            kwargs['pcolortype'] = 'mesh'
+            return _pcolorpieces(axes, workspace, distribution, *args, **kwargs)
+        else:
+            (x, y, z) = get_matrix_2d_data(workspace, distribution, histogram2D=True)
+
+    return axes.pcolormesh(x, y, z, *args, **kwargs)
+
+
+def tripcolor(axes, workspace, *args, **kwargs):
+    '''
+    To be used with non-uniform grids. Currently this only works with workspaces
+    that have a constant number of bins between spectra or with
+    MDHistoWorkspaces.
+
+    :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
+
+    See :meth:`matplotlib.axes.Axes.tripcolor` for more information.
+    '''
+    if isinstance(workspace, mantid.dataobjects.MDHistoWorkspace):
+        (normalization, kwargs) = get_normalization(workspace, **kwargs)
+        x_temp, y_temp, z = get_md_data2d_bin_centers(workspace, normalization)
+        x, y = numpy.meshgrid(x_temp, y_temp)
+    else:
+        (distribution, kwargs) = get_distribution(workspace, **kwargs)
+        (x, y, z) = get_matrix_2d_data(workspace, distribution, histogram2D=False)
+    _setLabels2D(axes, workspace)
+
+    return axes.tripcolor(x.ravel(), y.ravel(), z.ravel(), *args, **kwargs)
+
+
+def tricontour(axes, workspace, *args, **kwargs):
+    '''
+    Essentially the same as :meth:`mantid.plots.contour`, but works
+    for non-uniform grids. Currently this only works with workspaces
+    that have a constant number of bins between spectra or with
+    MDHistoWorkspaces.
+
+    :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
+
+    See :meth:`matplotlib.axes.Axes.tricontour` for more information.
+    '''
+    if isinstance(workspace, mantid.dataobjects.MDHistoWorkspace):
+        (normalization, kwargs) = get_normalization(workspace, **kwargs)
+        (x_temp, y_temp, z) = get_md_data2d_bin_centers(workspace, normalization)
+        (x, y) = numpy.meshgrid(x_temp, y_temp)
+    else:
+        (distribution, kwargs) = get_distribution(workspace, **kwargs)
+        (x, y, z) = get_matrix_2d_data(workspace, distribution, histogram2D=False)
+    _setLabels2D(axes, workspace)
+    # tricontour segfaults if many z values are not finite
+    # https://github.com/matplotlib/matplotlib/issues/10167
+    x = x.ravel()
+    y = y.ravel()
+    z = z.ravel()
+    condition = numpy.isfinite(z)
+    x = x[condition]
+    y = y[condition]
+    z = z[condition]
+    return axes.tricontour(x, y, z, *args, **kwargs)
+
+
+def tricontourf(axes, workspace, *args, **kwargs):
+    '''
+    Essentially the same as :meth:`mantid.plots.contourf`, but works
+    for non-uniform grids. Currently this only works with workspaces
+    that have a constant number of bins between spectra or with
+    MDHistoWorkspaces.
+
+    :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
+
+    See :meth:`matplotlib.axes.Axes.tricontourf` for more information.
+    '''
+    if isinstance(workspace, mantid.dataobjects.MDHistoWorkspace):
+        (normalization, kwargs) = get_normalization(workspace, **kwargs)
+        (x_temp, y_temp, z) = get_md_data2d_bin_centers(workspace, normalization)
+        (x, y) = numpy.meshgrid(x_temp, y_temp)
+    else:
+        (distribution, kwargs) = get_distribution(workspace, **kwargs)
+        (x, y, z) = get_matrix_2d_data(workspace, distribution, histogram2D=False)
+    _setLabels2D(axes, workspace)
+    # tricontourf segfaults if many z values are not finite
+    # https://github.com/matplotlib/matplotlib/issues/10167
+    x = x.ravel()
+    y = y.ravel()
+    z = z.ravel()
+    condition = numpy.isfinite(z)
+    x = x[condition]
+    y = y[condition]
+    z = z[condition]
+    return axes.tricontourf(x, y, z, *args, **kwargs)
+
diff --git a/Framework/PythonInterface/mantid/plots/plotfunctions3D.py b/Framework/PythonInterface/mantid/plots/plotfunctions3D.py
new file mode 100644
index 0000000000000000000000000000000000000000..f370dbcbc75fbcf0b5f04f3ef667f78bdb4e6273
--- /dev/null
+++ b/Framework/PythonInterface/mantid/plots/plotfunctions3D.py
@@ -0,0 +1,165 @@
+#  This file is part of the mantid package
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program 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.
+#
+#  This program 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/>.
+from __future__ import (absolute_import, division, print_function)
+
+import numpy
+
+from mantid.plots.helperfunctions import (get_axes_labels, get_normalization, get_distribution,
+                                          get_md_data2d_bin_centers, get_matrix_2d_data, get_md_data1d,
+                                          get_wksp_index_dist_and_label, get_spectrum)
+import mantid.dataobjects
+
+
+def _set_labels_3d(axes, workspace):
+    """
+    Helper function to automatically set axis labels for 3D plots
+    """
+    labels = get_axes_labels(workspace)
+    axes.set_xlabel(labels[1])
+    axes.set_ylabel(labels[2])
+    axes.set_zlabel(labels[0])
+
+
+def _extract_3d_data(workspace, **kwargs):
+    if isinstance(workspace, mantid.dataobjects.MDHistoWorkspace):
+        normalization, kwargs = get_normalization(workspace, **kwargs)
+        x_temp, y_temp, z = get_md_data2d_bin_centers(workspace, normalization)
+        x, y = numpy.meshgrid(x_temp, y_temp)
+    else:
+        distribution, kwargs = get_distribution(workspace, **kwargs)
+        x, y, z = get_matrix_2d_data(workspace, distribution, histogram2D=False)
+    return x, y, z
+
+
+def plot(axes, workspace, *args, **kwargs):
+    '''
+    3D plots - line plots
+
+    :param axes: class:`matplotlib.axes.Axes3D` object that will do the plotting
+    :param workspace: :class:`mantid.api.MatrixWorkspace` or
+                      :class:`mantid.api.IMDHistoWorkspace` to extract the data from
+    :param zdir: Which direction to use as z ('x', 'y' or 'z') when plotting a 2D set.
+    '''
+    if isinstance(workspace, mantid.dataobjects.MDHistoWorkspace):
+        (normalization, kwargs) = get_normalization(workspace, **kwargs)
+        (x, y, z) = get_md_data1d(workspace, normalization)
+    else:
+        (wksp_index, distribution, kwargs) = get_wksp_index_dist_and_label(workspace, **kwargs)
+        (x, z, _, _) = get_spectrum(workspace, wksp_index, distribution, withDy=False, withDx=False)
+        y_val = workspace.getAxis(1).extractValues()[wksp_index]
+        y = [y_val for _ in range(len(x))]  # fill x size array with y value
+        _set_labels_3d(axes, workspace)
+    return axes.plot(x, y, z, *args, **kwargs)
+
+
+def scatter(axes, workspace, *args, **kwargs):
+    '''
+    Scatter plots
+
+    :param axes: class:`matplotlib.axes.Axes3D` object that will do the plotting
+    :param workspace: :class:`mantid.api.MatrixWorkspace` or :class:`mantid.api.IMDHistoWorkspace`
+                      to extract the data from
+    :param zdir:	Which direction to use as z ('x', 'y' or 'z') when plotting a 2D set.
+    :param s:	Size in points^2. It is a scalar or an array of the same length as x and y.
+    :param c:	A color. c can be a single color format string, or a sequence of color
+                specifications of length N, or a sequence of N numbers to be mapped to
+                colors using the cmap and norm specified via kwargs (see below). Note
+                that c should not be a single numeric RGB or RGBA sequence because that
+                is indistinguishable from an array of values to be colormapped.
+                c can be a 2-D array in which the rows are RGB or RGBA, however, including
+                the case of a single row to specify the same color for all points.
+    :param depthshade:	Whether or not to shade the scatter markers to give the appearance
+                        of depth. Default is True.
+    '''
+    x, y, z = _extract_3d_data(workspace, **kwargs)
+    _set_labels_3d(axes, workspace)
+    return axes.scatter(x, y, z, *args, **kwargs)
+
+
+def plot_wireframe(axes, workspace, *args, **kwargs):
+    '''
+    Wire-frame plot
+
+    :param axes: class:`matplotlib.axes.Axes3D` object that will do the plotting
+    :param workspace: :class:`mantid.api.MatrixWorkspace` or
+                      :class:`mantid.api.IMDHistoWorkspace` to extract the data from
+    :param rstride: Array row stride (step size), defaults to 1
+    :param cstride: Array column stride (step size), defaults to 1
+    :param rcount:	Use at most this many rows, defaults to 50
+    :param ccount:	Use at most this many columns, defaults to 50
+    '''
+    x, y, z = _extract_3d_data(workspace, **kwargs)
+    _set_labels_3d(axes, workspace)
+    return axes.plot_wireframe(x, y, z, *args, **kwargs)
+
+
+def plot_surface(axes, workspace, *args, **kwargs):
+    '''
+    Surface plots
+
+    :param axes: class:`matplotlib.axes.Axes3D` object that will do the plotting
+    :param workspace: :class:`mantid.api.MatrixWorkspace` or
+                      :class:`mantid.api.IMDHistoWorkspace` to extract the data from
+    :param rstride: Array row stride (step size), defaults to 1
+    :param cstride: Array column stride (step size), defaults to 1
+    :param rcount:	Use at most this many rows, defaults to 50
+    :param ccount:	Use at most this many columns, defaults to 50
+    :param color:	Color of the surface patches
+    :param cmap:	A colormap for the surface patches.
+    :param norm:	An instance of Normalize to map values to colors
+    :param vmin:	Minimum value to map
+    :param vmax:	Maximum value to map
+    :param shade:	Whether to shade the facecolors
+    :param facecolors:	Face colors for the individual patches
+    '''
+    x, y, z = _extract_3d_data(workspace, **kwargs)
+    _set_labels_3d(axes, workspace)
+    return axes.plot_surface(x, y, z, *args, **kwargs)
+
+
+def contour(axes, workspace, *args, **kwargs):
+    '''
+    Contour plots
+
+    :param axes: class:`matplotlib.axes.Axes3D` object that will do the plotting
+    :param workspace: :class:`mantid.api.MatrixWorkspace` or
+                      :class:`mantid.api.IMDHistoWorkspace` to extract the data from
+    :param extend3d:	Whether to extend contour in 3D (default: False)
+    :param stride:	Stride (step size) for extending contour
+    :param zdir:	The direction to use: x, y or z (default)
+    :param offset:	If specified plot a projection of the contour lines
+                    on this position in plane normal to zdir
+    '''
+    x, y, z = _extract_3d_data(workspace, **kwargs)
+    _set_labels_3d(axes, workspace)
+    return axes.contour(x, y, z, *args, **kwargs)
+
+
+def contourf(axes, workspace, *args, **kwargs):
+    '''
+    Filled Contour plots
+
+    :param axes: class:`matplotlib.axes.Axes3D` object that will do the plotting
+    :param workspace: :class:`mantid.api.MatrixWorkspace` or
+                      :class:`mantid.api.IMDHistoWorkspace` to extract the data from
+    :param zdir:	The direction to use: x, y or z (default)
+    :param offset:	If specified plot a projection of the filled contour on this
+                    position in plane normal to zdir
+    '''
+    x, y, z = _extract_3d_data(workspace, **kwargs)
+    _set_labels_3d(axes, workspace)
+    return axes.contourf(x, y, z, *args, **kwargs)
diff --git a/Framework/PythonInterface/plugins/algorithms/AlignAndFocusPowderFromFiles.py b/Framework/PythonInterface/plugins/algorithms/AlignAndFocusPowderFromFiles.py
index b93252bfc570c3f9300b8a9e5e5870dec1984b2c..4faee2742fa410fcdd995eb10eb5940ccb9d4091 100644
--- a/Framework/PythonInterface/plugins/algorithms/AlignAndFocusPowderFromFiles.py
+++ b/Framework/PythonInterface/plugins/algorithms/AlignAndFocusPowderFromFiles.py
@@ -1,6 +1,6 @@
 from __future__ import (absolute_import, division, print_function)
 
-from mantid.api import mtd, AlgorithmFactory, DataProcessorAlgorithm, ITableWorkspaceProperty, \
+from mantid.api import mtd, AlgorithmFactory, DistributedDataProcessorAlgorithm, ITableWorkspaceProperty, \
     MatrixWorkspaceProperty, MultipleFileProperty, PropertyMode
 from mantid.kernel import ConfigService, Direction
 from mantid.simpleapi import AlignAndFocusPowder, CompressEvents, ConvertUnits, CreateCacheFilename, \
@@ -52,7 +52,7 @@ def determineChunking(filename, chunkSize):
     return strategy
 
 
-class AlignAndFocusPowderFromFiles(DataProcessorAlgorithm):
+class AlignAndFocusPowderFromFiles(DistributedDataProcessorAlgorithm):
     def category(self):
         return "Diffraction\\Reduction"
 
diff --git a/Framework/PythonInterface/plugins/algorithms/ApplyDetectorScanEffCorr.py b/Framework/PythonInterface/plugins/algorithms/ApplyDetectorScanEffCorr.py
new file mode 100644
index 0000000000000000000000000000000000000000..b0579735421a4cb5a149be264165d3507364e59c
--- /dev/null
+++ b/Framework/PythonInterface/plugins/algorithms/ApplyDetectorScanEffCorr.py
@@ -0,0 +1,66 @@
+from __future__ import print_function
+
+from mantid.simpleapi import CreateWorkspace, RenameWorkspace
+from mantid.api import AlgorithmFactory, PropertyMode, PythonAlgorithm, WorkspaceProperty
+from mantid.kernel import Direction
+import numpy as np
+
+
+class ApplyDetectorScanEffCorr(PythonAlgorithm):
+
+    def category(self):
+        return 'ILL\\Diffraction;Diffraction\\Utility'
+
+    def name(self):
+        return 'ApplyDetectorScanEffCorr'
+
+    def summary(self):
+        return 'Applies the calibration workspace generated by PowderDiffILLDetEffCorr to data loaded with LoadILLDiffraction.'
+
+    def PyInit(self):
+        self.declareProperty(WorkspaceProperty("InputWorkspace", "",
+                                               optional=PropertyMode.Mandatory,
+                                               direction=Direction.Input),
+                             "The workspace for the detector efficiency correction to be applied to.")
+        self.declareProperty(WorkspaceProperty("DetectorEfficiencyWorkspace", "",
+                                               optional=PropertyMode.Mandatory,
+                                               direction=Direction.Input),
+                             "The workspace containing the detector efficiency correction factors generated by PowderDiffILLDetEffCorr.")
+        self.declareProperty(WorkspaceProperty("OutputWorkspace", "",
+                                               optional=PropertyMode.Mandatory,
+                                               direction=Direction.Output),
+                             "The output workspace with the calibrated data. Optionally can be the same as the input workspace.")
+
+    def PyExec(self):
+        input_ws = self.getProperty("InputWorkspace").value
+        efficiency_workspace = self.getProperty("DetectorEfficiencyWorkspace").value
+
+        y_values = input_ws.extractY()
+        y_values = y_values.reshape(y_values.size)
+        e_values = input_ws.extractE()
+        e_values = e_values.reshape(e_values.size)
+
+        efficiency_values = efficiency_workspace.extractY()
+        efficiency_values = efficiency_values.reshape(efficiency_values.size)
+
+        detector_info = input_ws.detectorInfo()
+        for i in range(detector_info.size()):
+            if detector_info.isMonitor(i):
+                efficiency_values = np.insert(efficiency_values, 0, 1) # add the monitor efficiency
+
+        if (y_values.size % efficiency_values.size) is not 0:
+            raise ValueError('Number of entries in input workspace is not a multiple of number of efficiencies in detector efficiency '
+                             'workspace.')
+        number_time_indexes = y_values.size / efficiency_values.size
+
+        full_efficiency_values = np.repeat(efficiency_values, number_time_indexes)
+        y_values *= full_efficiency_values
+        e_values *= full_efficiency_values
+
+        __output_ws = CreateWorkspace(DataX=input_ws.extractX(), DataY=y_values, DataE=e_values, Nspec=y_values.size,
+                                      ParentWorkspace=input_ws)
+
+        RenameWorkspace(__output_ws, self.getPropertyValue("OutputWorkspace"))
+        self.setProperty("OutputWorkspace", __output_ws)
+
+AlgorithmFactory.subscribe(ApplyDetectorScanEffCorr)
diff --git a/Framework/PythonInterface/plugins/algorithms/GSASIIRefineFitPeaks.py b/Framework/PythonInterface/plugins/algorithms/GSASIIRefineFitPeaks.py
index a26c2db621ae78d0329777f15a0f77275f441db5..93aa61dbf9ff9a7f7eeeaf643d1ca636e24c1f67 100644
--- a/Framework/PythonInterface/plugins/algorithms/GSASIIRefineFitPeaks.py
+++ b/Framework/PythonInterface/plugins/algorithms/GSASIIRefineFitPeaks.py
@@ -21,24 +21,24 @@ class GSASIIRefineFitPeaks(PythonAlgorithm):
     PROP_GSAS_PROJ_PATH = "SaveGSASIIProjectFile"
     PROP_INPUT_WORKSPACE = "InputWorkspace"
     PROP_OUT_FITTED_PEAKS_WS = "OutputWorkspace"
+    PROP_OUT_GAMMA = "Gamma"
     PROP_OUT_GROUP_RESULTS = "Results"
     PROP_OUT_LATTICE_PARAMS = "LatticeParameters"
-    PROP_OUT_RESIDUALS = "ResidualsTable"
+    PROP_OUT_RWP = "Rwp"
+    PROP_OUT_SIGMA = "Sigma"
     PROP_PATH_TO_GSASII = "PathToGSASII"
     PROP_PATH_TO_INST_PARAMS = "InstrumentFile"
-    PROP_PATH_TO_PHASE = "PhaseInfoFile"
+    PROP_PATHS_TO_PHASE_FILES = "PhaseInfoFiles"
     PROP_PAWLEY_DMIN = "PawleyDMin"
     PROP_PAWLEY_NEGATIVE_WEIGHT = "PawleyNegativeWeight"
+    PROP_REFINE_GAMMA = "RefineGamma"
+    PROP_REFINE_SIGMA = "RefineSigma"
     PROP_REFINEMENT_METHOD = "RefinementMethod"
     PROP_SUPPRESS_GSAS_OUTPUT = "MuteGSASII"
     PROP_WORKSPACE_INDEX = "WorkspaceIndex"
     PROP_XMAX = "XMax"
     PROP_XMIN = "XMin"
 
-    DEFAULT_REFINEMENT_PARAMS = {"set":
-                                 {"Background": {"no.coeffs": 3,
-                                                 "refine": True},
-                                  "Sample Parameters": ["Scale"]}}
     LATTICE_TABLE_PARAMS = ["length_a", "length_b", "length_c", "angle_alpha", "angle_beta", "angle_gamma", "volume"]
     REFINEMENT_METHODS = ["Pawley refinement", "Rietveld refinement", "Peak fitting"]
 
@@ -53,10 +53,10 @@ class GSASIIRefineFitPeaks(PythonAlgorithm):
                 "using GSAS-II scriptable API")
 
     def validateInputs(self):
-        x_min = self.getProperty(self.PROP_XMIN).value
-        x_max = self.getProperty(self.PROP_XMAX).value
+        x_min = self.getProperty(self.PROP_XMIN)
+        x_max = self.getProperty(self.PROP_XMAX)
 
-        if x_max <= x_min:
+        if not x_max.isDefault and x_max.value <= x_min.value:
             return {self.PROP_XMAX: "{} must be greater than {}".format(self.PROP_XMAX, self.PROP_XMIN)}
 
         return {}
@@ -73,8 +73,8 @@ class GSASIIRefineFitPeaks(PythonAlgorithm):
                                  "(ie the only one for a focused workspace) is used")
         self.declareProperty(FileProperty(name=self.PROP_PATH_TO_INST_PARAMS, defaultValue="", action=FileAction.Load,
                                           extensions=[".prm"]), doc="Location of the phase file")
-        self.declareProperty(FileProperty(name=self.PROP_PATH_TO_PHASE, defaultValue="", action=FileAction.Load,
-                                          extensions=[".cif"]), doc="Location of the phase file")
+        self.declareProperty(MultipleFileProperty(name=self.PROP_PATHS_TO_PHASE_FILES, extensions=[".cif"]),
+                             doc="Paths to each required phase file")
         self.declareProperty(FileProperty(name=self.PROP_PATH_TO_GSASII, defaultValue="", action=FileAction.Directory),
                              doc="Path to the directory containing GSASII executable on the user's machine")
 
@@ -84,24 +84,28 @@ class GSASIIRefineFitPeaks(PythonAlgorithm):
         self.declareProperty(name=self.PROP_XMAX, defaultValue=0.0, direction=Direction.Input,
                              doc="Maximum x value to use for refinement, in the same units as the input workspace. " +
                                  "Leave blank to refine in the range {} to the end of the range".format(self.PROP_XMIN))
+        self.declareProperty(name=self.PROP_REFINE_SIGMA, defaultValue=False, direction=Direction.Input,
+                             doc="Whether to refine the sigma-1 profile coefficient")
+        self.declareProperty(name=self.PROP_REFINE_GAMMA, defaultValue=False, direction=Direction.Input,
+                             doc="Whether to refine the gamma-1 (called 'X' in GSAS-II) profile coefficient")
 
         self.declareProperty(WorkspaceProperty(name=self.PROP_OUT_FITTED_PEAKS_WS, defaultValue="",
                                                direction=Direction.Output), doc="Workspace with fitted peaks")
-        self.declareProperty(ITableWorkspaceProperty(name=self.PROP_OUT_RESIDUALS, direction=Direction.Output,
-                                                     defaultValue=self.PROP_OUT_RESIDUALS),
-                             doc="Table containing residual values for the fit. "
-                                 "Currently this contains goodness-of-fit (Chi squared, GoF) and weight-profile "
-                                 "R-factor discrepancy index (Rwp)")
-
         self.declareProperty(ITableWorkspaceProperty(name=self.PROP_OUT_LATTICE_PARAMS, direction=Direction.Output,
                                                      defaultValue=self.PROP_OUT_LATTICE_PARAMS),
                              doc="Table to output the lattice parameters (refined)")
+        self.declareProperty(name=self.PROP_OUT_RWP, direction=Direction.Output, defaultValue=0.0,
+                             doc="Weighted profile R factor (as a percentage)")
+        self.declareProperty(name=self.PROP_OUT_SIGMA, direction=Direction.Output, defaultValue=0.0,
+                             doc="Sigma-1 profile coefficient")
+        self.declareProperty(name=self.PROP_OUT_GAMMA, direction=Direction.Output, defaultValue=0.0,
+                             doc="Gamma-1 profile coefficient (called X in GSAS-II)")
         self.declareProperty(FileProperty(name=self.PROP_GSAS_PROJ_PATH, defaultValue="", action=FileAction.Save,
                                           extensions=".gpx"), doc="GSASII Project to work on")
 
         self.setPropertyGroup(self.PROP_OUT_FITTED_PEAKS_WS, self.PROP_OUT_GROUP_RESULTS)
-        self.setPropertyGroup(self.PROP_OUT_RESIDUALS, self.PROP_OUT_GROUP_RESULTS)
         self.setPropertyGroup(self.PROP_OUT_LATTICE_PARAMS, self.PROP_OUT_GROUP_RESULTS)
+        self.setPropertyGroup(self.PROP_OUT_RWP, self.PROP_OUT_GROUP_RESULTS)
         self.setPropertyGroup(self.PROP_GSAS_PROJ_PATH, self.PROP_OUT_GROUP_RESULTS)
 
         self.declareProperty(name=self.PROP_PAWLEY_DMIN, defaultValue=1.0, direction=Direction.Input,
@@ -127,12 +131,14 @@ class GSASIIRefineFitPeaks(PythonAlgorithm):
         with self._suppress_stdout():
             gsas_proj = self._initialise_GSAS()
 
-            residuals, lattice_params = \
+            rwp, lattice_params = \
                 self._run_rietveld_pawley_refinement(gsas_proj=gsas_proj,
                                                      do_pawley=refinement_method == self.REFINEMENT_METHODS[0])
 
-            self._set_output_properties(lattice_params=lattice_params, residuals=residuals,
-                                        fitted_peaks_ws=self._generate_fitted_peaks_ws(gsas_proj))
+            self._set_output_properties(lattice_params=lattice_params, rwp=rwp,
+                                        fitted_peaks_ws=self._generate_fitted_peaks_ws(gsas_proj),
+                                        gamma=gsas_proj.values()[5]["Instrument Parameters"][0]["X"][1],
+                                        sigma=gsas_proj.values()[5]["Instrument Parameters"][0]["sig-1"][1])
 
     def _build_output_lattice_table(self, lattice_params):
         table_name = self.getPropertyValue(self.PROP_OUT_LATTICE_PARAMS)
@@ -144,16 +150,30 @@ class GSASIIRefineFitPeaks(PythonAlgorithm):
         table.addRow([float(lattice_params[param]) for param in self.LATTICE_TABLE_PARAMS])
         return table
 
-    def _create_refinement_params_dict(self):
-        refinement_params = self.DEFAULT_REFINEMENT_PARAMS
+    def _create_refinement_params_dict(self, num_phases):
+        basic_refinement = {"set": {"Background": {"no.coeffs": 3, "refine": True},
+                                    "Sample Parameters": ["Scale"]}}
 
         x_max = self.getProperty(self.PROP_XMAX).value
-
         if x_max:
             x_min = self.getProperty(self.PROP_XMIN).value
-            refinement_params["set"].update({"Limits": [x_min, x_max]})
+            basic_refinement["set"].update({"Limits": [x_min, x_max]})
+
+        scale_refinement = {"set": {"Scale": True},
+                            "phases": range(1, num_phases)}
+        unit_cell_refinement = {"set": {"Cell": True}}
 
-        return refinement_params
+        profile_coeffs_refinement = {"set": {"Instrument Parameters": []}}
+
+        refine_sigma = self.getProperty(self.PROP_REFINE_SIGMA).value
+        if refine_sigma:
+            profile_coeffs_refinement["set"]["Instrument Parameters"].append("sig-1")
+
+        refine_gamma = self.getProperty(self.PROP_REFINE_GAMMA).value
+        if refine_gamma:
+            profile_coeffs_refinement["set"]["Instrument Parameters"].append("X")
+
+        return [basic_refinement, scale_refinement, unit_cell_refinement, profile_coeffs_refinement, {}]
 
     def _extract_spectrum_from_workspace(self):
         """
@@ -171,16 +191,6 @@ class GSASIIRefineFitPeaks(PythonAlgorithm):
 
         return spectrum
 
-    def _generate_residuals_table(self, rwp, gof):
-        table_name = self.getPropertyValue(self.PROP_OUT_RESIDUALS)
-        table = mantid.CreateEmptyTableWorkspace(OutputWorkspace=table_name, StoreInADS=False)
-
-        table.addColumn("double", "Rwp")
-        table.addColumn("double", "GoF")
-
-        table.addRow([rwp, gof])
-        return table
-
     def _generate_fitted_peaks_ws(self, gsas_proj):
         input_ws = self.getPropertyValue(self.PROP_INPUT_WORKSPACE)
         fitted_peaks_ws_name = self.getPropertyValue(self.PROP_OUT_FITTED_PEAKS_WS)
@@ -214,7 +224,7 @@ class GSASIIRefineFitPeaks(PythonAlgorithm):
         spectrum_path = self._save_temporary_fxye(spectrum=spectrum)
 
         inst_param_path = self.getPropertyValue(self.PROP_PATH_TO_INST_PARAMS)
-        gsas_proj.add_powder_histogram(datafile=spectrum_path, iparams=inst_param_path)
+        gsas_proj.add_powder_histogram(datafile=spectrum_path, iparams=inst_param_path, fmthint="xye")
 
         self._remove_temporary_fxye(spectrum_path=spectrum_path)
 
@@ -236,22 +246,26 @@ class GSASIIRefineFitPeaks(PythonAlgorithm):
         :param do_pawley: True if doing a Pawley refinement (the default), False if doing a Rietveld refinement
         :return: (R weighted profile, goodness-of-fit coefficient, table containing refined lattice parameters)
         """
-        phase_path = self.getPropertyValue(self.PROP_PATH_TO_PHASE)
-        phase = gsas_proj.add_phase(phasefile=phase_path, histograms=[gsas_proj.histograms()[0]])
-
-        if do_pawley:
-            self._set_pawley_phase_parameters(phase)
-
-        gsas_proj.set_refinement(refinement=self._create_refinement_params_dict())
-        gsas_proj.do_refinements([{}])
-
-        residuals = gsas_proj.values()[2]["data"]["Rvals"]
+        phase_paths = self.getPropertyValue(self.PROP_PATHS_TO_PHASE_FILES).split(",")
+        refinements = self._create_refinement_params_dict(num_phases=len(phase_paths))
+        prog = Progress(self, start=0, end=1, nreports=len(refinements) + 1)
+
+        prog.report("Reading phase files")
+        for phase_path in phase_paths:
+            phase = gsas_proj.add_phase(phasefile=phase_path, histograms=[gsas_proj.histograms()[0]])
+            if do_pawley:
+                self._set_pawley_phase_parameters(phase)
+
+        for i, refinement in enumerate(refinements):
+            prog.report("Step {} of refinement recipe".format(i + 1))
+            gsas_proj.do_refinements([refinement])
+        gsas_proj.save()
+
+        rwp = gsas_proj.histogram(0).get_wR()
         lattice_params = gsas_proj.phases()[0].get_cell()
         lattice_params_table = self._build_output_lattice_table(lattice_params)
 
-        residuals_table = self._generate_residuals_table(rwp=residuals["Rwp"], gof=residuals["GOF"])
-
-        return residuals_table, lattice_params_table
+        return rwp, lattice_params_table
 
     def _save_temporary_fxye(self, spectrum):
         """
@@ -268,10 +282,12 @@ class GSASIIRefineFitPeaks(PythonAlgorithm):
         mantid.SaveFocusedXYE(Filename=file_path, InputWorkspace=spectrum, SplitFiles=False, IncludeHeader=False)
         return file_path
 
-    def _set_output_properties(self, fitted_peaks_ws, residuals, lattice_params):
+    def _set_output_properties(self, fitted_peaks_ws, rwp, lattice_params, sigma, gamma):
         self.setProperty(self.PROP_OUT_FITTED_PEAKS_WS, fitted_peaks_ws)
-        self.setProperty(self.PROP_OUT_RESIDUALS, residuals)
+        self.setProperty(self.PROP_OUT_RWP, rwp)
         self.setProperty(self.PROP_OUT_LATTICE_PARAMS, lattice_params)
+        self.setProperty(self.PROP_OUT_GAMMA, gamma)
+        self.setProperty(self.PROP_OUT_SIGMA, sigma)
 
     def _set_pawley_phase_parameters(self, phase):
         # Note from GSAS-II doc: "you probably should clear the Histogram scale factor refinement
diff --git a/Framework/PythonInterface/plugins/algorithms/MuonMaxent.py b/Framework/PythonInterface/plugins/algorithms/MuonMaxent.py
new file mode 100644
index 0000000000000000000000000000000000000000..2ede9effc741e6184b06ff5121d5dd75bef57738
--- /dev/null
+++ b/Framework/PythonInterface/plugins/algorithms/MuonMaxent.py
@@ -0,0 +1,441 @@
+from __future__ import (absolute_import, division,print_function)
+import numpy as np
+import math
+from Muon.MaxentTools.multimaxalpha import MULTIMAX
+from mantid.api import *
+from mantid.kernel import *
+from mantid.simpleapi import *
+
+
+"""
+ input params from MuonMaxEnt
+ RunNo (int)
+ Frames (int)
+ Ires (int) = resolution in ps
+ Tzeroch (int)
+ firstgoodch (int)
+ fitphase (int==bool)
+ fitdt (int==bool)
+ deflevel (real)
+ sigloose(real)
+ ptstofit(int)
+ histolen(int)
+ nhisto(int)
+ counts (int array)
+
+ outputs from MuonMaxEnt
+ fchan (real array)
+ taud (real array)
+ phi (real array)
+"""
+
+
+class MuonMaxent(PythonAlgorithm):
+
+    def category(self):
+        return "Muon;Arithmetic\\FFT"
+
+    def PyInit(self):
+        self.declareProperty(
+            WorkspaceProperty("InputWorkspace",
+                              "",
+                              direction=Direction.Input,
+                              validator=RawCountValidator(True)),
+            doc="Raw muon workspace to process")
+        self.declareProperty(
+            ITableWorkspaceProperty("InputPhaseTable",
+                                    "",
+                                    direction=Direction.Input,
+                                    optional=PropertyMode.Optional),
+            doc="Phase table (initial guess)")  # from CalMuonDetectorPhases
+        self.declareProperty(
+            ITableWorkspaceProperty("InputDeadTimeTable",
+                                    "",
+                                    direction=Direction.Input,
+                                    optional=PropertyMode.Optional),
+            doc="Dead time table (initial)")  # from LoadMuonNexus or blanl=k
+        self.declareProperty(
+            ITableWorkspaceProperty("GroupTable",
+                                    "",
+                                    direction=Direction.Input,
+                                    optional=PropertyMode.Optional),
+            doc="Group Table")  # from LoadMuonNexus, none=do all spectra individually
+        self.declareProperty(
+            WorkspaceProperty("GroupWorkspace",
+                              "",
+                              direction=Direction.Input,
+                              optional=PropertyMode.Optional),
+            doc="Group Workspace")  # from LoadDetectorsGroupingTable, none=do all spectra individually
+        self.declareProperty("FirstGoodTime", 0.1, doc="First good data time")
+        self.declareProperty("LastGoodTime", 33.0, doc="Last good data time")
+        self.declareProperty(
+            "Npts", 2, doc="Number of frequency points to fit (should be power of 2)",
+            validator=IntListValidator([2**i for i in range(8, 21)]))
+        self.declareProperty(
+            "MaxField",
+            1000.0,
+            doc="Maximum field for spectrum")
+        self.declareProperty(
+            "FixPhases",
+            False,
+            doc="Fix phases to initial values")
+        self.declareProperty("FitDeadTime", True, doc="Fit deadtimes")
+        self.declareProperty("DoublePulse", False, doc="Double pulse data")
+        self.declareProperty(
+            "OuterIterations",
+            10,
+            doc="Number of loops to optimise phase, amplitudes, backgrounds and dead times")
+        self.declareProperty(
+            "InnerIterations",
+            10,
+            doc="Number of loops to optimise the spectrum")
+        valid = FloatBoundedValidator(lower=0)
+        valid.setLowerExclusive(True)
+        self.declareProperty(
+            "DefaultLevel",
+            0.1,
+            validator=valid,
+            doc="Default Level")
+        self.declareProperty(
+            "Factor",
+            1.04,
+            doc="Used to control the value chi-squared converge to",
+            direction=Direction.InOut)
+        self.declareProperty(
+            WorkspaceProperty("OutputWorkspace",
+                              "",
+                              direction=Direction.Output),
+            doc="Output Spectrum (combined) versus field")
+        self.declareProperty(
+            ITableWorkspaceProperty(
+                "OutputPhaseTable",
+                "",
+                direction=Direction.Output,
+                optional=PropertyMode.Optional),
+            doc="Output phase table (optional)")
+        self.declareProperty(
+            ITableWorkspaceProperty(
+                "OutputDeadTimeTable",
+                "",
+                direction=Direction.Output,
+                optional=PropertyMode.Optional),
+            doc="Output dead time table (optional)")
+        self.declareProperty(
+            WorkspaceProperty("ReconstructedSpectra",
+                              "",
+                              direction=Direction.Output,
+                              optional=PropertyMode.Optional),
+            doc="Reconstructed time spectra (optional)")
+        self.declareProperty(
+            WorkspaceProperty(
+                "PhaseConvergenceTable",
+                "",
+                direction=Direction.Output,
+                optional=PropertyMode.Optional),
+            doc="Convergence of phases (optional)")
+
+    def checkRValues(self,rg9,rg0,xv,mylog):
+        if(rg9 - rg0 < 4):
+            raise ValueError("Data too short after trimming")
+        if(rg0 > 0 or rg9 < len(xv)):
+            mylog.warning(
+                "Trimmed {} odd sized bins from start and {} bins from end".format(rg0, len(xv) - rg9))
+
+    def getPulse(self):
+        if(self.getProperty("DoublePulse").value):
+            return 2
+        else:
+            return 1
+
+    def doGrouping(self,POINTS_nhists,nhisto):
+        GROUPING_group,POINTS_ngroups = None,None
+        if(not self.getProperty("GroupWorkspace").isDefault):
+            # group Workspace (LoadDetectorsGroupingFile format) provided, use
+            # it
+            gwsp = self.getProperty("GroupWorkspace").value
+            gwdets = gwsp.getNumberHistograms()
+            if(gwdets != POINTS_nhists):
+                raise Exception(
+                    "Grouping workspace has a different number of spectra")
+            GROUPING_group = np.zeros([nhisto], dtype=np.int)
+            for hh in range(POINTS_nhists):
+                GROUPING_group[hh] = int(gwsp.dataY(hh)[0] - 1)
+            POINTS_ngroups = np.amax(GROUPING_group) + 1
+        elif(self.getProperty("GroupTable").isDefault):
+            # no table provided, map 1:1 and use all spectra
+            POINTS_ngroups = POINTS_nhists
+            GROUPING_group = np.arange(POINTS_ngroups, dtype=np.int)
+        else:
+            GROUPING_group = np.zeros([nhisto], dtype=np.int)
+            GROUPING_group[:] = -1  # for unused histograms in no group
+            POINTS_ngroups = len(self.getProperty("GroupTable").value)
+            for g, row in enumerate(self.getProperty("GroupTable").value):
+                for hh in map(int, row["Detectors"].split(",")):
+                    GROUPING_group[hh - 1] = g
+        return GROUPING_group,POINTS_ngroups
+
+    def doDeadTimes(self,POINTS_ngroups,GROUPING_group,ws,FLAGS_fitdead,mylog):
+        RUNDATA_frames = None
+        SENSE_taud = np.zeros([POINTS_ngroups])  # default zero if not provided
+        tmpTaud = [[] for i in range(POINTS_ngroups)]
+        if(not self.getProperty("InputDeadTimeTable").isDefault):
+            # load data from standard Mantid dead time table
+            for r in self.getProperty("InputDeadTimeTable").value:
+                if GROUPING_group[r["spectrum"] - 1] >= 0:
+                    tmpTaud[GROUPING_group[r["spectrum"] - 1]].append(
+                        r["dead-time"])
+            for g in range(POINTS_ngroups):
+                SENSE_taud[g] = np.mean(tmpTaud[g])
+        try:
+            RUNDATA_frames = ws.getRun().getProperty(
+                "goodfrm").value  # need frames for dead time calc
+        except:
+            if((not self.getProperty("InputDeadTimeTable").isDefault) or FLAGS_fitdead):
+                raise Exception(
+                    "Need number of frames to use or fit dead time")
+            else:
+                mylog.notice(
+                    "No dead time fitting, assuming arbitrary number of frames")
+                RUNDATA_frames = 1000000
+        return SENSE_taud,RUNDATA_frames
+
+    def getPhase(self,FLAGS_fixphase,POINTS_ngroups,POINTS_nhists,mylog):
+        filePHASE = None
+        if(self.getProperty("InputPhaseTable").isDefault):
+            if(FLAGS_fixphase and POINTS_ngroups > 2):
+                raise ValueError("Supply phases to fix to")
+            if(POINTS_ngroups > 2):
+                mylog.warning("Generating default phases which may be wrong")
+            else:
+                mylog.notice(
+                    "Initialising phases of the 2 groups to 0 and 180 degrees")
+            filePHASE = np.arange(
+                POINTS_ngroups) * math.pi * 2.0 / POINTS_ngroups
+        else:
+            filePHASE = np.zeros([POINTS_ngroups])
+            pt = self.getProperty("InputPhaseTable").value
+            names = pt.getColumnNames()
+            phaseLabel = None
+            IDLabel = None
+            for name in names:
+                if name.lower() == "phi" or name.lower() == "phase":
+                    phaseLabel = name
+                if name.lower() == "detid" or name.lower() == "spectrum number":
+                    IDLabel = name
+            if phaseLabel is None:
+                raise ValueError(
+                    "Phase/phi are not labelled in the phase table")
+            if IDLabel is None:
+                raise ValueError(
+                    "Spectrum number/DetID are not labelled in the phase table")
+
+            if(len(pt) == POINTS_ngroups):  # phase table for grouped data, or when not grouping
+                for r in pt:
+                    filePHASE[r[IDLabel] - 1] = r[phaseLabel]
+                        # sign of phase now OK for Mantid 3.12 onwards
+            elif (len(pt) == POINTS_nhists):  # phase table for ungrouped data. Pick a representative detector for each group (the last one)
+                for r in pt:
+                    filePHASE[GROUPING_group[r[IDLabel] - 1]] = r[phaseLabel]
+        return filePHASE
+
+    def PyExec(self):
+        # logging
+        mylog = self.log()
+        #
+        ws = self.getProperty("InputWorkspace").value
+        # crop off odd sized bins at start and end (if present)
+        xv = ws.readX(0)
+        rg0 = 0
+        rg9 = len(xv)
+        while(rg9 > rg0 and abs((2 * xv[rg9 - 2] - xv[rg9 - 3] - xv[rg9 - 1]) / (xv[rg9 - 1] - xv[rg9 - 3])) > 0.001):
+            rg9 = rg9 - 1
+        while(rg9 > rg0 and abs((2 * xv[rg0 + 1] - xv[rg0] - xv[rg0 + 2]) / (xv[rg0 + 2] - xv[rg0])) > 0.001):
+            rg0 = rg0 + 1
+        self.checkRValues(rg9,rg0,xv,mylog)
+        RUNDATA_res = (ws.readX(0)[rg9 - 1] - ws.readX(0)[rg0]) / (
+            rg9 - rg0 - 1.0)  # assume linear!
+        mylog.notice("resolution {0} us".format(RUNDATA_res))
+        CHANNELS_itzero = rg0 + \
+            int(math.floor(-ws.readX(0)[rg0] / RUNDATA_res))
+                # Bin with t0 in it. note, may be negative for pre-cropped
+                # data. Remove +0.5
+        TZERO_fine = (ws.readX(0)[CHANNELS_itzero] + ws.readX(0)[CHANNELS_itzero + 1]) / \
+            2.0  # since it's not an exact boundary. Error if t0<0 or t0 is in an odd sized bin
+        mylog.notice("time zero bin has boundaries {} and {} giving tzero={}".format(
+            ws.readX(0)[CHANNELS_itzero], ws.readX(0)[CHANNELS_itzero + 1], TZERO_fine))
+        t1stgood = self.getProperty("FirstGoodTime").value
+        CHANNELS_i1stgood = rg0 + max(
+            int(math.floor((t1stgood - ws.readX(0)[rg0]) / RUNDATA_res + 1.0)),
+            0)  # was 1.0. i1stgood is first bin with purely good data in it (and good sized)
+        FLAGS_fixphase = self.getProperty("FixPhases").value
+        FLAGS_fitdead = self.getProperty("FitDeadTime").value
+        OuterIter = self.getProperty("OuterIterations").value
+        InnerIter = self.getProperty("InnerIterations").value
+        # progress
+        prog = Progress(
+            self,
+            start=0.0,
+            end=1.0,
+            nreports=OuterIter *
+            InnerIter)
+        #
+        tlast = self.getProperty("LastGoodTime").value
+        ilast = min(
+            rg0 + int(math.floor((tlast - ws.readX(0)[rg0]) / RUNDATA_res)),
+            rg9 - 1)  # first bin with some bad data in it, or end (excluding bad sized bins)
+        nhisto = ws.getNumberHistograms()
+        POINTS_nhists = nhisto
+        histlen = ilast  # -CHANNELS_itzero # actual data points to process, including before i1stgood
+        # fill rdata with raw counts
+        CHANNELS_itotal = histlen
+        mylog.notice(
+            "channels t0={0} tgood={1} to {2}".format(
+                CHANNELS_itzero,
+                CHANNELS_i1stgood,
+                CHANNELS_itotal))
+        DATALL_rdata = np.zeros([nhisto, ilast])
+        for i in range(nhisto):
+            DATALL_rdata[i, :] = ws.readY(i)[:ilast]
+        PULSES_npulse = self.getPulse()
+        PULSES_def = self.getProperty("DefaultLevel").value
+        FAC_factor = self.getProperty("Factor").value
+        #
+        # note on lengths of transforms, etc:
+        # input data has CHANNELS_itotal data points with time spacing RUNDATA_res
+        # Frequency spectrum has MAXPAGE_n data points with frequency spacing fperchan
+        # maximum frequency fperchan*MAXPAGE_n should be greater than anything expected in the data (or resolved due to pulse width, etc)
+        # Frequency spectrum is zero padded to POINTS_npts points and another POINTS_npts negative frequency components added,
+        # all of those are zeros
+        # Fourier transform performed on POINTS_npts*2 points (signed frequency axis)
+        # after transform, only the first CHANNELS_itotal values are compared to the raw data, the others can take any value.
+        #  (Data set actually padded to POINTS_npts with errors set to 1.E15 beyond good range)
+        # length constraints:
+        # POINTS_npts >=CHANNELS_itotal and POINTS_npts >= MAXPAGE_n
+        # POINTS_npts should be a power of 2 for speed (though numpy.fft.fft() will cope if it isn't)
+        # no requirement that CHANNELS_itotal or MAXPAGE_n are sub-multiples of POINTS_npts, or powers of 2 themselves
+        # relationship between bin sizes, etc:
+        # fperchan=1./(RUNDATA_res*float(POINTS_npts)*2.)
+        #
+        POINTS_npts = self.getProperty("Npts").value
+        # e.g. npts=8192
+        # i2pwr=log2(8192)=13
+        # in zft and opus: call FFT with i2pwr+1 (=14)
+        # in FFT: uses 2**14 points
+        # so set I2 to be 2*npts (allows for all the negative ones!)
+        if(CHANNELS_itotal > POINTS_npts):
+            mylog.notice(
+                "truncating useful data set from {0} to {1} data points".format(
+                    CHANNELS_itotal,
+                    POINTS_npts))
+            CHANNELS_itotal = POINTS_npts  # short transform, omit some data points
+        SAVETIME_i2 = POINTS_npts * 2
+        maxfield = self.getProperty("MaxField").value
+        MAXPAGE_n = int(maxfield * 0.01355 * 2 * POINTS_npts * RUNDATA_res)
+                        # number of active frequency points, need not now be a
+                        # power of 2?
+        if(MAXPAGE_n < 256):
+            MAXPAGE_n = 256
+        if(MAXPAGE_n > POINTS_npts):
+            MAXPAGE_n = POINTS_npts
+        # load grouping. Mantid group table is different: one row per group, 1
+        # column "detectors" with list of values
+        RUNDATA_hists = np.zeros(nhisto)  # not necessary?
+        GROUPING_group,POINTS_ngroups=self.doGrouping(POINTS_nhists,nhisto)
+#        # load dead times (note Maxent needs values per GROUP!)
+#        # standard dead time table is per detector. Take averages
+
+        SENSE_taud,RUNDATA_frames=self.doDeadTimes(POINTS_ngroups,GROUPING_group,ws,FLAGS_fitdead,mylog)
+
+        # sum histograms for total counts (not necessary?)
+        # load Phase Table (previously done in BACK.for)
+        # default being to distribute phases uniformly over 2pi, will work for
+        # 2 groups F,B
+        filePHASE=self.getPhase(FLAGS_fixphase,POINTS_ngroups,POINTS_nhists,mylog)
+        #
+        # debugging
+        if not self.getProperty("PhaseConvergenceTable").isDefault:
+            phaseconvWS = WorkspaceFactory.create(
+                "Workspace2D",
+                NVectors=POINTS_ngroups,
+                XLength=OuterIter + 1,
+                YLength=OuterIter + 1)
+            for i in range(POINTS_ngroups):
+                phaseconvWS.dataX(i)[0] = 0.0
+                phaseconvWS.dataY(i)[0] = filePHASE[i]
+        else:
+            phaseconvWS = None
+        # do the work! Lots to pass in and out
+        (MISSCHANNELS_mm, RUNDATA_fnorm, RUNDATA_hists, MAXPAGE_f, FAC_factor, FAC_facfake, FAC_ratio,
+         DETECT_a, DETECT_b, DETECT_c, DETECT_d, DETECT_e, PULSESHAPE_convol, SENSE_taud, FASE_phase, SAVETIME_ngo,
+         AMPS_amp, SENSE_phi, OUTSPEC_test, OUTSPEC_guess) = MULTIMAX(
+                                        POINTS_nhists, POINTS_ngroups, POINTS_npts, CHANNELS_itzero, CHANNELS_i1stgood,
+                                        CHANNELS_itotal, RUNDATA_res, RUNDATA_frames, GROUPING_group, DATALL_rdata,
+                                        FAC_factor, SENSE_taud, MAXPAGE_n, filePHASE, PULSES_def, PULSES_npulse,
+                                        FLAGS_fitdead, FLAGS_fixphase, SAVETIME_i2,
+                                        OuterIter, InnerIter, mylog, prog, phaseconvWS, TZERO_fine)
+        #
+        fperchan = 1. / (RUNDATA_res * float(POINTS_npts) * 2.)
+        fchan = np.linspace(
+            0.0,
+            MAXPAGE_n *
+            fperchan /
+            135.5e-4,
+            MAXPAGE_n,
+            endpoint=False)
+        # write results! Frequency spectra
+        outSpec = WorkspaceFactory.create(
+            "Workspace2D",
+            NVectors=1,
+            XLength=MAXPAGE_n,
+            YLength=MAXPAGE_n)
+        outSpec.dataX(0)[:] = fchan
+        outSpec.dataY(0)[:] = MAXPAGE_f
+        self.setProperty("OutputWorkspace", outSpec)
+        # revised dead times
+        if(not self.getProperty("OutputDeadTimeTable").isDefault):
+            outTaud = WorkspaceFactory.createTable()
+            outTaud.addColumn("int", "spectrum", 1)
+            outTaud.addColumn("double", "dead-time", 2)
+            for i in range(POINTS_ngroups):
+                outTaud.addRow([i + 1, SENSE_taud[i]])
+            self.setProperty("OutputDeadTimeTable", outTaud)
+        # revised phases (and amplitudes since they're in the table too)
+        if(not self.getProperty("OutputPhaseTable").isDefault):
+            outPhase = WorkspaceFactory.createTable()
+            outPhase.addColumn("int", "Spectrum number", 1)
+            outPhase.addColumn("double", "Asymmetry", 2)
+            outPhase.addColumn("double", "Phase", 2)
+            for i in range(POINTS_ngroups):
+                outPhase.addRow([i + 1, AMPS_amp[i], SENSE_phi[i]])
+                                # sign of phase now OK for Mantid 3.12 onwards
+            self.setProperty("OutputPhaseTable", outPhase)
+        # reconstructed spectra passed back from OUTSPEC
+        if(not self.getProperty("ReconstructedSpectra").isDefault):
+            k2 = CHANNELS_itotal  # channel range in source workspace accounting for instrumental t0
+            k1 = CHANNELS_i1stgood
+            i1 = k1 - \
+                CHANNELS_itzero  # channel range in guess, etc (t0 at start)
+            i2 = k2 - CHANNELS_itzero
+            mylog.notice(
+                "i1={} i2={} k1={} k2={} len(srcX)={} len(guess)={}".format(i1,
+                                                                            i2,
+                                                                            k1,
+                                                                            k2,
+                                                                            len(ws.dataX(
+                                                                                0)),
+                                                                            OUTSPEC_guess.shape[0]))
+            recSpec = WorkspaceFactory.create(
+                ws,
+                NVectors=POINTS_ngroups,
+                XLength=i2 - i1 + 1,
+                YLength=i2 - i1)
+            for j in range(POINTS_ngroups):
+                recSpec.dataX(j)[:] = ws.dataX(j)[k1:k2 + 1]
+                recSpec.dataY(j)[:] = OUTSPEC_guess[i1:i2, j]
+            self.setProperty("ReconstructedSpectra", recSpec)
+        if phaseconvWS:
+            self.setProperty("PhaseConvergenceTable", phaseconvWS)
+        # final converged Factor
+        self.setProperty("Factor", FAC_factor)
+        # final chisquared?
+AlgorithmFactory.subscribe(MuonMaxent)
diff --git a/Framework/PythonInterface/plugins/algorithms/SNSPowderReduction.py b/Framework/PythonInterface/plugins/algorithms/SNSPowderReduction.py
index 1f11dbcd452c725f3bd70aec54480e687a05a312..042456c844eb161c9e6703e3b70a0379fef6e29b 100644
--- a/Framework/PythonInterface/plugins/algorithms/SNSPowderReduction.py
+++ b/Framework/PythonInterface/plugins/algorithms/SNSPowderReduction.py
@@ -4,7 +4,7 @@ from __future__ import (absolute_import, division, print_function)
 import os
 
 import mantid.simpleapi as api
-from mantid.api import mtd, AlgorithmFactory, AnalysisDataService, DataProcessorAlgorithm, \
+from mantid.api import mtd, AlgorithmFactory, AnalysisDataService, DistributedDataProcessorAlgorithm, \
     FileAction, FileProperty, ITableWorkspaceProperty, MultipleFileProperty, PropertyMode, WorkspaceProperty, \
     ITableWorkspace, MatrixWorkspace
 from mantid.kernel import ConfigService, Direction, FloatArrayProperty, \
@@ -106,7 +106,7 @@ def getBasename(filename):
 #pylint: disable=too-many-instance-attributes
 
 
-class SNSPowderReduction(DataProcessorAlgorithm):
+class SNSPowderReduction(DistributedDataProcessorAlgorithm):
     COMPRESS_TOL_TOF = .01
     _resampleX = None
     _binning = None
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinder.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinder.py
index d74a05dfaba13c6b5191f3ac93c1fdf2d476581d..f0ab4f123599172a4070de36180c26edb4973b49 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinder.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANS/SANSBeamCentreFinder.py
@@ -14,6 +14,15 @@ from sans.algorithm_detail.strip_end_nans_and_infs import strip_end_nans
 from sans.common.file_information import get_instrument_paths_for_sans_file
 from sans.common.xml_parsing import get_named_elements_from_ipf_file
 from sans.algorithm_detail.single_execution import perform_can_subtraction
+from mantid import AnalysisDataService
+from mantid.simpleapi import CloneWorkspace, GroupWorkspaces
+
+try:
+    import mantidplot
+except (Exception, Warning):
+    mantidplot = None
+# this should happen when this is called from outside Mantidplot and only then,
+# the result is that attempting to plot will raise an exception
 
 
 class SANSBeamCentreFinder(DataProcessorAlgorithm):
@@ -85,6 +94,9 @@ class SANSBeamCentreFinder(DataProcessorAlgorithm):
         self.declareProperty('Direction', FindDirectionEnum.to_string(FindDirectionEnum.All), direction=Direction.Input,
                              doc="The search direction is an enumerable which can be either All, LeftRight or UpDown")
 
+        self.declareProperty('Verbose', False, direction=Direction.Input,
+                             doc="Whether to keep workspaces from each iteration in ADS.")
+
         # ----------
         # Output
         # ----------
@@ -103,7 +115,7 @@ class SANSBeamCentreFinder(DataProcessorAlgorithm):
         progress = self._get_progress()
         self.scale_1 = 1000
         self.scale_2 = 1000
-
+        verbose = self.getProperty('Verbose').value
         x_start = self.getProperty("Position1Start").value
         y_start = self.getProperty("Position2Start").value
 
@@ -144,8 +156,10 @@ class SANSBeamCentreFinder(DataProcessorAlgorithm):
             position_1_step = 0.0
         centre1 = x_start
         centre2 = y_start
-        resLR_old = 0.0
-        resTB_old = 0.0
+        residueLR = []
+        residueTB = []
+        centre_1_hold = x_start
+        centre_2_hold = y_start
         for j in range(0, max_iterations + 1):
             if(j != 0):
                 centre1 += position_1_step
@@ -163,38 +177,71 @@ class SANSBeamCentreFinder(DataProcessorAlgorithm):
                 for key in sample_quartiles:
                     sample_quartiles[key] = perform_can_subtraction(sample_quartiles[key], can_quartiles[key], self)
 
-            residueLR = self._calculate_residuals(sample_quartiles[MaskingQuadrant.Left],
-                                                  sample_quartiles[MaskingQuadrant.Right])
-            residueTB = self._calculate_residuals(sample_quartiles[MaskingQuadrant.Top],
-                                                  sample_quartiles[MaskingQuadrant.Bottom])
+            if mantidplot:
+                output_workspaces = self._publish_to_ADS(sample_quartiles)
+                if verbose:
+                    self._rename_and_group_workspaces(j, output_workspaces)
+
+            residueLR.append(self._calculate_residuals(sample_quartiles[MaskingQuadrant.Left],
+                                                       sample_quartiles[MaskingQuadrant.Right]))
+            residueTB.append(self._calculate_residuals(sample_quartiles[MaskingQuadrant.Top],
+                                                       sample_quartiles[MaskingQuadrant.Bottom]))
             if(j == 0):
                 logger.notice("Itr " + str(j) + ": (" + str(self.scale_1 * centre1) + ", " + str(self.scale_2 * centre2) + ")  SX="
-                              + str(residueLR) + "  SY=" + str(residueTB))
+                              + str(residueLR[j]) + "  SY=" + str(residueTB[j]))
+                if mantidplot:
+                    self._plot_quartiles(output_workspaces, state.data.sample_scatter)
+
             else:
                 # have we stepped across the y-axis that goes through the beam center?
-                if residueLR > resLR_old:
+                if residueLR[j] > residueLR[j-1]:
                     # yes with stepped across the middle, reverse direction and half the step size
                     position_1_step = - position_1_step / 2
-                if residueTB > resTB_old:
+                if residueTB[j] > residueTB[j-1]:
                     position_2_step = - position_2_step / 2
 
                 logger.notice("Itr " + str(j) + ": (" + str(self.scale_1 * centre1) + ", " + str(self.scale_2 * centre2) + ")  SX="
-                              + str(residueLR) + "  SY=" + str(residueTB))
+                              + str(residueLR[j]) + "  SY=" + str(residueTB[j]))
+
+                if (residueLR[j]+residueTB[j]) < (residueLR[j-1]+residueTB[j-1]) or state.compatibility.use_compatibility_mode:
+                    centre_1_hold = centre1
+                    centre_2_hold = centre2
 
                 if abs(position_1_step) < tolerance and abs(position_2_step) < tolerance:
                     # this is the success criteria, we've close enough to the center
                     logger.notice("Converged - check if stuck in local minimum! ")
                     break
+
             if j == max_iterations:
                 logger.notice("Out of iterations, new coordinates may not be the best")
 
-            resLR_old = residueLR
-            resTB_old = residueTB
-
-        self.setProperty("Centre1", centre1)
-        self.setProperty("Centre2", centre2)
-
-        logger.notice("Centre coordinates updated: [{}, {}]".format(centre1*self.scale_1, centre2*self.scale_2))
+        self.setProperty("Centre1", centre_1_hold)
+        self.setProperty("Centre2", centre_2_hold)
+
+        logger.notice("Centre coordinates updated: [{}, {}]".format(centre_1_hold*self.scale_1, centre_2_hold*self.scale_2))
+
+    def _rename_and_group_workspaces(self, index, output_workspaces):
+        to_group = []
+        for workspace in output_workspaces:
+            CloneWorkspace(InputWorkspace=workspace, OutputWorkspace='{}_{}'.format(workspace, index))
+            to_group.append('{}_{}'.format(workspace, index))
+        GroupWorkspaces(InputWorkspaces=to_group,OutputWorkspace='Iteration_{}'.format(index))
+
+    def _publish_to_ADS(self, sample_quartiles):
+        output_workspaces = []
+        for key in sample_quartiles:
+            output_workspaces.append(MaskingQuadrant.to_string(key))
+            AnalysisDataService.addOrReplace(MaskingQuadrant.to_string(key), sample_quartiles[key])
+
+        return output_workspaces
+
+    def _plot_quartiles(self, output_workspaces, sample_scatter):
+        title = '{}_beam_centre_finder'.format(sample_scatter)
+        graph_handle = mantidplot.plotSpectrum(output_workspaces, 0)
+        graph_handle.activeLayer().logLogAxes()
+        graph_handle.activeLayer().setTitle(title)
+        graph_handle.setName(title)
+        return graph_handle
 
     def _get_cloned_workspace(self, workspace_name):
         workspace = self.getProperty(workspace_name).value
diff --git a/Framework/PythonInterface/test/python/mantid/plots/CMakeLists.txt b/Framework/PythonInterface/test/python/mantid/plots/CMakeLists.txt
index 4b1aa30b3b8e9bd9a8e47c2f1ea8fa715249b6c5..43f66b99d04f27e665b532f7659373900c0f00d6 100644
--- a/Framework/PythonInterface/test/python/mantid/plots/CMakeLists.txt
+++ b/Framework/PythonInterface/test/python/mantid/plots/CMakeLists.txt
@@ -2,7 +2,9 @@
 ## mantid.dataobjects tests
 ##
 set ( TEST_PY_FILES
-  functionsTest.py
+  helperfunctionsTest.py
+  plotfunctionsTest.py
+  plotfunctions3DTest.py
   plots__init__Test.py
 )
 
diff --git a/Framework/PythonInterface/test/python/mantid/plots/functionsTest.py b/Framework/PythonInterface/test/python/mantid/plots/functionsTest.py
deleted file mode 100644
index 309e45a7257e58767128faa651d433e8273e3869..0000000000000000000000000000000000000000
--- a/Framework/PythonInterface/test/python/mantid/plots/functionsTest.py
+++ /dev/null
@@ -1,283 +0,0 @@
-from __future__ import (absolute_import, division, print_function)
-
-import unittest
-from mantid.simpleapi import CreateWorkspace,DeleteWorkspace,CreateMDHistoWorkspace, ConjoinWorkspaces
-import mantid.api
-import numpy as np
-from mantid.kernel import config
-import matplotlib
-matplotlib.use('AGG')
-import mantid.plots.functions as funcs
-import matplotlib.pyplot as plt
-
-
-class PlotsFunctionsTest(unittest.TestCase):
-
-    @classmethod
-    def setUpClass(cls):
-        cls.g1da=config['graph1d.autodistribution']
-        config['graph1d.autodistribution']='On'
-        cls.ws2d_histo = CreateWorkspace(DataX=[10,20,30,10,20,30],
-                                         DataY=[2,3,4,5],
-                                         DataE=[1,2,3,4],
-                                         NSpec=2,
-                                         Distribution=True,
-                                         UnitX='Wavelength',
-                                         VerticalAxisUnit='DeltaE',
-                                         VerticalAxisValues=[4,6,8],
-                                         OutputWorkspace='ws2d_histo')
-        cls.ws2d_point = CreateWorkspace(DataX=[1,2,3,4,1,2,3,4,1,2,3,4],
-                                         DataY=[2]*12,
-                                         NSpec=3,
-                                         OutputWorkspace='ws2d_point')
-        cls.ws1d_point = CreateWorkspace(DataX=[1,2],
-                                         DataY=[1,2],
-                                         NSpec=1,
-                                         Distribution=False,
-                                         OutputWorkspace='ws1d_point')
-        cls.ws2d_histo_rag = CreateWorkspace(DataX=[1,2,3,4,5,2,4,6,8,10],
-                                             DataY=[2]*8,
-                                             NSpec=2,
-                                             VerticalAxisUnit='DeltaE',
-                                             VerticalAxisValues=[5,7,9],
-                                             OutputWorkspace='ws2d_histo_rag')
-        cls.ws2d_point_rag = CreateWorkspace(DataX=[1,2,3,4,2,4,6,8],
-                                             DataY=[2]*8,
-                                             NSpec=2,
-                                             OutputWorkspace='ws2d_point_rag')
-        cls.ws_MD_2d = CreateMDHistoWorkspace(Dimensionality=3,
-                                              Extents='-3,3,-10,10,-1,1',
-                                              SignalInput=range(25),
-                                              ErrorInput=range(25),
-                                              NumberOfEvents=10*np.ones(25),
-                                              NumberOfBins='5,5,1',
-                                              Names='Dim1,Dim2,Dim3',
-                                              Units='MomentumTransfer,EnergyTransfer,Angstrom',
-                                              OutputWorkspace='ws_MD_2d')
-        cls.ws_MD_1d = CreateMDHistoWorkspace(Dimensionality=3,
-                                              Extents='-3,3,-10,10,-1,1',
-                                              SignalInput=range(5),
-                                              ErrorInput=range(5),
-                                              NumberOfEvents=10*np.ones(5),
-                                              NumberOfBins='1,5,1',
-                                              Names='Dim1,Dim2,Dim3',
-                                              Units='MomentumTransfer,EnergyTransfer,Angstrom',
-                                              OutputWorkspace='ws_MD_1d')
-        cls.ws2d_point_uneven=CreateWorkspace(DataX=[10,20,30],
-                                              DataY=[1,2,3],
-                                              NSpec=1,
-                                              OutputWorkspace='ws2d_point_uneven')
-        wp=CreateWorkspace(DataX=[15,25,35,45],DataY=[1,2,3,4],NSpec=1)
-        ConjoinWorkspaces(cls.ws2d_point_uneven,wp,CheckOverlapping=False)
-        cls.ws2d_point_uneven=mantid.mtd['ws2d_point_uneven']
-        cls.ws2d_histo_uneven=CreateWorkspace(DataX=[10,20,30,40],
-                                              DataY=[1,2,3],
-                                              NSpec=1,
-                                              OutputWorkspace='ws2d_histo_uneven')
-        wp=CreateWorkspace(DataX=[15,25,35,45,55],DataY=[1,2,3,4],NSpec=1)
-        ConjoinWorkspaces(cls.ws2d_histo_uneven,wp,CheckOverlapping=False)
-        cls.ws2d_histo_uneven=mantid.mtd['ws2d_histo_uneven']
-        newYAxis=mantid.api.NumericAxis.create(3)
-        newYAxis.setValue(0,10)
-        newYAxis.setValue(1,15)
-        newYAxis.setValue(2,25)
-        cls.ws2d_histo_uneven.replaceAxis(1,newYAxis)
-
-    @classmethod
-    def tearDownClass(cls):
-        config['graph1d.autodistribution']=cls.g1da
-        DeleteWorkspace('ws2d_histo')
-        DeleteWorkspace('ws2d_point')
-        DeleteWorkspace('ws1d_point')
-        DeleteWorkspace('ws_MD_2d')
-        DeleteWorkspace('ws_MD_1d')
-        DeleteWorkspace('ws2d_histo_rag')
-        DeleteWorkspace('ws2d_point_rag')
-        DeleteWorkspace('ws2d_point_uneven')
-        DeleteWorkspace('ws2d_histo_uneven')
-
-    def test_getWkspIndexDistAndLabel(self):
-        #fail case
-        self.assertRaises(RuntimeError,funcs._getWkspIndexDistAndLabel,self.ws2d_histo)
-        #get info from a 2d workspace
-        index, dist, kwargs = funcs._getWkspIndexDistAndLabel(self.ws2d_histo,specNum=2)
-        self.assertEqual(index,1)
-        self.assertTrue(dist)
-        self.assertEqual(kwargs['label'],'ws2d_histo: spec 2')
-        #get info from default spectrum in the 1d case
-        index, dist, kwargs = funcs._getWkspIndexDistAndLabel(self.ws1d_point)
-        self.assertEqual(index,0)
-        self.assertFalse(dist)
-        self.assertEqual(kwargs['label'],'ws1d_point: spec 1')
-
-    def test_getAxesLabels(self):
-        axs=funcs.getAxesLabels(self.ws2d_histo)
-        self.assertEqual(axs,('', 'Wavelength ($\\AA$)', 'Energy transfer ($meV$)'))
-
-    def test_getAxesLabeld_MDWS(self):
-        axs=funcs.getAxesLabels(self.ws_MD_2d)
-        #should get the first two dimension labels only
-        self.assertEqual(axs,('Intensity', 'Dim1 ($\\AA^{-1}$)', 'Dim2 (EnergyTransfer)'))
-
-    def test_getDataUnevenFlag(self):
-        flag,kwargs=funcs._getDataUnevenFlag(self.ws2d_histo_rag, axisaligned=True, other_kwarg=1)
-        self.assertTrue(flag)
-        self.assertEquals(kwargs,{'other_kwarg':1})
-        flag,kwargs=funcs._getDataUnevenFlag(self.ws2d_histo_rag, other_kwarg=2)
-        self.assertFalse(flag)
-        self.assertEquals(kwargs,{'other_kwarg':2})
-        flag,kwargs=funcs._getDataUnevenFlag(self.ws2d_histo_uneven, axisaligned=False, other_kwarg=3)
-        self.assertTrue(flag)
-        self.assertEquals(kwargs,{'other_kwarg':3})
-
-    def test_boundaries_from_points(self):
-        centers=np.array([1.,2.,4.,8.])
-        bounds=funcs.boundaries_from_points(centers)
-        self.assertTrue(np.array_equal(bounds,np.array([0.5,1.5,3,6,10])))
-
-    def test_points_from_boundaries(self):
-        bounds=np.array([1.,3,4,10])
-        centers=funcs.points_from_boundaries(bounds)
-        self.assertTrue(np.array_equal(centers,np.array([2.,3.5,7])))
-
-    def test_getSpectrum(self):
-        #get data divided by bin width
-        x,y,dy,dx=funcs._getSpectrum(self.ws2d_histo, 1, False,withDy=True, withDx=True)
-        self.assertTrue(np.array_equal(x,np.array([15.,25.])))
-        self.assertTrue(np.array_equal(y,np.array([.4,.5])))
-        self.assertTrue(np.array_equal(dy,np.array([.3,.4])))
-        self.assertEqual(dx,None)
-        #get data not divided by bin width
-        x,y,dy,dx=funcs._getSpectrum(self.ws2d_histo, 0, True, withDy=True, withDx=True)
-        self.assertTrue(np.array_equal(x,np.array([15.,25.])))
-        self.assertTrue(np.array_equal(y,np.array([2,3])))
-        self.assertTrue(np.array_equal(dy,np.array([1,2])))
-        self.assertEqual(dx,None)
-        #fail case - try to find spectrum out of range
-        self.assertRaises(RuntimeError,funcs._getSpectrum, self.ws2d_histo, 10, True)
-
-    def test_getMDData2D_bin_bounds(self):
-        x,y,data=funcs._getMDData2D_bin_bounds(self.ws_MD_2d,mantid.api.MDNormalization.NoNormalization)
-        #logger.error(str(coords))
-        np.testing.assert_allclose(x,np.array([-3,-1.8,-0.6,0.6,1.8,3]),atol=1e-10)
-        np.testing.assert_allclose(y,np.array([-10,-6,-2,2,6,10.]),atol=1e-10)
-        np.testing.assert_allclose(data,np.arange(25).reshape(5,5),atol=1e-10)
-
-    def test_getMDData2D_bin_centers(self):
-        x,y,data=funcs._getMDData2D_bin_centers(self.ws_MD_2d,mantid.api.MDNormalization.NumEventsNormalization)
-        np.testing.assert_allclose(x,np.array([-2.4,-1.2,0,1.2,2.4]),atol=1e-10)
-        np.testing.assert_allclose(y,np.array([-8,-4,0,4,8]),atol=1e-10)
-        np.testing.assert_allclose(data,np.arange(25).reshape(5,5)*0.1,atol=1e-10)
-
-    def test_getMDData1D(self):
-        coords,data,err=funcs._getMDData1D(self.ws_MD_1d,mantid.api.MDNormalization.NumEventsNormalization)
-        np.testing.assert_allclose(coords,np.array([-8,-4,0,4,8]),atol=1e-10)
-
-    def test_getMatrix2DData_rect(self):
-        #contour from aligned point data
-        x,y,z=funcs._getMatrix2DData(self.ws2d_point,True,histogram2D=False)
-        np.testing.assert_allclose(x,np.array([[1,2,3,4],[1,2,3,4],[1,2,3,4]]))
-        np.testing.assert_allclose(y,np.array([[1,1,1,1],[2,2,2,2],[3,3,3,3]]))
-        #mesh from aligned point data
-        x,y,z=funcs._getMatrix2DData(self.ws2d_point,True,histogram2D=True)
-        np.testing.assert_allclose(x,np.array([[0.5,1.5,2.5,3.5,4.5],[0.5,1.5,2.5,3.5,4.5],[0.5,1.5,2.5,3.5,4.5],[0.5,1.5,2.5,3.5,4.5]]))
-        np.testing.assert_allclose(y,np.array([[0.5,0.5,0.5,0.5,0.5],[1.5,1.5,1.5,1.5,1.5],[2.5,2.5,2.5,2.5,2.5],[3.5,3.5,3.5,3.5,3.5]]))
-        #contour from aligned histo data
-        x,y,z=funcs._getMatrix2DData(self.ws2d_histo,True,histogram2D=False)
-        np.testing.assert_allclose(x,np.array([[15,25],[15,25]]))
-        np.testing.assert_allclose(y,np.array([[5,5],[7,7]]))
-        #mesh from aligned histo data
-        x,y,z=funcs._getMatrix2DData(self.ws2d_histo,True,histogram2D=True)
-        np.testing.assert_allclose(x,np.array([[10,20,30],[10,20,30],[10,20,30]]))
-        np.testing.assert_allclose(y,np.array([[4,4,4],[6,6,6],[8,8,8]]))
-
-    def test_getMatrix2DData_rag(self):
-        #contour from ragged point data
-        x,y,z=funcs._getMatrix2DData(self.ws2d_point_rag,True,histogram2D=False)
-        np.testing.assert_allclose(x,np.array([[1,2,3,4],[2,4,6,8]]))
-        np.testing.assert_allclose(y,np.array([[1,1,1,1],[2,2,2,2]]))
-        #contour from ragged histo data
-        x,y,z=funcs._getMatrix2DData(self.ws2d_histo_rag,True,histogram2D=False)
-        np.testing.assert_allclose(x,np.array([[1.5,2.5,3.5,4.5],[3,5,7,9]]))
-        np.testing.assert_allclose(y,np.array([[6,6,6,6],[8,8,8,8]]))
-        #mesh from ragged point data
-        x,y,z=funcs._getMatrix2DData(self.ws2d_point_rag,True,histogram2D=True)
-        np.testing.assert_allclose(x,np.array([[0.5,1.5,2.5,3.5,4.5],[1,3,5,7,9],[1,3,5,7,9]]))
-        np.testing.assert_allclose(y,np.array([[0.5,0.5,0.5,0.5,0.5],[1.5,1.5,1.5,1.5,1.5],[2.5,2.5,2.5,2.5,2.5]]))
-        #mesh from ragged histo data
-        x,y,z=funcs._getMatrix2DData(self.ws2d_histo_rag,True,histogram2D=True)
-        np.testing.assert_allclose(x,np.array([[1,2,3,4,5],[2,4,6,8,10],[2,4,6,8,10]]))
-        np.testing.assert_allclose(y,np.array([[5,5,5,5,5],[7,7,7,7,7],[9,9,9,9,9]]))
-        #check that fails for uneven data
-        self.assertRaises(ValueError,funcs._getMatrix2DData, self.ws2d_point_uneven,True)
-
-    def test_getUnevenData(self):
-        #even points
-        x,y,z=funcs._getUnevenData(self.ws2d_point_rag,True)
-        np.testing.assert_allclose(x[0],np.array([0.5,1.5,2.5,3.5,4.5]))
-        np.testing.assert_allclose(x[1],np.array([1,3,5,7,9]))
-        np.testing.assert_allclose(y[0],np.array([0.5,1.5]))
-        np.testing.assert_allclose(y[1],np.array([1.5,2.5]))
-        np.testing.assert_allclose(z[0],np.array([2,2,2,2]))
-        np.testing.assert_allclose(z[1],np.array([2,2,2,2]))
-        #even histo
-        x,y,z=funcs._getUnevenData(self.ws2d_histo_rag,True)
-        np.testing.assert_allclose(x[0],np.array([1,2,3,4,5]))
-        np.testing.assert_allclose(x[1],np.array([2,4,6,8,10]))
-        np.testing.assert_allclose(y[0],np.array([5,7]))
-        np.testing.assert_allclose(y[1],np.array([7,9]))
-        np.testing.assert_allclose(z[0],np.array([2,2,2,2]))
-        np.testing.assert_allclose(z[1],np.array([2,2,2,2]))
-        #uneven points
-        x,y,z=funcs._getUnevenData(self.ws2d_point_uneven,True)
-        np.testing.assert_allclose(x[0],np.array([5,15,25,35]))
-        np.testing.assert_allclose(x[1],np.array([10,20,30,40,50]))
-        np.testing.assert_allclose(y[0],np.array([0.5,1.5]))
-        np.testing.assert_allclose(y[1],np.array([1.5,2.5]))
-        np.testing.assert_allclose(z[0],np.array([1,2,3]))
-        np.testing.assert_allclose(z[1],np.array([1,2,3,4]))
-        #uneven histo
-        x,y,z=funcs._getUnevenData(self.ws2d_histo_uneven,True)
-        np.testing.assert_allclose(x[0],np.array([10,20,30,40]))
-        np.testing.assert_allclose(x[1],np.array([15,25,35,45,55]))
-        np.testing.assert_allclose(y[0],np.array([10,15]))
-        np.testing.assert_allclose(y[1],np.array([15,25]))
-        np.testing.assert_allclose(z[0],np.array([1,2,3]))
-        np.testing.assert_allclose(z[1],np.array([1,2,3,4]))
-
-    def test_1dplots(self):
-        fig, ax = plt.subplots()
-        funcs.plot(ax,self.ws2d_histo,'rs',specNum=1)
-        funcs.plot(ax,self.ws2d_histo,specNum=2,linewidth=6)
-        funcs.plot(ax,self.ws_MD_1d,'bo')
-
-    def test_1derrorbars(self):
-        fig, ax = plt.subplots()
-        funcs.errorbar(ax,self.ws2d_histo,'rs',specNum=1)
-        funcs.errorbar(ax,self.ws2d_histo,specNum=2,linewidth=6)
-        funcs.errorbar(ax,self.ws_MD_1d,'bo')
-
-    def test_1dscatter(self):
-        fig, ax = plt.subplots()
-        funcs.scatter(ax,self.ws2d_histo,specNum=1)
-        funcs.scatter(ax,self.ws2d_histo,specNum=2)
-        funcs.scatter(ax,self.ws_MD_1d)
-
-    def test_2dcontours(self):
-        fig, ax = plt.subplots()
-        funcs.contour(ax,self.ws2d_histo_rag)
-        funcs.contourf(ax,self.ws2d_histo,vmin=0)
-        funcs.tricontour(ax,self.ws_MD_2d)
-        funcs.tricontourf(ax,self.ws_MD_2d)
-        self.assertRaises(ValueError,funcs.contour, ax, self.ws2d_point_uneven)
-
-    def test_2dpcolors(self):
-        fig, ax = plt.subplots()
-        funcs.pcolor(ax,self.ws2d_histo_rag)
-        funcs.tripcolor(ax,self.ws2d_histo,vmin=0)
-        funcs.pcolormesh(ax,self.ws_MD_2d)
-        funcs.pcolorfast(ax,self.ws2d_point_uneven,vmin=-1)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/Framework/PythonInterface/test/python/mantid/plots/helperfunctionsTest.py b/Framework/PythonInterface/test/python/mantid/plots/helperfunctionsTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..7089f2ad0d0eb2819cfd16c2f67f60809eaf6a9e
--- /dev/null
+++ b/Framework/PythonInterface/test/python/mantid/plots/helperfunctionsTest.py
@@ -0,0 +1,271 @@
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+import matplotlib
+matplotlib.use('AGG')
+import datetime
+import numpy as np
+import mantid.api
+import mantid.plots.helperfunctions as funcs
+from mantid.kernel import config
+from mantid.simpleapi import CreateWorkspace, DeleteWorkspace, CreateMDHistoWorkspace,\
+                             ConjoinWorkspaces, AddTimeSeriesLog
+
+
+
+
+class HelperFunctionsTest(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.g1da = config['graph1d.autodistribution']
+        config['graph1d.autodistribution'] = 'On'
+        cls.ws2d_histo = CreateWorkspace(DataX=[10, 20, 30, 10, 20, 30],
+                                         DataY=[2, 3, 4, 5],
+                                         DataE=[1, 2, 3, 4],
+                                         NSpec=2,
+                                         Distribution=True,
+                                         UnitX='Wavelength',
+                                         VerticalAxisUnit='DeltaE',
+                                         VerticalAxisValues=[4, 6, 8],
+                                         OutputWorkspace='ws2d_histo')
+        cls.ws2d_point = CreateWorkspace(DataX=[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4],
+                                         DataY=[2] * 12,
+                                         NSpec=3,
+                                         OutputWorkspace='ws2d_point')
+        cls.ws1d_point = CreateWorkspace(DataX=[1, 2],
+                                         DataY=[1, 2],
+                                         NSpec=1,
+                                         Distribution=False,
+                                         OutputWorkspace='ws1d_point')
+        cls.ws2d_histo_rag = CreateWorkspace(DataX=[1, 2, 3, 4, 5, 2, 4, 6, 8, 10],
+                                             DataY=[2] * 8,
+                                             NSpec=2,
+                                             VerticalAxisUnit='DeltaE',
+                                             VerticalAxisValues=[5, 7, 9],
+                                             OutputWorkspace='ws2d_histo_rag')
+        cls.ws2d_point_rag = CreateWorkspace(DataX=[1, 2, 3, 4, 2, 4, 6, 8],
+                                             DataY=[2] * 8,
+                                             NSpec=2,
+                                             OutputWorkspace='ws2d_point_rag')
+        cls.ws_MD_2d = CreateMDHistoWorkspace(Dimensionality=3,
+                                              Extents='-3,3,-10,10,-1,1',
+                                              SignalInput=range(25),
+                                              ErrorInput=range(25),
+                                              NumberOfEvents=10 * np.ones(25),
+                                              NumberOfBins='5,5,1',
+                                              Names='Dim1,Dim2,Dim3',
+                                              Units='MomentumTransfer,EnergyTransfer,Angstrom',
+                                              OutputWorkspace='ws_MD_2d')
+        cls.ws_MD_1d = CreateMDHistoWorkspace(Dimensionality=3,
+                                              Extents='-3,3,-10,10,-1,1',
+                                              SignalInput=range(5),
+                                              ErrorInput=range(5),
+                                              NumberOfEvents=10 * np.ones(5),
+                                              NumberOfBins='1,5,1',
+                                              Names='Dim1,Dim2,Dim3',
+                                              Units='MomentumTransfer,EnergyTransfer,Angstrom',
+                                              OutputWorkspace='ws_MD_1d')
+        cls.ws2d_point_uneven = CreateWorkspace(DataX=[10, 20, 30],
+                                                DataY=[1, 2, 3],
+                                                NSpec=1,
+                                                OutputWorkspace='ws2d_point_uneven')
+        wp = CreateWorkspace(DataX=[15, 25, 35, 45], DataY=[1, 2, 3, 4], NSpec=1)
+        ConjoinWorkspaces(cls.ws2d_point_uneven, wp, CheckOverlapping=False)
+        cls.ws2d_point_uneven = mantid.mtd['ws2d_point_uneven']
+        cls.ws2d_histo_uneven = CreateWorkspace(DataX=[10, 20, 30, 40],
+                                                DataY=[1, 2, 3],
+                                                NSpec=1,
+                                                OutputWorkspace='ws2d_histo_uneven')
+        wp = CreateWorkspace(DataX=[15, 25, 35, 45, 55], DataY=[1, 2, 3, 4], NSpec=1)
+        ConjoinWorkspaces(cls.ws2d_histo_uneven, wp, CheckOverlapping=False)
+        cls.ws2d_histo_uneven = mantid.mtd['ws2d_histo_uneven']
+        newYAxis = mantid.api.NumericAxis.create(3)
+        newYAxis.setValue(0, 10)
+        newYAxis.setValue(1, 15)
+        newYAxis.setValue(2, 25)
+        cls.ws2d_histo_uneven.replaceAxis(1, newYAxis)
+        AddTimeSeriesLog(cls.ws2d_histo, Name="my_log", Time="2010-01-01T00:00:00", Value=100)
+        AddTimeSeriesLog(cls.ws2d_histo, Name="my_log", Time="2010-01-01T00:30:00", Value=15)
+        AddTimeSeriesLog(cls.ws2d_histo, Name="my_log", Time="2010-01-01T00:50:00", Value=100.2)
+
+    @classmethod
+    def tearDownClass(cls):
+        config['graph1d.autodistribution'] = cls.g1da
+        DeleteWorkspace('ws2d_histo')
+        DeleteWorkspace('ws2d_point')
+        DeleteWorkspace('ws1d_point')
+        DeleteWorkspace('ws_MD_2d')
+        DeleteWorkspace('ws_MD_1d')
+        DeleteWorkspace('ws2d_histo_rag')
+        DeleteWorkspace('ws2d_point_rag')
+        DeleteWorkspace('ws2d_point_uneven')
+        DeleteWorkspace('ws2d_histo_uneven')
+
+    def test_get_wksp_index_dist_and_label(self):
+        # fail case
+        self.assertRaises(RuntimeError, funcs.get_wksp_index_dist_and_label, self.ws2d_histo)
+        # get info from a 2d workspace
+        index, dist, kwargs = funcs.get_wksp_index_dist_and_label(self.ws2d_histo, specNum=2)
+        self.assertEqual(index, 1)
+        self.assertTrue(dist)
+        self.assertEqual(kwargs['label'], 'ws2d_histo: spec 2')
+        # get info from default spectrum in the 1d case
+        index, dist, kwargs = funcs.get_wksp_index_dist_and_label(self.ws1d_point)
+        self.assertEqual(index, 0)
+        self.assertFalse(dist)
+        self.assertEqual(kwargs['label'], 'ws1d_point: spec 1')
+
+    def test_get_axes_labels(self):
+        axs = funcs.get_axes_labels(self.ws2d_histo)
+        self.assertEqual(axs, ('', 'Wavelength ($\\AA$)', 'Energy transfer ($meV$)'))
+
+    def test_get_axes_label_2d_MDWS(self):
+        axs = funcs.get_axes_labels(self.ws_MD_2d)
+        # should get the first two dimension labels only
+        self.assertEqual(axs, ('Intensity', 'Dim1 ($\\AA^{-1}$)', 'Dim2 (EnergyTransfer)'))
+
+    def test_get_data_uneven_flag(self):
+        flag, kwargs = funcs.get_data_uneven_flag(self.ws2d_histo_rag, axisaligned=True, other_kwarg=1)
+        self.assertTrue(flag)
+        self.assertEquals(kwargs, {'other_kwarg': 1})
+        flag, kwargs = funcs.get_data_uneven_flag(self.ws2d_histo_rag, other_kwarg=2)
+        self.assertFalse(flag)
+        self.assertEquals(kwargs, {'other_kwarg': 2})
+        flag, kwargs = funcs.get_data_uneven_flag(self.ws2d_histo_uneven, axisaligned=False, other_kwarg=3)
+        self.assertTrue(flag)
+        self.assertEquals(kwargs, {'other_kwarg': 3})
+
+    def test_boundaries_from_points(self):
+        centers = np.array([1., 2., 4., 8.])
+        bounds = funcs.boundaries_from_points(centers)
+        self.assertTrue(np.array_equal(bounds, np.array([0.5, 1.5, 3, 6, 10])))
+
+    def test_points_from_boundaries(self):
+        bounds = np.array([1., 3, 4, 10])
+        centers = funcs.points_from_boundaries(bounds)
+        self.assertTrue(np.array_equal(centers, np.array([2., 3.5, 7])))
+
+    def test_get_spectrum(self):
+        # get data divided by bin width
+        x, y, dy, dx = funcs.get_spectrum(self.ws2d_histo, 1, False, withDy=True, withDx=True)
+        self.assertTrue(np.array_equal(x, np.array([15., 25.])))
+        self.assertTrue(np.array_equal(y, np.array([.4, .5])))
+        self.assertTrue(np.array_equal(dy, np.array([.3, .4])))
+        self.assertEqual(dx, None)
+        # get data not divided by bin width
+        x, y, dy, dx = funcs.get_spectrum(self.ws2d_histo, 0, True, withDy=True, withDx=True)
+        self.assertTrue(np.array_equal(x, np.array([15., 25.])))
+        self.assertTrue(np.array_equal(y, np.array([2, 3])))
+        self.assertTrue(np.array_equal(dy, np.array([1, 2])))
+        self.assertEqual(dx, None)
+        # fail case - try to find spectrum out of range
+        self.assertRaises(RuntimeError, funcs.get_spectrum, self.ws2d_histo, 10, True)
+
+    def test_get_md_data2d_bin_bounds(self):
+        x, y, data = funcs.get_md_data2d_bin_bounds(self.ws_MD_2d, mantid.api.MDNormalization.NoNormalization)
+        # logger.error(str(coords))
+        np.testing.assert_allclose(x, np.array([-3, -1.8, -0.6, 0.6, 1.8, 3]), atol=1e-10)
+        np.testing.assert_allclose(y, np.array([-10, -6, -2, 2, 6, 10.]), atol=1e-10)
+        np.testing.assert_allclose(data, np.arange(25).reshape(5, 5), atol=1e-10)
+
+    def test_get_md_data2d_bin_centers(self):
+        x, y, data = funcs.get_md_data2d_bin_centers(self.ws_MD_2d, mantid.api.MDNormalization.NumEventsNormalization)
+        np.testing.assert_allclose(x, np.array([-2.4, -1.2, 0, 1.2, 2.4]), atol=1e-10)
+        np.testing.assert_allclose(y, np.array([-8, -4, 0, 4, 8]), atol=1e-10)
+        np.testing.assert_allclose(data, np.arange(25).reshape(5, 5) * 0.1, atol=1e-10)
+
+    def test_get_md_data1d(self):
+        coords, data, err = funcs.get_md_data1d(self.ws_MD_1d, mantid.api.MDNormalization.NumEventsNormalization)
+        np.testing.assert_allclose(coords, np.array([-8, -4, 0, 4, 8]), atol=1e-10)
+
+    def test_get_matrix_2d_data_rect(self):
+        # contour from aligned point data
+        x, y, z = funcs.get_matrix_2d_data(self.ws2d_point, True, histogram2D=False)
+        np.testing.assert_allclose(x, np.array([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]))
+        np.testing.assert_allclose(y, np.array([[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]))
+        # mesh from aligned point data
+        x, y, z = funcs.get_matrix_2d_data(self.ws2d_point, True, histogram2D=True)
+        np.testing.assert_allclose(x, np.array(
+            [[0.5, 1.5, 2.5, 3.5, 4.5], [0.5, 1.5, 2.5, 3.5, 4.5], [0.5, 1.5, 2.5, 3.5, 4.5],
+             [0.5, 1.5, 2.5, 3.5, 4.5]]))
+        np.testing.assert_allclose(y, np.array(
+            [[0.5, 0.5, 0.5, 0.5, 0.5], [1.5, 1.5, 1.5, 1.5, 1.5], [2.5, 2.5, 2.5, 2.5, 2.5],
+             [3.5, 3.5, 3.5, 3.5, 3.5]]))
+        # contour from aligned histo data
+        x, y, z = funcs.get_matrix_2d_data(self.ws2d_histo, True, histogram2D=False)
+        np.testing.assert_allclose(x, np.array([[15, 25], [15, 25]]))
+        np.testing.assert_allclose(y, np.array([[5, 5], [7, 7]]))
+        # mesh from aligned histo data
+        x, y, z = funcs.get_matrix_2d_data(self.ws2d_histo, True, histogram2D=True)
+        np.testing.assert_allclose(x, np.array([[10, 20, 30], [10, 20, 30], [10, 20, 30]]))
+        np.testing.assert_allclose(y, np.array([[4, 4, 4], [6, 6, 6], [8, 8, 8]]))
+
+    def test_get_matrix_2d_data_rag(self):
+        # contour from ragged point data
+        x, y, z = funcs.get_matrix_2d_data(self.ws2d_point_rag, True, histogram2D=False)
+        np.testing.assert_allclose(x, np.array([[1, 2, 3, 4], [2, 4, 6, 8]]))
+        np.testing.assert_allclose(y, np.array([[1, 1, 1, 1], [2, 2, 2, 2]]))
+        # contour from ragged histo data
+        x, y, z = funcs.get_matrix_2d_data(self.ws2d_histo_rag, True, histogram2D=False)
+        np.testing.assert_allclose(x, np.array([[1.5, 2.5, 3.5, 4.5], [3, 5, 7, 9]]))
+        np.testing.assert_allclose(y, np.array([[6, 6, 6, 6], [8, 8, 8, 8]]))
+        # mesh from ragged point data
+        x, y, z = funcs.get_matrix_2d_data(self.ws2d_point_rag, True, histogram2D=True)
+        np.testing.assert_allclose(x, np.array([[0.5, 1.5, 2.5, 3.5, 4.5], [1, 3, 5, 7, 9], [1, 3, 5, 7, 9]]))
+        np.testing.assert_allclose(y, np.array(
+            [[0.5, 0.5, 0.5, 0.5, 0.5], [1.5, 1.5, 1.5, 1.5, 1.5], [2.5, 2.5, 2.5, 2.5, 2.5]]))
+        # mesh from ragged histo data
+        x, y, z = funcs.get_matrix_2d_data(self.ws2d_histo_rag, True, histogram2D=True)
+        np.testing.assert_allclose(x, np.array([[1, 2, 3, 4, 5], [2, 4, 6, 8, 10], [2, 4, 6, 8, 10]]))
+        np.testing.assert_allclose(y, np.array([[5, 5, 5, 5, 5], [7, 7, 7, 7, 7], [9, 9, 9, 9, 9]]))
+        # check that fails for uneven data
+        self.assertRaises(ValueError, funcs.get_matrix_2d_data, self.ws2d_point_uneven, True)
+
+    def test_get_uneven_data(self):
+        # even points
+        x, y, z = funcs.get_uneven_data(self.ws2d_point_rag, True)
+        np.testing.assert_allclose(x[0], np.array([0.5, 1.5, 2.5, 3.5, 4.5]))
+        np.testing.assert_allclose(x[1], np.array([1, 3, 5, 7, 9]))
+        np.testing.assert_allclose(y[0], np.array([0.5, 1.5]))
+        np.testing.assert_allclose(y[1], np.array([1.5, 2.5]))
+        np.testing.assert_allclose(z[0], np.array([2, 2, 2, 2]))
+        np.testing.assert_allclose(z[1], np.array([2, 2, 2, 2]))
+        # even histo
+        x, y, z = funcs.get_uneven_data(self.ws2d_histo_rag, True)
+        np.testing.assert_allclose(x[0], np.array([1, 2, 3, 4, 5]))
+        np.testing.assert_allclose(x[1], np.array([2, 4, 6, 8, 10]))
+        np.testing.assert_allclose(y[0], np.array([5, 7]))
+        np.testing.assert_allclose(y[1], np.array([7, 9]))
+        np.testing.assert_allclose(z[0], np.array([2, 2, 2, 2]))
+        np.testing.assert_allclose(z[1], np.array([2, 2, 2, 2]))
+        # uneven points
+        x, y, z = funcs.get_uneven_data(self.ws2d_point_uneven, True)
+        np.testing.assert_allclose(x[0], np.array([5, 15, 25, 35]))
+        np.testing.assert_allclose(x[1], np.array([10, 20, 30, 40, 50]))
+        np.testing.assert_allclose(y[0], np.array([0.5, 1.5]))
+        np.testing.assert_allclose(y[1], np.array([1.5, 2.5]))
+        np.testing.assert_allclose(z[0], np.array([1, 2, 3]))
+        np.testing.assert_allclose(z[1], np.array([1, 2, 3, 4]))
+        # uneven histo
+        x, y, z = funcs.get_uneven_data(self.ws2d_histo_uneven, True)
+        np.testing.assert_allclose(x[0], np.array([10, 20, 30, 40]))
+        np.testing.assert_allclose(x[1], np.array([15, 25, 35, 45, 55]))
+        np.testing.assert_allclose(y[0], np.array([10, 15]))
+        np.testing.assert_allclose(y[1], np.array([15, 25]))
+        np.testing.assert_allclose(z[0], np.array([1, 2, 3]))
+        np.testing.assert_allclose(z[1], np.array([1, 2, 3, 4]))
+
+    def test_get_sample_logs(self):
+        x, y, FullTime, LogName, units, kwargs = funcs.get_sample_log(self.ws2d_histo,LogName='my_log', FullTime=True)
+        self.assertEquals(x[0],datetime.datetime(2010,1,1,0,0,0))
+        self.assertEquals(x[1],datetime.datetime(2010,1,1,0,30,0))
+        self.assertEquals(x[2],datetime.datetime(2010,1,1,0,50,0))
+        np.testing.assert_allclose(y, np.array([100,15,100.2]))
+        self.assertTrue(FullTime)
+        self.assertEquals(LogName, 'my_log')
+        self.assertEquals(units, '')
+        self.assertEquals(kwargs, {})
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Framework/PythonInterface/test/python/mantid/plots/plotfunctions3DTest.py b/Framework/PythonInterface/test/python/mantid/plots/plotfunctions3DTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..28c364a3283348b2aefb98e3ce80a762b0546c5d
--- /dev/null
+++ b/Framework/PythonInterface/test/python/mantid/plots/plotfunctions3DTest.py
@@ -0,0 +1,112 @@
+from __future__ import (absolute_import, division, print_function)
+
+import numpy as np
+import matplotlib
+matplotlib.use('AGG')
+import matplotlib.pyplot as plt
+import unittest
+
+import mantid.api
+import mantid.plots.plotfunctions3D as funcs
+from mantid.kernel import config
+from mantid.simpleapi import CreateWorkspace, DeleteWorkspace, CreateMDHistoWorkspace,\
+                             ConjoinWorkspaces
+
+
+
+
+class PlotFunctions3DTest(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.g1da = config['graph1d.autodistribution']
+        config['graph1d.autodistribution'] = 'On'
+        cls.ws2d_histo = CreateWorkspace(DataX=[10, 20, 30, 10, 20, 30],
+                                         DataY=[2, 3, 4, 5],
+                                         DataE=[1, 2, 3, 4],
+                                         NSpec=2,
+                                         Distribution=True,
+                                         UnitX='Wavelength',
+                                         VerticalAxisUnit='DeltaE',
+                                         VerticalAxisValues=[4, 6, 8],
+                                         OutputWorkspace='ws2d_histo')
+        cls.ws2d_histo_rag = CreateWorkspace(DataX=[1, 2, 3, 4, 5, 2, 4, 6, 8, 10],
+                                             DataY=[2] * 8,
+                                             NSpec=2,
+                                             VerticalAxisUnit='DeltaE',
+                                             VerticalAxisValues=[5, 7, 9],
+                                             OutputWorkspace='ws2d_histo_rag')
+        cls.ws_MD_2d = CreateMDHistoWorkspace(Dimensionality=3,
+                                              Extents='-3,3,-10,10,-1,1',
+                                              SignalInput=range(25),
+                                              ErrorInput=range(25),
+                                              NumberOfEvents=10 * np.ones(25),
+                                              NumberOfBins='5,5,1',
+                                              Names='Dim1,Dim2,Dim3',
+                                              Units='MomentumTransfer,EnergyTransfer,Angstrom',
+                                              OutputWorkspace='ws_MD_2d')
+        cls.ws_MD_1d = CreateMDHistoWorkspace(Dimensionality=3,
+                                              Extents='-3,3,-10,10,-1,1',
+                                              SignalInput=range(5),
+                                              ErrorInput=range(5),
+                                              NumberOfEvents=10 * np.ones(5),
+                                              NumberOfBins='1,5,1',
+                                              Names='Dim1,Dim2,Dim3',
+                                              Units='MomentumTransfer,EnergyTransfer,Angstrom',
+                                              OutputWorkspace='ws_MD_1d')
+        cls.ws2d_point_uneven = CreateWorkspace(DataX=[10, 20, 30],
+                                                DataY=[1, 2, 3],
+                                                NSpec=1,
+                                                OutputWorkspace='ws2d_point_uneven')
+        wp = CreateWorkspace(DataX=[15, 25, 35, 45], DataY=[1, 2, 3, 4], NSpec=1)
+        ConjoinWorkspaces(cls.ws2d_point_uneven, wp, CheckOverlapping=False)
+        cls.ws2d_point_uneven = mantid.mtd['ws2d_point_uneven']
+        cls.ws2d_histo_uneven = CreateWorkspace(DataX=[10, 20, 30, 40],
+                                                DataY=[1, 2, 3],
+                                                NSpec=1,
+                                                OutputWorkspace='ws2d_histo_uneven')
+
+    @classmethod
+    def tearDownClass(cls):
+        config['graph1d.autodistribution'] = cls.g1da
+        DeleteWorkspace('ws2d_histo')
+        DeleteWorkspace('ws_MD_2d')
+        DeleteWorkspace('ws_MD_1d')
+        DeleteWorkspace('ws2d_point_uneven')
+
+    def test_3d_plot(self):
+        fig = plt.figure()
+        ax = fig.add_subplot(111, projection='mantid3d')
+        funcs.plot(ax, self.ws2d_histo, 'rs', specNum=1)
+        funcs.plot(ax, self.ws2d_histo, specNum=2)
+        funcs.plot(ax, self.ws_MD_1d)
+
+    def test_3d_scatter(self):
+        fig = plt.figure()
+        ax = fig.add_subplot(111, projection='mantid3d')
+        funcs.scatter(ax, self.ws2d_histo)
+        funcs.scatter(ax, self.ws_MD_2d)
+
+    def test_3d_wireframe(self):
+        fig = plt.figure()
+        ax = fig.add_subplot(111, projection='mantid3d')
+        funcs.plot_wireframe(ax, self.ws2d_histo)
+        funcs.plot_wireframe(ax, self.ws_MD_2d)
+
+    def test_3d_surface(self):
+        fig = plt.figure()
+        ax = fig.add_subplot(111, projection='mantid3d')
+        funcs.plot_surface(ax, self.ws2d_histo_rag)
+        funcs.plot_surface(ax, self.ws2d_histo, vmin=0)
+        funcs.plot_surface(ax, self.ws_MD_2d)
+
+    def test_3d_contour(self):
+        fig = plt.figure()
+        ax = fig.add_subplot(111, projection='mantid3d')
+        funcs.contour(ax, self.ws2d_histo_rag)
+        funcs.contourf(ax, self.ws2d_histo, vmin=0)
+        self.assertRaises(ValueError, funcs.contour, ax, self.ws2d_point_uneven)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Framework/PythonInterface/test/python/mantid/plots/plotfunctionsTest.py b/Framework/PythonInterface/test/python/mantid/plots/plotfunctionsTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..75bbf1d5e059e2c2e0dad1c0f265e337187e91ea
--- /dev/null
+++ b/Framework/PythonInterface/test/python/mantid/plots/plotfunctionsTest.py
@@ -0,0 +1,121 @@
+from __future__ import (absolute_import, division, print_function)
+
+import numpy as np
+import matplotlib
+matplotlib.use('AGG')
+import matplotlib.pyplot as plt
+import unittest
+
+import mantid.api
+import mantid.plots.plotfunctions as funcs
+from mantid.kernel import config
+from mantid.simpleapi import CreateWorkspace, DeleteWorkspace, CreateMDHistoWorkspace,\
+                             ConjoinWorkspaces, AddTimeSeriesLog
+
+
+
+
+class PlotFunctionsTest(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.g1da = config['graph1d.autodistribution']
+        config['graph1d.autodistribution'] = 'On'
+        cls.ws2d_histo = CreateWorkspace(DataX=[10, 20, 30, 10, 20, 30],
+                                         DataY=[2, 3, 4, 5],
+                                         DataE=[1, 2, 3, 4],
+                                         NSpec=2,
+                                         Distribution=True,
+                                         UnitX='Wavelength',
+                                         VerticalAxisUnit='DeltaE',
+                                         VerticalAxisValues=[4, 6, 8],
+                                         OutputWorkspace='ws2d_histo')
+        cls.ws2d_histo_rag = CreateWorkspace(DataX=[1, 2, 3, 4, 5, 2, 4, 6, 8, 10],
+                                             DataY=[2] * 8,
+                                             NSpec=2,
+                                             VerticalAxisUnit='DeltaE',
+                                             VerticalAxisValues=[5, 7, 9],
+                                             OutputWorkspace='ws2d_histo_rag')
+        cls.ws_MD_2d = CreateMDHistoWorkspace(Dimensionality=3,
+                                              Extents='-3,3,-10,10,-1,1',
+                                              SignalInput=range(25),
+                                              ErrorInput=range(25),
+                                              NumberOfEvents=10 * np.ones(25),
+                                              NumberOfBins='5,5,1',
+                                              Names='Dim1,Dim2,Dim3',
+                                              Units='MomentumTransfer,EnergyTransfer,Angstrom',
+                                              OutputWorkspace='ws_MD_2d')
+        cls.ws_MD_1d = CreateMDHistoWorkspace(Dimensionality=3,
+                                              Extents='-3,3,-10,10,-1,1',
+                                              SignalInput=range(5),
+                                              ErrorInput=range(5),
+                                              NumberOfEvents=10 * np.ones(5),
+                                              NumberOfBins='1,5,1',
+                                              Names='Dim1,Dim2,Dim3',
+                                              Units='MomentumTransfer,EnergyTransfer,Angstrom',
+                                              OutputWorkspace='ws_MD_1d')
+        cls.ws2d_point_uneven = CreateWorkspace(DataX=[10, 20, 30],
+                                                DataY=[1, 2, 3],
+                                                NSpec=1,
+                                                OutputWorkspace='ws2d_point_uneven')
+        wp = CreateWorkspace(DataX=[15, 25, 35, 45], DataY=[1, 2, 3, 4], NSpec=1)
+        ConjoinWorkspaces(cls.ws2d_point_uneven, wp, CheckOverlapping=False)
+        cls.ws2d_point_uneven = mantid.mtd['ws2d_point_uneven']
+        cls.ws2d_histo_uneven = CreateWorkspace(DataX=[10, 20, 30, 40],
+                                                DataY=[1, 2, 3],
+                                                NSpec=1,
+                                                OutputWorkspace='ws2d_histo_uneven')
+        AddTimeSeriesLog(cls.ws2d_histo, Name="my_log", Time="2010-01-01T00:00:00", Value=100)
+        AddTimeSeriesLog(cls.ws2d_histo, Name="my_log", Time="2010-01-01T00:30:00", Value=15)
+        AddTimeSeriesLog(cls.ws2d_histo, Name="my_log", Time="2010-01-01T00:50:00", Value=100.2)
+
+    @classmethod
+    def tearDownClass(cls):
+        config['graph1d.autodistribution'] = cls.g1da
+        DeleteWorkspace('ws2d_histo')
+        DeleteWorkspace('ws_MD_2d')
+        DeleteWorkspace('ws_MD_1d')
+        DeleteWorkspace('ws2d_point_uneven')
+
+    def test_1d_plots(self):
+        fig, ax = plt.subplots()
+        funcs.plot(ax, self.ws2d_histo, 'rs', specNum=1)
+        funcs.plot(ax, self.ws2d_histo, specNum=2, linewidth=6)
+        funcs.plot(ax, self.ws_MD_1d, 'bo')
+
+    def test_1d_log(self):
+        fig, ax = plt.subplots()
+        funcs.plot(ax, self.ws2d_histo, LogName='my_log')
+        ax1 = ax.twiny()
+        funcs.plot(ax1, self.ws2d_histo, LogName='my_log', FullTime=True)
+
+    def test_1d_errorbars(self):
+        fig, ax = plt.subplots()
+        funcs.errorbar(ax, self.ws2d_histo, 'rs', specNum=1)
+        funcs.errorbar(ax, self.ws2d_histo, specNum=2, linewidth=6)
+        funcs.errorbar(ax, self.ws_MD_1d, 'bo')
+
+    def test_1d_scatter(self):
+        fig, ax = plt.subplots()
+        funcs.scatter(ax, self.ws2d_histo, specNum=1)
+        funcs.scatter(ax, self.ws2d_histo, specNum=2)
+        funcs.scatter(ax, self.ws_MD_1d)
+
+    def test_2d_contours(self):
+        fig, ax = plt.subplots()
+        funcs.contour(ax, self.ws2d_histo_rag)
+        funcs.contourf(ax, self.ws2d_histo, vmin=0)
+        funcs.tricontour(ax, self.ws_MD_2d)
+        funcs.tricontourf(ax, self.ws_MD_2d)
+        self.assertRaises(ValueError, funcs.contour, ax, self.ws2d_point_uneven)
+
+    def test_2d_pcolors(self):
+        fig, ax = plt.subplots()
+        funcs.pcolor(ax, self.ws2d_histo_rag)
+        funcs.tripcolor(ax, self.ws2d_histo, vmin=0)
+        funcs.pcolormesh(ax, self.ws_MD_2d)
+        funcs.pcolorfast(ax, self.ws2d_point_uneven, vmin=-1)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Framework/PythonInterface/test/python/mantid/plots/plots__init__Test.py b/Framework/PythonInterface/test/python/mantid/plots/plots__init__Test.py
index fd08e0ac37be3e4f63583f8c72f464e59715bb9f..8c1a386ae85f10142c0b1567c013fb68b2268dff 100644
--- a/Framework/PythonInterface/test/python/mantid/plots/plots__init__Test.py
+++ b/Framework/PythonInterface/test/python/mantid/plots/plots__init__Test.py
@@ -1,12 +1,13 @@
 from __future__ import (absolute_import, division, print_function)
 
-import unittest
-from mantid.simpleapi import CreateWorkspace,DeleteWorkspace
 import matplotlib
 matplotlib.use('AGG')
-import mantid.plots
 import matplotlib.pyplot as plt
 import numpy as np
+import unittest
+
+from mantid.simpleapi import CreateWorkspace, DeleteWorkspace
+
 
 class Plots__init__Test(unittest.TestCase):
     '''
@@ -14,29 +15,44 @@ class Plots__init__Test(unittest.TestCase):
     '''
     @classmethod
     def setUpClass(cls):
-        cls.ws2d_histo = CreateWorkspace(DataX=[10,20,30,10,20,30,10,20,30],
-                                         DataY=[2,3,4,5,3,5],
-                                         DataE=[1,2,3,4,1,1],
+        cls.ws2d_histo = CreateWorkspace(DataX=[10, 20, 30, 10, 20, 30, 10, 20, 30],
+                                         DataY=[2, 3, 4, 5, 3, 5],
+                                         DataE=[1, 2, 3, 4, 1, 1],
                                          NSpec=3,
                                          Distribution=True,
                                          UnitX='Wavelength',
                                          VerticalAxisUnit='DeltaE',
-                                         VerticalAxisValues=[4,6,8],
+                                         VerticalAxisValues=[4, 6, 8],
                                          OutputWorkspace='ws2d_histo')
+
     @classmethod
     def tearDownClass(cls):
         DeleteWorkspace('ws2d_histo')
 
-    def test_1dplots(self):
-        fig, ax = plt.subplots(subplot_kw={'projection':'mantid'})
-        ax.plot(self.ws2d_histo,'rs',specNum=1)
-        ax.plot(self.ws2d_histo,specNum=2,linewidth=6)
-        ax.plot(np.arange(10),np.arange(10),'bo-')
+    def test_1d_plots(self):
+        fig, ax = plt.subplots(subplot_kw={'projection': 'mantid'})
+        # ax.plot(self.ws2d_histo, 'rs', specNum=1)
+        ax.plot(self.ws2d_histo, specNum=2, linewidth=6)
+        ax.plot(np.arange(10), np.arange(10), 'bo-')
+
+    def test_3d_plots(self):
+        fig = plt.figure()
+        ax = fig.add_subplot(111, projection='mantid3d')
+        ax.plot(self.ws2d_histo, specNum=1)
+        ax.plot(np.arange(10), np.arange(10), np.arange(10))
+        ax.plot_wireframe(self.ws2d_histo)
 
     def test_fail(self):
         fig, ax = plt.subplots()
-        self.assertRaises(Exception,ax.plot,self.ws2d_histo,'rs',specNum=1)
-        self.assertRaises(Exception,ax.pcolormesh,self.ws2d_histo)
+        self.assertRaises(Exception, ax.plot, self.ws2d_histo, 'rs', specNum=1)
+        self.assertRaises(Exception, ax.pcolormesh, self.ws2d_histo)
+
+    def test_fail_3d(self):
+        fig = plt.figure()
+        ax = fig.add_subplot(111, projection='3d')
+        self.assertRaises(Exception, ax.plot_wireframe, self.ws2d_histo)
+        self.assertRaises(Exception, ax.plot_surface, self.ws2d_histo)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/ApplyDetectorScanEffCorrTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/ApplyDetectorScanEffCorrTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..ac25a60f6a4c9eb8703f428df6c51267c3674973
--- /dev/null
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/ApplyDetectorScanEffCorrTest.py
@@ -0,0 +1,48 @@
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+import numpy as np
+from mantid.simpleapi import ApplyDetectorScanEffCorr, CreateWorkspace, CreateSampleWorkspace
+
+
+class ApplyDetectorScanEffCorrTest(unittest.TestCase):
+    def test_non_scanning_case(self):
+        input_ws = CreateSampleWorkspace(NumMonitors=1, NumBanks=6, BankPixelWidth=1, XMin=0, XMax=1, BinWidth=1)
+
+        calibration_x = np.array([0, 0, 0, 0, 0, 0])
+        calibration_y = np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
+
+        # Note the monitors are in the wrong place doing the test workspace creation like this - but it does not affect the test.
+        calibration_ws = CreateWorkspace(DataX=calibration_x, DataY=calibration_y, Nspec=calibration_y.size)
+
+        calibrated_ws = ApplyDetectorScanEffCorr(input_ws, calibration_ws)
+        for i in range(1, 7):
+            self.assertEquals(calibrated_ws.readY(i), input_ws.readY(i) * i)
+            self.assertEquals(calibrated_ws.readE(i), input_ws.readE(i) * i)
+
+    def test_simple_scanning_case(self):
+        input_ws = CreateSampleWorkspace(NumMonitors=1, NumBanks=6, BankPixelWidth=1, XMin=0, XMax=1, BinWidth=1, NumScanPoints=2)
+
+        calibration_x = np.array([0, 0, 0, 0, 0, 0])
+        calibration_y = np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
+
+        # Note the monitors are in the wrong place doing the test workspace creation like this - but it does not affect the test.
+        calibration_ws = CreateWorkspace(DataX=calibration_x, DataY=calibration_y, Nspec=calibration_y.size)
+
+        calibrated_ws = ApplyDetectorScanEffCorr(input_ws, calibration_ws)
+        for i in range(2, 14):
+            self.assertEquals(calibrated_ws.readY(i), input_ws.readY(i) * (i//2))
+            self.assertEquals(calibrated_ws.readE(i), input_ws.readE(i) * (i//2))
+
+    def test_mismatched_workspace_size(self):
+        input_ws = CreateSampleWorkspace(NumMonitors=1, NumBanks=6, BankPixelWidth=1, XMin=0, XMax=1, BinWidth=1)
+
+        calibration_x = np.array([0, 0, 0, 0, 0, 0])
+        calibration_y = np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
+        calibration_ws = CreateWorkspace(DataX=calibration_x, DataY=calibration_y, Nspec=calibration_y.size)
+
+        self.assertRaises(ValueError, ApplyDetectorScanEffCorr, InputWorkspace=input_ws,
+                          DetectorEfficiencyWorkspace=calibration_ws, OutputWorkspace='')
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt
index bdd1d608da3942b2ab048d043647a33b22ec6cb6..56a17e0502e8b196d0480345d4c22f73d315cee6 100644
--- a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt
@@ -10,6 +10,7 @@ set ( TEST_PY_FILES
   AlignComponentsTest.py
   AngularAutoCorrelationsSingleAxisTest.py
   AngularAutoCorrelationsTwoAxesTest.py
+  ApplyDetectorScanEffCorrTest.py
   BinWidthAtXTest.py
   CalculateSampleTransmissionTest.py
   CheckForSampleLogsTest.py
@@ -63,6 +64,7 @@ set ( TEST_PY_FILES
   MedianBinWidthTest.py
   MergeCalFilesTest.py
   MuscatSofQWTest.py
+  MuonMaxEntTest.py
   NMoldyn4InterpolationTest.py
   NormaliseSpectraTest.py
   RetrieveRunInfoTest.py
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/MuonMaxEntTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/MuonMaxEntTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..23fa9c348d64ec0fba3d99800b06eec9f2d757a4
--- /dev/null
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/MuonMaxEntTest.py
@@ -0,0 +1,71 @@
+from __future__ import (absolute_import, print_function)
+
+import numpy as np
+
+import unittest
+from mantid.simpleapi import *
+from mantid.api import *
+
+class MuonMaxEntTest(unittest.TestCase):
+    def genData(self):
+         x_data = np.linspace(0,30.,100)
+         e_data = np.cos(0.0*x_data)
+         y_data = np.sin(2.3*x_data+0.1)*np.exp(-x_data/2.19703)
+         inputData= CreateWorkspace(DataX=x_data,DataY=y_data,DataE=e_data,UnitX='Time')
+         return inputData
+
+    def genData2(self):
+         x_data = np.linspace(0,30.,100)
+         e_data = []
+         y_data = []
+         for xx in x_data:
+               y_data.append(np.sin(2.3*xx+0.1)*np.exp(-xx/2.19703))
+               e_data.append(np.cos(0.2*xx))
+         for xx in x_data:
+               y_data.append(np.sin(4.3*xx+0.2)*np.exp(-xx/2.19703))
+               e_data.append( np.cos(0.2*xx))
+
+         inputData= CreateWorkspace(DataX=x_data,DataY=y_data,DataE=e_data,NSpec=2,UnitX='Time')
+         return inputData
+
+    def cleanUp(self):
+        DeleteWorkspace("InputData")
+        DeleteWorkspace("freq")
+        DeleteWorkspace("time")
+        DeleteWorkspace("phase")
+
+    def test_executes(self):
+        inputData = self.genData()
+        MuonMaxent(InputWorkspace=inputData,Npts=32768,FitDeaDTime=False,FixPhases=True ,OuterIterations=1,InnerIterations=1,OutputWorkspace='freq',ReconstructedSpectra='time',OutputPhaseTable="phase")
+        freq = AnalysisDataService.retrieve("freq")   
+        time = AnalysisDataService.retrieve("time")
+        phase = AnalysisDataService.retrieve("phase")
+        self.assertEqual(freq.getNumberHistograms(),1)    
+        self.assertEqual(time.getNumberHistograms(),1)    
+        self.assertEqual(phase.rowCount(),1)    
+        self.cleanUp()
+
+    def test_exitOnNAN(self):
+        inputData = self.genData()
+        try:
+            MuonMaxent(InputWorkspace=inputData,Npts=32768,FitDeaDTime=False,FixPhases=True ,OuterIterations=10,InnerIterations=10,OutputWorkspace='freq',ReconstructedSpectra='time',OutputPhaseTable="phase")
+        except RuntimeError:
+            pass
+        else:
+           self.fail("should throw an error as it will get stuck in a loop due to NAN values")
+        finally:
+           DeleteWorkspace("InputData")
+
+    def test_multipleSpec(self):
+        inputData = self.genData2()
+        MuonMaxent(InputWorkspace=inputData,Npts=32768,FitDeaDTime=False,FixPhases=True ,OuterIterations=1,InnerIterations=1,OutputWorkspace='freq',ReconstructedSpectra='time',OutputPhaseTable="phase")
+        freq = AnalysisDataService.retrieve("freq")   
+        time = AnalysisDataService.retrieve("time")
+        phase = AnalysisDataService.retrieve("phase")
+        self.assertEqual(freq.getNumberHistograms(),1)    
+        self.assertEqual(time.getNumberHistograms(),2)    
+        self.assertEqual(phase.rowCount(),2)    
+        self.cleanUp()
+ 
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Framework/RemoteAlgorithms/CMakeLists.txt b/Framework/RemoteAlgorithms/CMakeLists.txt
index 2c57e7f243d902718eaab71c0672aa4756549919..7b2ad0fb0d4b281ffa26f5f0e137e1470de1efc2 100644
--- a/Framework/RemoteAlgorithms/CMakeLists.txt
+++ b/Framework/RemoteAlgorithms/CMakeLists.txt
@@ -95,8 +95,10 @@ set_target_properties ( RemoteAlgorithms PROPERTIES OUTPUT_NAME MantidRemoteAlgo
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( RemoteAlgorithms PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
-endif () 
+  set_target_properties(RemoteAlgorithms PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(RemoteAlgorithms PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
+endif ()
 
 # Add to the 'Framework' group in VS
 set_property ( TARGET RemoteAlgorithms PROPERTY FOLDER "MantidFramework" )
diff --git a/Framework/RemoteJobManagers/CMakeLists.txt b/Framework/RemoteJobManagers/CMakeLists.txt
index 197bbc0054c0b7fa0361a633ba0fc36497929c00..4bd444b58becd53ca3835a486b1f309afb2a763f 100644
--- a/Framework/RemoteJobManagers/CMakeLists.txt
+++ b/Framework/RemoteJobManagers/CMakeLists.txt
@@ -44,7 +44,9 @@ set_target_properties ( RemoteJobManagers PROPERTIES OUTPUT_NAME MantidRemoteJob
 
 if (OSX_VERSION VERSION_GREATER 10.8)
   set_target_properties ( RemoteJobManagers PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
-endif () 
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties ( RemoteJobManagers PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
+endif ()
 
 # Add to the 'Framework' group in VS
 set_property ( TARGET RemoteJobManagers PROPERTY FOLDER "MantidFramework" )
diff --git a/Framework/SINQ/CMakeLists.txt b/Framework/SINQ/CMakeLists.txt
index d9ff0695430a0ee2568ca25e8b7c29c676ed2a75..23141ba462e7d4b35098e20ec0ba7033aa94be7d 100644
--- a/Framework/SINQ/CMakeLists.txt
+++ b/Framework/SINQ/CMakeLists.txt
@@ -141,13 +141,15 @@ enable_precompiled_headers ( inc/MantidSINQ/PrecompiledHeader.h SRC_FILES )
 # Add the target for this directory
 add_library ( SINQ ${SRC_FILES} ${INC_FILES} )
 # Set the name of the generated library
-set_target_properties ( SINQ PROPERTIES OUTPUT_NAME MantidSINQ 
+set_target_properties ( SINQ PROPERTIES OUTPUT_NAME MantidSINQ
   COMPILE_DEFINITIONS "IN_MANTID_SINQ"
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( SINQ PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
-endif () 
+  set_target_properties(SINQ PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(SINQ PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
+endif ()
 
 # Add to the 'Framework' group in VS
 set_property ( TARGET SINQ PROPERTY FOLDER "MantidFramework" )
diff --git a/Framework/ScriptRepository/CMakeLists.txt b/Framework/ScriptRepository/CMakeLists.txt
index f70964c5520a457e0c22f10280894f22226ea0d1..30daa6f690d014962bd7963957004e99fec6c3f7 100644
--- a/Framework/ScriptRepository/CMakeLists.txt
+++ b/Framework/ScriptRepository/CMakeLists.txt
@@ -23,11 +23,13 @@ add_library ( ScriptRepository ${SRC_FILES} ${INC_FILES} )
 # Add the unit tests directory
 add_subdirectory ( test )
 # Set the name of the generated library
-set_target_properties ( ScriptRepository PROPERTIES OUTPUT_NAME MantidScriptRepository 
+set_target_properties ( ScriptRepository PROPERTIES OUTPUT_NAME MantidScriptRepository
   COMPILE_DEFINITIONS IN_MANTID_SCRIPTREPO
 )
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( ScriptRepository PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
+  set_target_properties(ScriptRepository PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(ScriptRepository PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 set_property (TARGET ScriptRepository PROPERTY FOLDER "MantidFramework")
diff --git a/Framework/TestHelpers/src/FileComparisonHelper.cpp b/Framework/TestHelpers/src/FileComparisonHelper.cpp
index ccf783e80f24838877556223c3d75f81d45e8f30..95e86c89afdfeabbb311a67536aa12be0a01e93e 100644
--- a/Framework/TestHelpers/src/FileComparisonHelper.cpp
+++ b/Framework/TestHelpers/src/FileComparisonHelper.cpp
@@ -12,15 +12,15 @@ using streamCharIter = std::istreambuf_iterator<char>;
 
 // Returns if the difference was due to EOL and fixes the position of the
 // stream if it was due to EOL
-bool isEolDifference(streamCharIter streamOne, streamCharIter streamTwo) {
+bool isEolDifference(streamCharIter &streamOne, streamCharIter &streamTwo) {
 
   // Check which is the Windows file stream (CR in CRLF)
   // and advance it by a character so we are back to LF on both
   if (*streamOne == '\r' && *streamTwo == '\n') {
 
-    streamOne++;
+    ++streamOne;
   } else if (*streamOne == '\n' && *streamTwo == '\r') {
-    streamTwo++;
+    ++streamTwo;
   } else {
     // Was not a different EOL so indicate false to that
     return false;
diff --git a/Framework/WorkflowAlgorithms/CMakeLists.txt b/Framework/WorkflowAlgorithms/CMakeLists.txt
index 9a83e8febbbff622f8470428999233ed6f77fe00..7bdb72de478154de29dec5ea24968c3423065de9 100644
--- a/Framework/WorkflowAlgorithms/CMakeLists.txt
+++ b/Framework/WorkflowAlgorithms/CMakeLists.txt
@@ -126,7 +126,9 @@ set_target_properties ( WorkflowAlgorithms PROPERTIES OUTPUT_NAME MantidWorkflow
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( WorkflowAlgorithms PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
+  set_target_properties(WorkflowAlgorithms PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(WorkflowAlgorithms PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 # Add to the 'Framework' group in VS
diff --git a/Framework/WorkflowAlgorithms/inc/MantidWorkflowAlgorithms/AlignAndFocusPowder.h b/Framework/WorkflowAlgorithms/inc/MantidWorkflowAlgorithms/AlignAndFocusPowder.h
index 79f53087c848a1ec2838fbf46df550c7e40b5d60..a5c16c6eb349f0b6b1eb89f28292c4e36866a639 100644
--- a/Framework/WorkflowAlgorithms/inc/MantidWorkflowAlgorithms/AlignAndFocusPowder.h
+++ b/Framework/WorkflowAlgorithms/inc/MantidWorkflowAlgorithms/AlignAndFocusPowder.h
@@ -1,9 +1,6 @@
 #ifndef MANTID_ALGORITHM_AlignAndFocusPowder_H_
 #define MANTID_ALGORITHM_AlignAndFocusPowder_H_
 
-//----------------------------------------------------------------------
-// Includes
-//----------------------------------------------------------------------
 #include "MantidAPI/DataProcessorAlgorithm.h"
 #include "MantidAPI/MatrixWorkspace_fwd.h"
 #include "MantidDataObjects/EventWorkspace.h"
@@ -60,13 +57,9 @@ 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 AlignAndFocusPowder : public API::DataProcessorAlgorithm {
+class DLLExport AlignAndFocusPowder
+    : public API::DistributedDataProcessorAlgorithm {
 public:
-  /// Constructor
-  AlignAndFocusPowder() : API::DataProcessorAlgorithm() {}
-  /// Destructor
-  ~AlignAndFocusPowder() override {}
-
   /// Algorithm's name for identification overriding a virtual method
   const std::string name() const override { return "AlignAndFocusPowder"; }
 
diff --git a/Framework/WorkflowAlgorithms/inc/MantidWorkflowAlgorithms/LoadEventAndCompress.h b/Framework/WorkflowAlgorithms/inc/MantidWorkflowAlgorithms/LoadEventAndCompress.h
index 7860af3fdb9d90571f84241769d7f6a947d80bfc..ce8aaf3dba4de77b153588cdbcd056751365b10f 100644
--- a/Framework/WorkflowAlgorithms/inc/MantidWorkflowAlgorithms/LoadEventAndCompress.h
+++ b/Framework/WorkflowAlgorithms/inc/MantidWorkflowAlgorithms/LoadEventAndCompress.h
@@ -45,6 +45,10 @@ protected:
   API::MatrixWorkspace_sptr processChunk(API::MatrixWorkspace_sptr &wksp,
                                          double filterBadPulses);
 
+  Parallel::ExecutionMode getParallelExecutionMode(
+      const std::map<std::string, Parallel::StorageMode> &storageModes)
+      const override;
+
 private:
   void init() override;
   void exec() override;
diff --git a/Framework/WorkflowAlgorithms/src/AlignAndFocusPowder.cpp b/Framework/WorkflowAlgorithms/src/AlignAndFocusPowder.cpp
index bbac83f7b1d27d44e4dcc9a582b18c0af388460d..efa41473f509565a852bcf62a7487e1084159090 100644
--- a/Framework/WorkflowAlgorithms/src/AlignAndFocusPowder.cpp
+++ b/Framework/WorkflowAlgorithms/src/AlignAndFocusPowder.cpp
@@ -421,12 +421,21 @@ void AlignAndFocusPowder::exec() {
   if (m_maskWS) {
     g_log.information() << "running MaskDetectors started at "
                         << Types::Core::DateAndTime::getCurrentTime() << "\n";
-    API::IAlgorithm_sptr maskAlg = createChildAlgorithm("MaskDetectors");
-    maskAlg->setProperty("Workspace", m_outputW);
-    maskAlg->setProperty("MaskedWorkspace", m_maskWS);
+    const auto &maskedDetectors = m_maskWS->getMaskedDetectors();
+    API::IAlgorithm_sptr maskAlg = createChildAlgorithm("MaskInstrument");
+    maskAlg->setProperty("InputWorkspace", m_outputW);
+    maskAlg->setProperty("OutputWorkspace", m_outputW);
+    maskAlg->setProperty(
+        "DetectorIDs",
+        std::vector<detid_t>(maskedDetectors.begin(), maskedDetectors.end()));
     maskAlg->executeAsChildAlg();
-    Workspace_sptr tmpW = maskAlg->getProperty("Workspace");
-    m_outputW = boost::dynamic_pointer_cast<MatrixWorkspace>(tmpW);
+    MatrixWorkspace_sptr tmpW = maskAlg->getProperty("OutputWorkspace");
+
+    API::IAlgorithm_sptr clearAlg = createChildAlgorithm("ClearMaskedSpectra");
+    clearAlg->setProperty("InputWorkspace", tmpW);
+    clearAlg->setProperty("OutputWorkspace", tmpW);
+    clearAlg->executeAsChildAlg();
+    m_outputW = clearAlg->getProperty("OutputWorkspace");
     m_outputEW = boost::dynamic_pointer_cast<EventWorkspace>(m_outputW);
   }
   m_progress->report();
diff --git a/Framework/WorkflowAlgorithms/src/LoadEventAndCompress.cpp b/Framework/WorkflowAlgorithms/src/LoadEventAndCompress.cpp
index 15744aa6e5cddd49dfb7e687a520fc4793dbcba6..7d97cad9c7ae815e0123bbbb8dcac1b3449db8aa 100644
--- a/Framework/WorkflowAlgorithms/src/LoadEventAndCompress.cpp
+++ b/Framework/WorkflowAlgorithms/src/LoadEventAndCompress.cpp
@@ -239,5 +239,11 @@ void LoadEventAndCompress::exec() {
   setProperty("OutputWorkspace", total);
 }
 
+Parallel::ExecutionMode LoadEventAndCompress::getParallelExecutionMode(
+    const std::map<std::string, Parallel::StorageMode> &storageModes) const {
+  static_cast<void>(storageModes);
+  return Parallel::ExecutionMode::Distributed;
+}
+
 } // namespace WorkflowAlgorithms
 } // namespace Mantid
diff --git a/MantidPlot/CMakeLists.txt b/MantidPlot/CMakeLists.txt
index cff02fdbb024d42e9815dcd26b2d77f10c982658..14147fcf7c18fde969c2d50b0897df1be755348e 100644
--- a/MantidPlot/CMakeLists.txt
+++ b/MantidPlot/CMakeLists.txt
@@ -825,6 +825,13 @@ add_dependencies( MantidPlot mantidqtpython CompilePyUI )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
   set_target_properties(MantidPlot PROPERTIES INSTALL_RPATH "@executable_path;@executable_path/../Libraries")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  if(MAKE_VATES)
+    set_target_properties(MantidPlot PROPERTIES
+                          INSTALL_RPATH "\$ORIGIN/../${LIB_DIR};\$ORIGIN/../${LIB_DIR}/paraview-${ParaView_VERSION_MAJOR}.${ParaView_VERSION_MINOR}")
+  else()
+    set_target_properties(MantidPlot PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
+  endif()
 endif ()
 
 set_target_properties ( MantidPlot PROPERTIES FOLDER "Qt4" )
diff --git a/MantidPlot/src/ApplicationWindow.cpp b/MantidPlot/src/ApplicationWindow.cpp
index 1c0f820e17aa5493c0030312e1db007f881606d7..daa9a930eb8b3c3f511445e5d3be95655c114577 100644
--- a/MantidPlot/src/ApplicationWindow.cpp
+++ b/MantidPlot/src/ApplicationWindow.cpp
@@ -11558,6 +11558,32 @@ void ApplicationWindow::setUpdateCurvesFromTable(Table *table, bool on) {
   }
 }
 
+/** Fixes the colour pallete so that the hints are readable.
+
+  On Linux (tested on Fedora 26 and Ubuntu 14.4) the palette colour for
+  ToolTipBase has no effect on the colour of tooltips, but does change
+  the colour of 'What's This' boxes and and Line Edit hints. The palette
+  colour for ToolTipText on the other hand affects all three of
+  these.
+
+  The default pallete shows light text on a pale background which, although not
+  affecting tooltips, makes LineEdit hints and 'What's This' boxes difficuilt
+  if not impossible to read.
+
+  Changing the tooltip text to a darker colour fixes the problem for 'LineEdit'
+  hints and 'What's This' boxes but creates one for ordinary tooltips.
+  Instead we set the background colour to a darker one, consistent with
+  ordinary tooltips.
+*/
+void ApplicationWindow::patchPaletteForLinux(QPalette &palette) const {
+  auto tooltipColorApprox =
+      palette.color(QPalette::ColorGroup::Active, QPalette::Text);
+  palette.setColor(QPalette::ColorGroup::Inactive, QPalette::ToolTipBase,
+                   tooltipColorApprox);
+  palette.setColor(QPalette::ColorGroup::Active, QPalette::ToolTipBase,
+                   tooltipColorApprox);
+}
+
 void ApplicationWindow::setAppColors(const QColor &wc, const QColor &pc,
                                      const QColor &tpc, bool force) {
   if (force || workspaceColor != wc) {
@@ -11572,6 +11598,11 @@ void ApplicationWindow::setAppColors(const QColor &wc, const QColor &pc,
   panelsTextColor = tpc;
 
   QPalette palette;
+
+#ifdef Q_OS_LINUX
+  patchPaletteForLinux(palette);
+#endif
+
   palette.setColor(QPalette::Base, QColor(panelsColor));
   qApp->setPalette(palette);
 
@@ -13504,14 +13535,12 @@ ApplicationWindow *ApplicationWindow::importOPJ(const QString &filename,
     app->recentProjects.push_front(filename);
     app->updateRecentProjectsList();
 
-    // cppcheck-suppress unusedScopedObject
     ImportOPJ(app, filename);
 
     QApplication::restoreOverrideCursor();
     return app;
   } else if (filename.endsWith(".ogm", Qt::CaseInsensitive) ||
              filename.endsWith(".ogw", Qt::CaseInsensitive)) {
-    // cppcheck-suppress unusedScopedObject
     ImportOPJ(this, filename);
     recentProjects.removeAll(filename);
     recentProjects.push_front(filename);
@@ -15241,7 +15270,6 @@ void ApplicationWindow::scriptsDirPathChanged(const QString &path) {
 }
 
 void ApplicationWindow::makeToolbarsMenu() {
-  // cppcheck-suppress publicAllocationError
   actionFileTools = new QAction(standardTools->windowTitle(), toolbarsMenu);
   actionFileTools->setCheckable(true);
   toolbarsMenu->addAction(actionFileTools);
@@ -15550,7 +15578,6 @@ void ApplicationWindow::showUserDirectoryDialog() {
   ad->setAttribute(Qt::WA_DeleteOnClose);
   ad->show();
   ad->setFocus();
-  // cppcheck-suppress memleak
 }
 
 void ApplicationWindow::addCustomAction(QAction *action,
diff --git a/MantidPlot/src/ApplicationWindow.h b/MantidPlot/src/ApplicationWindow.h
index 42b07ab063afa88b93ac3d0a98bdc2655ca237c8..15ea1dcb045875049097b5e8fde4cfdaf0a8d311 100644
--- a/MantidPlot/src/ApplicationWindow.h
+++ b/MantidPlot/src/ApplicationWindow.h
@@ -1141,7 +1141,7 @@ private:
   void handleConfigDir();
   /// Save the working directory to QSettings
   void cacheWorkingDirectory() const;
-
+  void patchPaletteForLinux(QPalette &palette) const;
 private slots:
   //! \name Initialization
   //@{
diff --git a/Testing/Data/SystemTest/FE_ALPHA.cif.md5 b/Testing/Data/SystemTest/FE_ALPHA.cif.md5
deleted file mode 100644
index e004f2c6113ee3505066af65c9c97cc926d69675..0000000000000000000000000000000000000000
--- a/Testing/Data/SystemTest/FE_ALPHA.cif.md5
+++ /dev/null
@@ -1 +0,0 @@
-b184c411cf657178803326c733b2a34c
\ No newline at end of file
diff --git a/Testing/Data/SystemTest/Fe-alpha.cif.md5 b/Testing/Data/SystemTest/Fe-alpha.cif.md5
new file mode 100644
index 0000000000000000000000000000000000000000..6033502b7dee9e38ac9939ac8418d23b469366b0
--- /dev/null
+++ b/Testing/Data/SystemTest/Fe-alpha.cif.md5
@@ -0,0 +1 @@
+f10c2b40100f2b8fd1a9d5b9c956e742
\ No newline at end of file
diff --git a/Testing/Data/SystemTest/Fe-gamma.cif.md5 b/Testing/Data/SystemTest/Fe-gamma.cif.md5
new file mode 100644
index 0000000000000000000000000000000000000000..a5f7dcaf74135ebf360b5240437612db098e0dd7
--- /dev/null
+++ b/Testing/Data/SystemTest/Fe-gamma.cif.md5
@@ -0,0 +1 @@
+70b6d024258f5e08e6203197d3d446ea
\ No newline at end of file
diff --git a/Testing/Data/SystemTest/ISIS_Powder/input/HRPD66063_focused.nxs.md5 b/Testing/Data/SystemTest/ISIS_Powder/input/HRPD66063_focused.nxs.md5
index 8433b20456a650ff57aeec623634208618a7cb99..902a2e11b6e97bbb832ddea12cc28e71cb191110 100644
--- a/Testing/Data/SystemTest/ISIS_Powder/input/HRPD66063_focused.nxs.md5
+++ b/Testing/Data/SystemTest/ISIS_Powder/input/HRPD66063_focused.nxs.md5
@@ -1 +1 @@
-6ea1c2ce8438d0e6fd797d65aef02176
\ No newline at end of file
+108ad48ccf2804be44f860741c380e8c
\ No newline at end of file
diff --git a/Testing/Data/SystemTest/ISIS_Powder/input/calibration/hrp/16_5/ISIS_Powder-HRPD-VanSplined_66031_hrpd_new_072_01_corr.cal.nxs.md5 b/Testing/Data/SystemTest/ISIS_Powder/input/calibration/hrp/16_5/ISIS_Powder-HRPD-VanSplined_66031_hrpd_new_072_01_corr.cal.nxs.md5
index a5c06926af13ce27945e90d9cb3d99f9a3e03054..4ab74e55709f8803e57ea2f940a187bffa72c4cc 100644
--- a/Testing/Data/SystemTest/ISIS_Powder/input/calibration/hrp/16_5/ISIS_Powder-HRPD-VanSplined_66031_hrpd_new_072_01_corr.cal.nxs.md5
+++ b/Testing/Data/SystemTest/ISIS_Powder/input/calibration/hrp/16_5/ISIS_Powder-HRPD-VanSplined_66031_hrpd_new_072_01_corr.cal.nxs.md5
@@ -1 +1 @@
-109a72ffc8f2f708466170abd8f7b1e4
\ No newline at end of file
+ee6c5910b3f940ee888a7e7a28cbf077
\ No newline at end of file
diff --git a/Testing/Data/SystemTest/Sm2O3.cif.md5 b/Testing/Data/SystemTest/Sm2O3.cif.md5
new file mode 100644
index 0000000000000000000000000000000000000000..4ca349a92ca0f0a33fefdf7e4e9a73c8310bbcf6
--- /dev/null
+++ b/Testing/Data/SystemTest/Sm2O3.cif.md5
@@ -0,0 +1 @@
+1d127c4d48f67c2260e06e1844e87725
diff --git a/Testing/Data/UnitTest/MAR21335_Ei60meV.nxs.md5 b/Testing/Data/UnitTest/MAR21335_Ei60meV.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..24eea1e58e1f4ed55ef0fcce610e83f8933eb98a
--- /dev/null
+++ b/Testing/Data/UnitTest/MAR21335_Ei60meV.nxs.md5
@@ -0,0 +1 @@
+a7cfd700137ce27025208323fc930e35
diff --git a/Testing/SystemTests/tests/analysis/CrystalFieldPythonInterface.py b/Testing/SystemTests/tests/analysis/CrystalFieldPythonInterface.py
new file mode 100644
index 0000000000000000000000000000000000000000..7dbf920e6b907049e3f99fbc7bcd66e371abd162
--- /dev/null
+++ b/Testing/SystemTests/tests/analysis/CrystalFieldPythonInterface.py
@@ -0,0 +1,391 @@
+#pylint: disable=no-init
+from stresstesting import MantidStressTest
+from mantid.simpleapi import CreateWorkspace, LinearBackground, FlatBackground, Gaussian, CalculateChiSquared, mtd
+from CrystalField.fitting import makeWorkspace
+from PyChop import PyChop2
+import numpy as np
+
+
+class CrystalFieldPythonInterface(MantidStressTest):
+    """ Runs all the commands in the crystal field python interface documentation file
+        This test only checks that the syntax of all the commands are still valid.
+        Unit tests for the individual functions / fitting procedure check for correctness.
+    """
+
+    def my_create_ws(self, outwsname, x, y):
+        jitter = (np.random.rand(np.shape(y)[0])-0.5)*np.max(y)/100
+        CreateWorkspace(x, y + jitter, y*0+np.max(y)/100, Distribution=True, OutputWorkspace=outwsname)
+        return mtd[outwsname]
+
+    def my_func(self, en):
+        return (25-en)**(1.5) / 200 + 0.1
+
+    def runTest(self):
+        from CrystalField import CrystalField, CrystalFieldFit, CrystalFieldMultiSite, Background, Function, ResolutionModel
+
+        cf = CrystalField('Ce', 'C2v')
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770)
+        cf['B40'] = -0.031
+        b = cf['B40']
+
+        # Calculate and return the Hamiltonian matrix as a 2D numpy array.
+        h = cf.getHamiltonian()
+        print(h)
+        # Calculate and return the eigenvalues of the Hamiltonian as a 1D numpy array.
+        e = cf.getEigenvalues()
+        print(e)
+        # Calculate and return the eigenvectors of the Hamiltonian as a 2D numpy array.
+        w = cf.getEigenvectors()
+        print(w)
+        # Using the keyword argument
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, Temperature=44)
+        # Using the property
+        cf.Temperature = 44
+
+        print(cf.getPeakList())
+        #[[  0.00000000e+00   2.44006198e+01   4.24977124e+01   1.80970926e+01 -2.44006198e+01]
+        # [  2.16711565e+02   8.83098530e+01   5.04430056e+00   1.71153708e-01  1.41609425e-01]]
+        cf.ToleranceIntensity = 1
+        print(cf.getPeakList())
+        #[[   0.           24.40061976   42.49771237]
+        # [ 216.71156467   88.30985303    5.04430056]]
+        cf.PeakShape = 'Gaussian'
+        cf.FWHM = 0.9
+        sp = cf.getSpectrum()
+        print(cf.function)
+        CrystalField_Ce = CreateWorkspace(*sp)
+        print(CrystalField_Ce)
+
+        # If the peak shape is Gaussian
+        cf.peaks.param[1]['Sigma'] = 2.0
+        cf.peaks.param[2]['Sigma'] = 0.01
+
+        # If the peak shape is Lorentzian
+        cf.PeakShape = 'Lorentzian'
+        cf.peaks.param[1]['FWHM'] = 2.0
+        cf.peaks.param[2]['FWHM'] = 0.01
+
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544,
+                          Temperature=44.0, FWHM=1.1)
+        cf.background = Background(peak=Function('Gaussian', Height=10, Sigma=1),
+                                   background=Function('LinearBackground', A0=1.0, A1=0.01))
+        h = cf.background.peak.param['Height']
+        a1 = cf.background.background.param['A1']
+        print(h)
+        print(a1)
+
+        cf.ties(B20=1.0, B40='B20/2')
+        cf.constraints('1 < B22 <= 2', 'B22 < 4')
+
+        print(cf.function[1].getTies())
+        print(cf.function[1].getConstraints())
+
+        cf.background.peak.ties(Height=10.1)
+        cf.background.peak.constraints('Sigma > 0')
+        cf.background.background.ties(A0=0.1)
+        cf.background.background.constraints('A1 > 0')
+
+        print(cf.function[0][0].getConstraints())
+        print(cf.function[0][1].getConstraints())
+        print(cf.function.getTies())
+        print(cf.function.getConstraints())
+
+        cf.peaks.ties({'f2.FWHM': '2*f1.FWHM', 'f3.FWHM': '2*f2.FWHM'})
+        cf.peaks.constraints('f0.FWHM < 2.2', 'f1.FWHM >= 0.1')
+
+        cf.PeakShape = 'Gaussian'
+        cf.peaks.tieAll('Sigma=0.1', 3)
+        cf.peaks.constrainAll('0 < Sigma < 0.1', 4)
+        cf.peaks.tieAll('Sigma=f0.Sigma', 1, 3)
+        cf.peaks.ties({'f1.Sigma': 'f0.Sigma', 'f2.Sigma': 'f0.Sigma', 'f3.Sigma': 'f0.Sigma'})
+
+        rm = ResolutionModel(([1, 2, 3, 100], [0.1, 0.3, 0.35, 2.1]))
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, Temperature=44.0, ResolutionModel=rm)
+
+        rm = ResolutionModel(self.my_func, xstart=0.0, xend=24.0, accuracy=0.01)
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, Temperature=44.0, ResolutionModel=rm)
+
+        marires = PyChop2('MARI')
+        marires.setChopper('S')
+        marires.setFrequency(250)
+        marires.setEi(30)
+        rm = ResolutionModel(marires.getResolution, xstart=0.0, xend=29.0, accuracy=0.01)
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, Temperature=44.0, ResolutionModel=rm)
+
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, Temperature=44.0, ResolutionModel=rm, FWHMVariation=0.1)
+
+        # ---------------------------
+
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544,
+                          Temperature=[44.0, 50], FWHM=[1.1, 0.9])
+        cf.PeakShape = 'Lorentzian'
+        cf.peaks[0].param[0]['FWHM'] = 1.11
+        cf.peaks[1].param[1]['FWHM'] = 1.12
+        cf.background = Background(peak=Function('Gaussian', Height=10, Sigma=0.3),
+                                   background=Function('FlatBackground', A0=1.0))
+        cf.background[1].peak.param['Sigma'] = 0.8
+        cf.background[1].background.param['A0'] = 1.1
+
+        # The B parameters are common for all spectra - syntax doesn't change
+        cf.ties(B20=1.0, B40='B20/2')
+        cf.constraints('1 < B22 <= 2', 'B22 < 4')
+
+        # Backgrounds and peaks are different for different spectra - must be indexed
+        cf.background[0].peak.ties(Height=10.1)
+        cf.background[0].peak.constraints('Sigma > 0.1')
+        cf.background[1].peak.ties(Height=20.2)
+        cf.background[1].peak.constraints('Sigma > 0.2')
+        cf.peaks[1].tieAll('FWHM=2*f1.FWHM', 2, 5)
+        cf.peaks[0].constrainAll('FWHM < 2.2', 1, 4)
+
+        rm = ResolutionModel([self.my_func, marires.getResolution], 0, 100, accuracy = 0.01)
+        cf.ResolutionModel = rm
+
+        # Calculate second spectrum, use the generated x-values
+        sp = cf.getSpectrum(1)
+        # Calculate second spectrum, use the first spectrum of a workspace
+        sp = cf.getSpectrum(1, 'CrystalField_Ce')
+        # Calculate first spectrum, use the i-th spectrum of a workspace
+        i=0
+        sp = cf.getSpectrum(0, 'CrystalField_Ce', i)
+
+        print(cf.function)
+
+        cf.Temperature = [5, 50, 150]
+
+        print()
+        print(cf.function)
+
+        ws = 'CrystalField_Ce'
+        ws1 = 'CrystalField_Ce'
+        ws2 = 'CrystalField_Ce'
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544, Temperature=5)
+
+        # In case of a single spectrum (ws is a workspace)
+        fit = CrystalFieldFit(Model=cf, InputWorkspace=ws)
+
+        # Or for multiple spectra
+        fit = CrystalFieldFit(Model=cf, InputWorkspace=[ws1, ws2])
+        cf.Temperature = [5, 50]
+        fit.fit()
+
+        params = {'B20': 0.377, 'B22': 3.9, 'B40': -0.03, 'B42': -0.116, 'B44': -0.125,
+                  'Temperature': [44.0, 50], 'FWHM': [1.1, 0.9]}
+        cf1 = CrystalField('Ce', 'C2v', **params)
+        cf2 = CrystalField('Pr', 'C2v', **params)
+        cfms = cf1 + cf2
+        cf = 2*cf1 + cf2
+
+        cfms = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0], FWHMs=[1.1])
+        cfms['ion0.B40'] = -0.031
+        cfms['ion1.B20'] = 0.37737
+        b = cfms['ion0.B22']
+
+        print(b)
+        print(cfms.function)
+
+        cfms = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0], FWHMs=[1.1],
+                                     parameters={'ion0.B20': 0.37737, 'ion0.B22': 3.9770, 'ion1.B40':-0.031787,
+                                                 'ion1.B42':-0.11611, 'ion1.B44':-0.12544})
+        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0],
+                                     Background='name=Gaussian,Height=0,PeakCentre=1,Sigma=0;name=LinearBackground,A0=0,A1=0')
+        cfms = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[50], FWHMs=[0.9],
+                                     Background=LinearBackground(A0=1.0), BackgroundPeak=Gaussian(Height=10, Sigma=0.3))
+        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0],
+                                     Background= Gaussian(PeakCentre=1) + LinearBackground())
+        cfms = CrystalFieldMultiSite(Ions=['Ce','Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44, 50], FWHMs=[1.1, 0.9],
+                                     Background=FlatBackground(), BackgroundPeak=Gaussian(Height=10, Sigma=0.3),
+                                     parameters={'ion0.B20': 0.37737, 'ion0.B22': 3.9770, 'ion1.B40':-0.031787,
+                                                 'ion1.B42':-0.11611, 'ion1.B44':-0.12544})
+        cfms.ties({'sp0.bg.f0.Height': 10.1})
+        cfms.constraints('sp0.bg.f0.Sigma > 0.1')
+        cfms.constraints('ion0.sp0.pk1.FWHM < 2.2')
+        cfms.ties({'ion0.sp1.pk2.FWHM': '2*ion0.sp1.pk1.FWHM', 'ion1.sp1.pk3.FWHM': '2*ion1.sp1.pk2.FWHM'})
+
+        # --------------------------
+
+        params = {'ion0.B20': 0.37737, 'ion0.B22': 3.9770, 'ion1.B40':-0.031787, 'ion1.B42':-0.11611, 'ion1.B44':-0.12544}
+        cf = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0, 50.0],
+                                   FWHMs=[1.0, 1.0], ToleranceIntensity=6.0, ToleranceEnergy=1.0,  FixAllPeaks=True,
+                                   parameters=params)
+
+        cf.fix('ion0.BmolX', 'ion0.BmolY', 'ion0.BmolZ', 'ion0.BextX', 'ion0.BextY', 'ion0.BextZ', 'ion0.B40',
+               'ion0.B42', 'ion0.B44', 'ion0.B60', 'ion0.B62', 'ion0.B64', 'ion0.B66', 'ion0.IntensityScaling',
+               'ion1.BmolX', 'ion1.BmolY', 'ion1.BmolZ', 'ion1.BextX', 'ion1.BextY', 'ion1.BextZ', 'ion1.B40',
+               'ion1.B42', 'ion1.B44', 'ion1.B60', 'ion1.B62', 'ion1.B64', 'ion1.B66', 'ion1.IntensityScaling',
+               'sp0.IntensityScaling', 'sp1.IntensityScaling')
+
+        chi2 = CalculateChiSquared(str(cf.function), InputWorkspace=ws1, InputWorkspace_1=ws2)[1]
+
+        fit = CrystalFieldFit(Model=cf, InputWorkspace=[ws1, ws2], MaxIterations=10)
+        fit.fit()
+
+        print(chi2)
+
+        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2', Temperatures=[25], FWHMs=[1.0], PeakShape='Gaussian',
+                                     BmolX=1.0, B40=-0.02)
+        print(str(cfms.function).split(',')[0])
+
+        # --------------------------
+
+        # Create some crystal field data
+        origin = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544,
+                              Temperature=44.0, FWHM=1.1)
+        x, y = origin.getSpectrum()
+        ws = makeWorkspace(x, y)
+
+        # Define a CrystalField object with parameters slightly shifted.
+        cf = CrystalField('Ce', 'C2v', B20=0, B22=0, B40=0, B42=0, B44=0,
+                          Temperature=44.0, FWHM=1.0, ResolutionModel=([0, 100], [1, 1]), FWHMVariation=0)
+
+        # Set any ties on the field parameters.
+        cf.ties(B20=0.37737)
+        # Create a fit object
+        fit = CrystalFieldFit(cf, InputWorkspace=ws)
+        # Find initial values for the field parameters.
+        # You need to define the energy splitting and names of parameters to estimate.
+        # Optionally additional constraints can be set on tied parameters (eg, peak centres).
+        fit.estimate_parameters(EnergySplitting=50,
+                                Parameters=['B22', 'B40', 'B42', 'B44'],
+                                Constraints='20<f1.PeakCentre<45,20<f2.PeakCentre<45',
+                                NSamples=1000)
+        print('Returned', fit.get_number_estimates(), 'sets of parameters.')
+        # The first set (the smallest chi squared) is selected by default.
+        # Select a different parameter set if required
+        fit.select_estimated_parameters(3)
+        print(cf['B22'], cf['B40'], cf['B42'], cf['B44'])
+        # Run fit
+        fit.fit()
+
+        # --------------------------
+
+        from CrystalField import PointCharge
+        axial_pc_model = PointCharge([[-2, 0, 0, -4], [-2, 0, 0, 4]], 'Nd')
+        axial_blm = axial_pc_model.calculate()
+        print(axial_blm)
+
+        from CrystalField import PointCharge
+        from mantid.geometry import CrystalStructure
+        perovskite_structure = CrystalStructure('4 4 4 90 90 90', 'P m -3 m', 'Ce 0 0 0 1 0; Al 0.5 0.5 0.5 1 0; O 0.5 0.5 0 1 0')
+        cubic_pc_model = PointCharge(perovskite_structure, 'Ce', Charges={'Ce':3, 'Al':3, 'O':-2}, MaxDistance=7.5)
+
+        cubic_pc_model = PointCharge(perovskite_structure, 'Ce', Charges={'Ce':3, 'Al':3, 'O':-2}, Neighbour=2)
+        print(cubic_pc_model)
+
+        cif_pc_model = PointCharge('Sm2O3.cif')
+        print(cif_pc_model.getIons())
+
+        cif_pc_model.Charges = {'O1':-2, 'O2':-2, 'Sm1':3, 'Sm2':3, 'Sm3':3}
+        cif_pc_model.IonLabel = 'Sm2'
+        cif_pc_model.Neighbour = 1
+        cif_blm = cif_pc_model.calculate()
+        print(cif_blm)
+        bad_pc_model = PointCharge('Sm2O3.cif', MaxDistance=7.5, Neighbour=2)
+        print(bad_pc_model.Neighbour)
+        print(bad_pc_model.MaxDistance)
+
+        cif_pc_model.Charges = {'O':-2, 'Sm':3}
+        cif_blm = cif_pc_model.calculate()
+        print(cif_blm)
+
+        cf = CrystalField('Sm', 'C2', Temperature=5, FWHM=10, **cif_pc_model.calculate())
+        fit = CrystalFieldFit(cf, InputWorkspace=ws)
+
+        fit = CrystalFieldFit(cf, InputWorkspace=ws)
+        fit.fit()
+
+        # --------------------------
+
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, Temperature=44.0)
+        Cv = cf.getHeatCapacity()       # Calculates Cv(T) for 1<T<300K in 1K steps  (default)
+
+        T = np.arange(1,900,5)
+        Cv = cf.getHeatCapacity(T)      # Calculates Cv(T) for specified values of T (1 to 900K in 5K steps here)
+
+        # Temperatures from a single spectrum workspace
+        ws = CreateWorkspace(T, T, T)
+        Cv = cf.getHeatCapacity(ws)     # Use the x-values of a workspace as the temperatures
+        ws_cp = CreateWorkspace(*Cv)
+
+        # Temperatures from a multi-spectrum workspace
+        ws = CreateWorkspace(T, T, T, NSpec=2)
+        Cv = cf.getHeatCapacity(ws, 1)  # Uses the second spectrum's x-values for T (e.g. 450<T<900)
+
+        chi_v = cf.getSusceptibility(T, Hdir=[1, 1, 1])
+        chi_v_powder = cf.getSusceptibility(T, Hdir='powder')
+        chi_v_cgs = cf.getSusceptibility(T, Hdir=[1, 1, 0], Unit='SI')
+        chi_v_bohr = cf.getSusceptibility(T, Unit='bohr')
+        print(type([chi_v, chi_v_powder, chi_v_cgs, chi_v_bohr]))
+        moment_t = cf.getMagneticMoment(Temperature=T, Hdir=[1, 1, 1], Hmag=0.1) # Calcs M(T) with at 0.1T field||[111]
+        H = np.linspace(0, 30, 121)
+        moment_h = cf.getMagneticMoment(Hmag=H, Hdir='powder', Temperature=10)   # Calcs M(H) at 10K for powder sample
+        moment_SI = cf.getMagneticMoment(H, [1, 1, 1], Unit='SI')         # M(H) in Am^2/mol at 1K for H||[111]
+        moment_cgs = cf.getMagneticMoment(100, Temperature=T, Unit='cgs') # M(T) in emu/mol in a field of 100G || [001]
+        print(type([moment_t, moment_h, moment_SI, moment_cgs]))
+
+        # --------------------------
+
+        from CrystalField import CrystalField, CrystalFieldFit, PhysicalProperties
+        # Fits a heat capacity dataset - you must have subtracted the phonon contribution by some method already
+        # and the data must be in J/mol/K.
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544,
+                          PhysicalProperty=PhysicalProperties('Cv'))
+        fitcv = CrystalFieldFit(Model=cf, InputWorkspace=ws)
+        fitcv.fit()
+
+        params = {'B20':0.37737, 'B22':3.9770, 'B40':-0.031787, 'B42':-0.11611, 'B44':-0.12544}
+        cf = CrystalField('Ce', 'C2v', **params)
+        cf.PhysicalProperty = PhysicalProperties('Cv')
+        fitcv = CrystalFieldFit(Model=cf, InputWorkspace=ws)
+        fitcv.fit()
+
+        # Fits a susceptibility dataset. Data is the volume susceptibility in SI units
+        cf = CrystalField('Ce', 'C2v', **params)
+        cf.PhysicalProperty = PhysicalProperties('susc', Hdir='powder', Unit='SI')
+        fit_chi = CrystalFieldFit(Model=cf, InputWorkspace=ws)
+        fit_chi.fit()
+
+        # Fits a magnetisation dataset. Data is in emu/mol, and was measured at 5K with the field || [111].
+        cf = CrystalField('Ce', 'C2v', **params)
+        cf.PhysicalProperty = PhysicalProperties('M(H)', Temperature=5, Hdir=[1, 1, 1], Unit='cgs')
+        fit_mag = CrystalFieldFit(Model=cf, InputWorkspace=ws)
+        fit_mag.fit()
+
+        # Fits a magnetisation vs temperature dataset. Data is in Am^2/mol, measured with a 0.1T field || [110]
+        cf = CrystalField('Ce', 'C2v', **params)
+        cf.PhysicalProperty = PhysicalProperties('M(T)', Hmag=0.1, Hdir=[1, 1, 0], Unit='SI')
+        fit_moment = CrystalFieldFit(Model=cf, InputWorkspace=ws)
+        fit_moment.fit()
+
+        # --------------------------
+
+        # Pregenerate the required workspaces
+        for tt in [10, 44, 50]:
+            cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544, Temperature=tt, FWHM=0.5)
+            x, y = cf.getSpectrum()
+            self.my_create_ws('ws_ins_'+str(tt)+'K', x, y)
+        ws_ins_10K = mtd['ws_ins_10K']
+        ws_ins_44K = mtd['ws_ins_44K']
+        ws_ins_50K = mtd['ws_ins_50K']
+        ws_cp = self.my_create_ws('ws_cp', *cf.getHeatCapacity())
+        ws_chi = self.my_create_ws('ws_chi', *cf.getSusceptibility(np.linspace(1,300,100), Hdir='powder', Unit='cgs'))
+        ws_mag = self.my_create_ws('ws_mag', *cf.getMagneticMoment(Hmag=np.linspace(0, 30, 100), Hdir=[1,1,1], Unit='bohr', Temperature=5))
+
+        # --------------------------
+
+        # Fits an INS spectrum (at 10K) and the heat capacity simultaneously
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544)
+        cf.Temperature = 10
+        cf.FWHM = 1.5
+        cf.PhysicalProperty = PhysicalProperties('Cv')
+        fit = CrystalFieldFit(Model=cf, InputWorkspace=[ws_ins_10K, ws_cp])
+        fit.fit()
+
+        # Fits two INS spectra (at 44K and 50K) and the heat capacity, susceptibility and magnetisation simultaneously.
+        PPCv = PhysicalProperties('Cv')
+        PPchi = PhysicalProperties('susc', 'powder', Unit='cgs')
+        PPMag = PhysicalProperties('M(H)', [1, 1, 1], 5, 'bohr')
+        cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544,
+                          Temperature=[44.0, 50], FWHM=[1.1, 0.9], PhysicalProperty=[PPCv, PPchi, PPMag] )
+        fit = CrystalFieldFit(Model=cf, InputWorkspace=[ws_ins_44K, ws_ins_50K, ws_cp, ws_chi, ws_mag])
+        fit.fit()
diff --git a/Testing/SystemTests/tests/analysis/GSASIIRefineFitPeaksTest.py b/Testing/SystemTests/tests/analysis/GSASIIRefineFitPeaksTest.py
index b4f595b555582cd40613e6fea49cfaab384c23b0..da38a43afe3f549764a6da65b7dc799f2218f41b 100644
--- a/Testing/SystemTests/tests/analysis/GSASIIRefineFitPeaksTest.py
+++ b/Testing/SystemTests/tests/analysis/GSASIIRefineFitPeaksTest.py
@@ -14,32 +14,34 @@ class _AbstractGSASIIRefineFitPeaksTest(stresstesting.MantidStressTest):
     __metaclass__ = ABCMeta
 
     fitted_peaks_ws = None
+    gamma = None
     input_ws = None
-    residuals_table = None
+    rwp = None
+    sigma = None
     lattice_params_table = None
 
     _LATTICE_PARAM_TBL_NAME = "LatticeParameters"
-    _RESIDUALS_TBL_NAME = "Residuals"
     _INPUT_WORKSPACE_FILENAME = "focused_bank1_ENGINX00256663.nxs"
-    _PHASE_FILENAME = "FE_ALPHA.cif"
+    _PHASE_FILENAME_1 = "Fe-gamma.cif"
+    _PHASE_FILENAME_2 = "Fe-alpha.cif"
     _INST_PARAM_FILENAME = "template_ENGINX_241391_236516_North_bank.prm"
     _TEMP_DIR = tempfile.gettempdir()
     _path_to_gsas = None
 
     @abstractmethod
-    def _get_fit_params_reference_filename(self):
+    def _get_expected_rwp(self):
         pass
 
     @abstractmethod
-    def _get_gsas_proj_filename(self):
+    def _get_fit_params_reference_filename(self):
         pass
 
     @abstractmethod
-    def _get_refinement_method(self):
+    def _get_gsas_proj_filename(self):
         pass
 
     @abstractmethod
-    def _get_residuals_reference_filename(self):
+    def _get_refinement_method(self):
         pass
 
     def cleanup(self):
@@ -65,8 +67,9 @@ class _AbstractGSASIIRefineFitPeaksTest(stresstesting.MantidStressTest):
 
         return self._path_to_gsas
 
-    def phase_file_path(self):
-        return mantid.FileFinder.getFullPath(self._PHASE_FILENAME)
+    def phase_file_paths(self):
+        return mantid.FileFinder.getFullPath(self._PHASE_FILENAME_1) + "," + \
+               mantid.FileFinder.getFullPath(self._PHASE_FILENAME_2)
 
     def remove_all_gsas_files(self, gsas_filename_without_extension):
         for filename in os.listdir(self._TEMP_DIR):
@@ -80,17 +83,17 @@ class _AbstractGSASIIRefineFitPeaksTest(stresstesting.MantidStressTest):
         if not gsas_path:
             self.fail("Could not find GSAS-II installation")
 
-        self.fitted_peaks_ws, self.residuals_table, self.lattice_params_table = \
+        self.fitted_peaks_ws, self.lattice_params_table, self.rwp, self.sigma, self.gamma = \
             GSASIIRefineFitPeaks(RefinementMethod=self._get_refinement_method(),
                                  InputWorkspace=self.input_ws,
-                                 PhaseInfoFile=self.phase_file_path(),
+                                 PhaseInfoFiles=self.phase_file_paths(),
                                  InstrumentFile=self.inst_param_file_path(),
                                  PathToGSASII=gsas_path,
                                  SaveGSASIIProjectFile=self._get_gsas_proj_filename(),
                                  MuteGSASII=True,
+                                 XMin=10000, XMax=40000,
                                  LatticeParameters=self._LATTICE_PARAM_TBL_NAME,
-                                 ResidualsTable=self._RESIDUALS_TBL_NAME,
-                                 XMin=10000, XMax=40000)
+                                 RefineSigma=True, RefineGamma=True)
 
     def skipTests(self):
         # Skip this test, as it's just a wrapper for the Rietveld and Pawley tests
@@ -99,8 +102,8 @@ class _AbstractGSASIIRefineFitPeaksTest(stresstesting.MantidStressTest):
     def validate(self):
         # TODO: Check fitted_peaks_ws has correct values once we have some data we're sure of
         self.assertEqual(self.input_ws.getNumberBins(), self.fitted_peaks_ws.getNumberBins())
-        return (self._LATTICE_PARAM_TBL_NAME, mantid.FileFinder.getFullPath(self._get_fit_params_reference_filename()),
-                self._RESIDUALS_TBL_NAME, mantid.FileFinder.getFullPath(self._get_residuals_reference_filename()))
+        self.assertAlmostEqual(self.rwp, self._get_expected_rwp(), delta=1e-6)
+        return self._LATTICE_PARAM_TBL_NAME, mantid.FileFinder.getFullPath(self._get_fit_params_reference_filename())
 
 
 class GSASIIRefineFitPeaksRietveldTest(_AbstractGSASIIRefineFitPeaksTest):
@@ -108,6 +111,9 @@ class GSASIIRefineFitPeaksRietveldTest(_AbstractGSASIIRefineFitPeaksTest):
     def skipTests(self):
         return not self.path_to_gsas()
 
+    def _get_expected_rwp(self):
+        return 28.441745
+
     def _get_fit_params_reference_filename(self):
         return "GSASIIRefineFitPeaksRietveldFitParams.nxs"
 
@@ -117,15 +123,15 @@ class GSASIIRefineFitPeaksRietveldTest(_AbstractGSASIIRefineFitPeaksTest):
     def _get_refinement_method(self):
         return "Rietveld refinement"
 
-    def _get_residuals_reference_filename(self):
-        return "GSASIIRefineFitPeaksRietveldResiduals.nxs"
-
 
 class GSASIIRefineFitPeaksPawleyTest(_AbstractGSASIIRefineFitPeaksTest):
 
     def skipTests(self):
         return not self.path_to_gsas()
 
+    def _get_expected_rwp(self):
+        return 74.025863
+
     def _get_fit_params_reference_filename(self):
         return "GSASIIRefineFitPeaksPawleyFitParams.nxs"
 
@@ -134,6 +140,3 @@ class GSASIIRefineFitPeaksPawleyTest(_AbstractGSASIIRefineFitPeaksTest):
 
     def _get_refinement_method(self):
         return "Pawley refinement"
-
-    def _get_residuals_reference_filename(self):
-        return "GSASIIRefineFitPeaksPawleyResiduals.nxs"
diff --git a/Testing/SystemTests/tests/analysis/ISIS_PowderHRPDTest.py b/Testing/SystemTests/tests/analysis/ISIS_PowderHRPDTest.py
index 8b17f03c4e8f7ddfb53f2582914c44340c88266d..0a7caa39cb73e9afd5042b6b3d126982377d7a88 100644
--- a/Testing/SystemTests/tests/analysis/ISIS_PowderHRPDTest.py
+++ b/Testing/SystemTests/tests/analysis/ISIS_PowderHRPDTest.py
@@ -1,6 +1,7 @@
 from __future__ import (absolute_import, division, print_function)
 
 import os
+import platform
 import shutil
 import stresstesting
 
@@ -79,7 +80,10 @@ class FocusTest(stresstesting.MantidStressTest):
         self.focus_results = run_focus()
 
     def validate(self):
-        self.tolerance=0.25  # Required for difference in spline data between operating systems
+        if platform.system() == "Darwin":  # OSX requires higher tolerance for splines
+            self.tolerance = 0.4
+        else:
+            self.tolerance = 0.05
         return self.focus_results.getName(), "HRPD66063_focused.nxs"
 
     def cleanup(self):
diff --git a/Testing/SystemTests/tests/analysis/LOQCentreNoGrav.py b/Testing/SystemTests/tests/analysis/LOQCentreNoGrav.py
index 11a0b80933be4941909c30e2b092f099160cd14c..47e76d98856eab88b61946ec9296ec317ca0fae3 100644
--- a/Testing/SystemTests/tests/analysis/LOQCentreNoGrav.py
+++ b/Testing/SystemTests/tests/analysis/LOQCentreNoGrav.py
@@ -13,7 +13,6 @@ class LOQCentreNoGrav(stresstesting.MantidStressTest):
     def runTest(self):
 
         LOQ()
-
         Set1D()
         Detector("rear-detector")
         MaskFile('MASK.094AA')
diff --git a/Testing/SystemTests/tests/analysis/LOQCentreNoGrav_V2.py b/Testing/SystemTests/tests/analysis/LOQCentreNoGrav_V2.py
index 6ed3abf56719f918909ecee4474e1baa4e5e4ce4..2065350d919869138bdb6c179279d3d09e3c219c 100644
--- a/Testing/SystemTests/tests/analysis/LOQCentreNoGrav_V2.py
+++ b/Testing/SystemTests/tests/analysis/LOQCentreNoGrav_V2.py
@@ -5,36 +5,35 @@ import mantid  # noqa
 from sans.command_interface.ISISCommandInterface import (LOQ, Set1D, Detector, MaskFile, Gravity, AssignSample,
                                                          TransmissionSample, AssignCan, TransmissionCan,
                                                          WavRangeReduction, DefaultTrans, SetCentre,
-                                                         UseCompatibilityMode)
+                                                         UseCompatibilityMode, FindBeamCentre)
 
-# The Find beam centre command has not been implemented yet in SANS2 hence we cannot run this test correctly
-#
-# class LOQCentreNoGrav(stresstesting.MantidStressTest):
-#     def __init__(self):
-#         stresstesting.MantidStressTest.__init__(self)
-#         self.tolerance = 1e-6
-#
-#     def runTest(self):
-#         UseCompatibilityMode()
-#         LOQ()
-#
-#         Set1D()
-#         Detector("rear-detector")
-#         MaskFile('MASK.094AA')
-#         Gravity(False)
-#
-#         AssignSample('54431.raw')
-#         TransmissionSample('54435.raw', '54433.raw')
-#         AssignCan('54432.raw')
-#         TransmissionCan('54434.raw', '54433.raw')
-#
-#         FindBeamCentre(60, 200, 9)
-#
-#         WavRangeReduction(3, 9, DefaultTrans)
-#
-#     def validate(self):
-#         self.disableChecking.append('Instrument')
-#         return '54431main_1D_3.0_9.0','LOQCentreNoGravSearchCentreFixed.nxs'
+
+class LOQCentreNoGrav(stresstesting.MantidStressTest):
+    def __init__(self):
+        stresstesting.MantidStressTest.__init__(self)
+        self.tolerance = 1e-6
+
+    def runTest(self):
+        UseCompatibilityMode()
+        LOQ()
+
+        Set1D()
+        Detector("rear-detector")
+        MaskFile('MASK.094AA')
+        Gravity(False)
+
+        AssignSample('54431.raw')
+        TransmissionSample('54435.raw', '54433.raw')
+        AssignCan('54432.raw')
+        TransmissionCan('54434.raw', '54433.raw')
+
+        FindBeamCentre(60, 200, 9)
+
+        WavRangeReduction(3, 9, DefaultTrans)
+
+    def validate(self):
+        self.disableChecking.append('Instrument')
+        return '54431main_1D_3.0_9.0','LOQCentreNoGravSearchCentreFixed.nxs'
 
 
 class LOQCentreNoGravDefineCentreTest_V2(stresstesting.MantidStressTest):
diff --git a/Testing/SystemTests/tests/analysis/LoadLotsOfFiles.py b/Testing/SystemTests/tests/analysis/LoadLotsOfFiles.py
index 4d5dd7afdcd546bedf85bf1c357b4200e03ce145..dc3152ff747b9558b89cc06fa3a07a38c7d1f55e 100644
--- a/Testing/SystemTests/tests/analysis/LoadLotsOfFiles.py
+++ b/Testing/SystemTests/tests/analysis/LoadLotsOfFiles.py
@@ -110,6 +110,8 @@ BANNED_FILES = ['80_tubes_Top_and_Bottom_April_2015.xml',
                 'SingleCrystalDiffuseReduction_UB.mat',
                 'Na2SiF6_DMOL3.outmol',
                 'FE_ALPHA.cif',
+                'Fe-gamma.cif',
+                'Fe-alpha.cif'
                 'template_ENGINX_241391_236516_North_bank.prm'
                 ]
 
diff --git a/Testing/SystemTests/tests/analysis/MuonMaxEntTest.py b/Testing/SystemTests/tests/analysis/MuonMaxEntTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..4a22089967a122991e5a820f15829f23cc39b1e9
--- /dev/null
+++ b/Testing/SystemTests/tests/analysis/MuonMaxEntTest.py
@@ -0,0 +1,51 @@
+# pylint: disable=no-init,attribute-defined-outside-init
+import stresstesting
+from math import pi
+from mantid.simpleapi import *
+
+
+class MuonMaxEntTest(stresstesting.MantidStressTest):
+
+    '''Tests the MaxEnt algorithm on a MUSR workspace'''
+
+    def fixPhasesTest(self, phases0):
+        MuonMaxEnt(
+            InputWorkspace='MUSR00022725',
+            InputPhaseTable="tab",
+            Npts='16384',
+            FixPhases=True,
+            OutputWorkspace='tmp',
+            OutputPhaseTable='tmpPhase')
+        tmp = AnalysisDataService.retrieve("tmpPhase")
+        phases = tmp.column(2)
+        self.assertListEqual(phases0, phases)
+
+    def runTest(self):
+        Load(Filename='MUSR00022725.nxs', OutputWorkspace='MUSR00022725')
+        tab = CreateEmptyTableWorkspace()
+        tab.addColumn('int', 'Spectrum number')
+        tab.addColumn('double', 'Asymmetry')
+        tab.addColumn('double', 'Phase')
+        for i in range(0, 32):
+            phi = 2. * pi * i / 32.
+            tab.addRow([i + 1, 0.2, phi])
+        for i in range(0, 32):
+            phi = 2. * pi * i / 32.
+            tab.addRow([i + 33, 0.2, phi])
+
+        # first do with fixed phases
+        MuonMaxent(
+            InputWorkspace='MUSR00022725', InputPhaseTable="tab", Npts='16384', FixPhases=True,
+            OutputWorkspace='freq0', OutputPhaseTable='PhasesOut0', ReconstructedSpectra="time0")
+        # then do with fitting phases
+        MuonMaxent(
+            InputWorkspace='MUSR00022725', InputPhaseTable="tab", Npts='16384', FixPhases=False,
+            OutputWorkspace='freq', OutputPhaseTable='PhasesOut', ReconstructedSpectra="time")
+
+        GroupWorkspaces(
+            InputWorkspaces='freq0,phasesOut0,time0,freq,phasesOut,time',
+            OutputWorkspace='MuonMaxEntResults')
+
+    def validate(self):
+        self.tolerance = 5E-2
+        return ('MuonMaxEntResults', 'MuonMaxEntMUSR00022725.nxs')
diff --git a/Testing/SystemTests/tests/analysis/reference/ARCSsystemtest.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/ARCSsystemtest.nxs.md5
index 56a2302a638a4bcb53ddbde484bd2e70609706f4..7961cfedfb8153d1084b4fbdb5e772545f800aa3 100644
--- a/Testing/SystemTests/tests/analysis/reference/ARCSsystemtest.nxs.md5
+++ b/Testing/SystemTests/tests/analysis/reference/ARCSsystemtest.nxs.md5
@@ -1 +1 @@
-98c9c3beb2a0d04c064875f9f171cb54
\ No newline at end of file
+5cf3341a7a9518ddba0eeb7127231061
diff --git a/Testing/SystemTests/tests/analysis/reference/CNCSReduction_TIBasEvents.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/CNCSReduction_TIBasEvents.nxs.md5
index 3bd9997c84a86e12a1abeb8d65261d4482156633..06006cdcd29c26456ddf8c48ff4ce5d4e32e1b24 100644
--- a/Testing/SystemTests/tests/analysis/reference/CNCSReduction_TIBasEvents.nxs.md5
+++ b/Testing/SystemTests/tests/analysis/reference/CNCSReduction_TIBasEvents.nxs.md5
@@ -1 +1 @@
-38425b5a8f456db00527660cdc35ce21
\ No newline at end of file
+481e3ba997736c56e46d13077fa4117c
diff --git a/Testing/SystemTests/tests/analysis/reference/GSASIIRefineFitPeaksPawleyFitParams.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/GSASIIRefineFitPeaksPawleyFitParams.nxs.md5
index 473d25d8b9ce758c779899c83c60cc32d971e6d9..481e763db9085eb306ba75eb90a3dbfe8b72a961 100644
--- a/Testing/SystemTests/tests/analysis/reference/GSASIIRefineFitPeaksPawleyFitParams.nxs.md5
+++ b/Testing/SystemTests/tests/analysis/reference/GSASIIRefineFitPeaksPawleyFitParams.nxs.md5
@@ -1 +1 @@
-5198bacf0b2451a9b33561803c14b3aa
\ No newline at end of file
+1f689b8f4c095fb8fa10145399117407
\ No newline at end of file
diff --git a/Testing/SystemTests/tests/analysis/reference/GSASIIRefineFitPeaksRietveldFitParams.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/GSASIIRefineFitPeaksRietveldFitParams.nxs.md5
index 80f547fc7023d0280354c637e7688c270ebf804d..9bdd6c49aa834dc16cb086a1e50f449d8ba35e5e 100644
--- a/Testing/SystemTests/tests/analysis/reference/GSASIIRefineFitPeaksRietveldFitParams.nxs.md5
+++ b/Testing/SystemTests/tests/analysis/reference/GSASIIRefineFitPeaksRietveldFitParams.nxs.md5
@@ -1 +1 @@
-c32e06028474df95d42d1b5882999911
\ No newline at end of file
+aff1eb803b8070978dd2ea1df56aae53
\ No newline at end of file
diff --git a/Testing/SystemTests/tests/analysis/reference/MuonMaxEntMUSR00022725.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/MuonMaxEntMUSR00022725.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..6e229314db39ca1c5d3e607d924208553d5621ff
--- /dev/null
+++ b/Testing/SystemTests/tests/analysis/reference/MuonMaxEntMUSR00022725.nxs.md5
@@ -0,0 +1 @@
+aa7e91f0f64bf1197bedf9b22d559ca9
diff --git a/buildconfig/CMake/LinuxPackageScripts.cmake b/buildconfig/CMake/LinuxPackageScripts.cmake
index 22b0aa2ba03b415ec4eecc544b6f460f1d0f078b..55686ffbec105b569e18c3698870a4fb6a79e7e9 100644
--- a/buildconfig/CMake/LinuxPackageScripts.cmake
+++ b/buildconfig/CMake/LinuxPackageScripts.cmake
@@ -23,12 +23,6 @@ if ( CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT )
   set ( CMAKE_INSTALL_PREFIX /opt/mantid${CPACK_PACKAGE_SUFFIX} CACHE PATH "Install path" FORCE )
 endif()
 
-# We are only generating Qt4 packages at the moment
-set ( CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/${LIB_DIR};${CMAKE_INSTALL_PREFIX}/${PLUGINS_DIR};${CMAKE_INSTALL_PREFIX}/${PLUGINS_DIR}/qt4; )
-if ( MAKE_VATES )
-  list ( APPEND CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/${LIB_DIR}/paraview-${ParaView_VERSION_MAJOR}.${ParaView_VERSION_MINOR} )
-endif ()
-
 # Tell rpm that this package does not own /opt /usr/share/{applications,pixmaps}
 # Required for Fedora >= 18 and RHEL >= 7
 set ( CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION /opt /usr/share/applications /usr/share/pixmaps )
@@ -145,9 +139,8 @@ if ( TCMALLOC_FOUND )
 endif ()
 
 # Local dev version
-set ( EXTRA_LDPATH "${ParaView_DIR}/lib" )
 if ( MAKE_VATES )
-  set ( PARAVIEW_PYTHON_PATHS ":${EXTRA_LDPATH}:${EXTRA_LDPATH}/site-packages:${EXTRA_LDPATH}/site-packages/vtk" )
+  set ( PARAVIEW_PYTHON_PATHS ":${ParaView_DIR}/lib:${ParaView_DIR}/lib/site-packages" )
 else ()
   set ( PARAVIEW_PYTHON_PATHS "" )
 endif ()
@@ -172,9 +165,10 @@ execute_process ( COMMAND "chmod" "+x" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/AddPyt
                   OUTPUT_QUIET ERROR_QUIET )
 
 # Package version
-set ( EXTRA_LDPATH "\${INSTALLDIR}/../lib/paraview-${ParaView_VERSION_MAJOR}.${ParaView_VERSION_MINOR}:\${INSTALLDIR}/../plugins/qtplugins/mantid" )
+set ( EXTRA_LDPATH "\${INSTALLDIR}/../lib/paraview-${ParaView_VERSION_MAJOR}.${ParaView_VERSION_MINOR}" )
 if ( MAKE_VATES )
-  set ( PARAVIEW_PYTHON_PATHS ":${EXTRA_LDPATH}:${EXTRA_LDPATH}/site-packages:${EXTRA_LDPATH}/site-packages/vtk" )
+  set ( PV_PYTHON_PATH "\${INSTALLDIR}/../lib/paraview-${ParaView_VERSION_MAJOR}.${ParaView_VERSION_MINOR}" )
+  set ( PARAVIEW_PYTHON_PATHS ":${PV_PYTHON_PATH}:${PV_PYTHON_PATH}/site-packages:${PV_PYTHON_PATH}/site-packages/vtk" )
 else ()
   set ( PARAVIEW_PYTHON_PATHS "" )
 endif ()
diff --git a/buildconfig/CMake/MSVCSetup.cmake b/buildconfig/CMake/MSVCSetup.cmake
index e43004a65adeb0c698afe821b82562fbcf71e45c..52a41570b53bc32d421b2d0f74892ff40cd241fa 100644
--- a/buildconfig/CMake/MSVCSetup.cmake
+++ b/buildconfig/CMake/MSVCSetup.cmake
@@ -87,6 +87,17 @@ set ( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin )
 ###########################################################################
 set ( WINDOWS_BUILDCONFIG ${PROJECT_SOURCE_DIR}/buildconfig/windows )
 configure_file ( ${WINDOWS_BUILDCONFIG}/buildenv.bat.in ${PROJECT_BINARY_DIR}/buildenv.bat @ONLY )
+
+if ( MSVC_VERSION LESS 1911 )
+    get_filename_component ( MSVC_VAR_LOCATION "$ENV{VS140COMNTOOLS}/../../VC/" ABSOLUTE)
+    get_filename_component ( MSVC_IDE_LOCATION "$ENV{VS140COMNTOOLS}/../IDE" ABSOLUTE)
+else ()
+    get_filename_component ( MSVC_IDE_LOCATION "${CMAKE_CXX_COMPILER}" DIRECTORY )
+    get_filename_component ( MSVC_IDE_LOCATION "${MSVC_IDE_LOCATION}/../../../../../../.." ABSOLUTE )
+    set ( MSVC_VAR_LOCATION "${MSVC_IDE_LOCATION}/VC/Auxiliary/Build")
+    set ( MSVC_IDE_LOCATION "${MSVC_IDE_LOCATION}/Common7/IDE")
+endif()
+
 configure_file ( ${WINDOWS_BUILDCONFIG}/command-prompt.bat ${PROJECT_BINARY_DIR}/command-prompt.bat @ONLY )
 configure_file ( ${WINDOWS_BUILDCONFIG}/visual-studio.bat ${PROJECT_BINARY_DIR}/visual-studio.bat @ONLY )
 
diff --git a/buildconfig/CMake/Packaging/launch_mantidplot.sh.in b/buildconfig/CMake/Packaging/launch_mantidplot.sh.in
index bb5ae47d89e232de0154aa95935bde5872236b0d..cdaa81f081f364a37c23a98091c6396e3f8ff8e5 100644
--- a/buildconfig/CMake/Packaging/launch_mantidplot.sh.in
+++ b/buildconfig/CMake/Packaging/launch_mantidplot.sh.in
@@ -13,7 +13,6 @@ LOCAL_PRELOAD=@TCMALLOC_RUNTIME_LIB@
 if [ -n "${LD_PRELOAD}" ]; then
     LOCAL_PRELOAD=${LOCAL_PRELOAD}:${LD_PRELOAD}
 fi
-LOCAL_LDPATH=@EXTRA_LDPATH@:${LD_LIBRARY_PATH}
 if [ -z "${TCMALLOC_RELEASE_RATE}" ]; then
     TCM_RELEASE=10000
 else
@@ -51,6 +50,5 @@ fi
 
 # Launch
 LD_PRELOAD=${LOCAL_PRELOAD} TCMALLOC_RELEASE_RATE=${TCM_RELEASE} \
-    TCMALLOC_LARGE_ALLOC_REPORT_THRESHOLD=${TCM_REPORT} \
-    LD_LIBRARY_PATH=${LOCAL_LDPATH} QT_API=pyqt \
+    TCMALLOC_LARGE_ALLOC_REPORT_THRESHOLD=${TCM_REPORT} QT_API=pyqt \
     @WRAPPER_PREFIX@$VGLRUN $GDB $INSTALLDIR/@MANTIDPLOT_EXEC@ $*@WRAPPER_POSTFIX@
diff --git a/buildconfig/CMake/Packaging/mantidpython.in b/buildconfig/CMake/Packaging/mantidpython.in
index 2eb2ba7c728cbc47f1a26996abcb7896c92b3b04..a833c2f1a392148626c7c7e9c9dc91497b2184f0 100755
--- a/buildconfig/CMake/Packaging/mantidpython.in
+++ b/buildconfig/CMake/Packaging/mantidpython.in
@@ -13,12 +13,12 @@ LOCAL_PRELOAD=@TCMALLOC_RUNTIME_LIB@
 if [ -n "${LD_PRELOAD}" ]; then
     LOCAL_PRELOAD=${LOCAL_PRELOAD}:${LD_PRELOAD}
 fi
-LOCAL_LDPATH=@EXTRA_LDPATH@:${LD_LIBRARY_PATH}
 if [ -z "${TCMALLOC_RELEASE_RATE}" ]; then
     TCM_RELEASE=10000
 else
     TCM_RELEASE=${TCMALLOC_RELEASE_RATE}
 fi
+LOCAL_LDPATH=@EXTRA_LDPATH@:${LD_LIBRARY_PATH}
 
 # Define when to report large memory allocation
 if [ -z "${TCMALLOC_LARGE_ALLOC_REPORT_THRESHOLD}" ]; then
@@ -38,7 +38,7 @@ fi
 PV_PLUGIN_PATH="${INSTALLDIR}/pvplugins/pvplugins"
 
 # Define extra libraries for python
-LOCAL_PYTHONPATH=$INSTALLDIR@PARAVIEW_PYTHON_PATHS@
+LOCAL_PYTHONPATH=${INSTALLDIR}@PARAVIEW_PYTHON_PATHS@
 if [ -n "${PYTHONPATH}" ]; then
     LOCAL_PYTHONPATH=${LOCAL_PYTHONPATH}:${PYTHONPATH}
 fi
diff --git a/buildconfig/CMake/QtTargetFunctions.cmake b/buildconfig/CMake/QtTargetFunctions.cmake
index 9dbb8ff160caa03b2d5b6b3e02548ce18d125914..f94b71258d21aed553c2fbe10068a8e4ddca21bd 100644
--- a/buildconfig/CMake/QtTargetFunctions.cmake
+++ b/buildconfig/CMake/QtTargetFunctions.cmake
@@ -88,6 +88,7 @@ endfunction()
 # keyword: INSTALL_DIR A destination directory for the install command.
 # keyword: INSTALL_DIR_BASE Base directory the build output. The final product goes into a subdirectory based on the Qt version.
 # keyword: OSX_INSTALL_RPATH Install path for osx version > 10.8
+# keyword: LINUX_INSTALL_RPATH Install path for CMAKE_SYSTEM_NAME == Linux
 function (mtd_add_qt_target)
   set (options LIBRARY EXECUTABLE NO_SUFFIX EXCLUDE_FROM_ALL)
   set (oneValueArgs
@@ -95,7 +96,7 @@ function (mtd_add_qt_target)
     INSTALL_DIR INSTALL_DIR_BASE PRECOMPILED)
   set (multiValueArgs SRC UI MOC
     NOMOC RES DEFS QT4_DEFS QT5_DEFS INCLUDE_DIRS SYSTEM_INCLUDE_DIRS LINK_LIBS
-    QT4_LINK_LIBS QT5_LINK_LIBS MTD_QT_LINK_LIBS OSX_INSTALL_RPATH)
+    QT4_LINK_LIBS QT5_LINK_LIBS MTD_QT_LINK_LIBS OSX_INSTALL_RPATH LINUX_INSTALL_RPATH)
   cmake_parse_arguments (PARSED "${options}" "${oneValueArgs}"
                          "${multiValueArgs}" ${ARGN})
   if (PARSED_UNPARSED_ARGUMENTS)
@@ -194,6 +195,10 @@ function (mtd_add_qt_target)
     if (PARSED_OSX_INSTALL_RPATH)
       set_target_properties ( ${_target} PROPERTIES INSTALL_RPATH  "${PARSED_OSX_INSTALL_RPATH}" )
     endif()
+  elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+    if (PARSED_LINUX_INSTALL_RPATH)
+      set_target_properties ( ${_target} PROPERTIES INSTALL_RPATH  "${PARSED_LINUX_INSTALL_RPATH}" )
+    endif ()
   endif ()
 
   if ( PARSED_EXCLUDE_FROM_ALL )
@@ -223,7 +228,7 @@ function (mtd_add_qt_target)
   else ()
     add_custom_target ( ${_alltarget} DEPENDS ${_target} )
   endif()
-  
+
 endfunction()
 
 function (mtd_add_qt_tests)
@@ -405,7 +410,7 @@ function (_disable_suggest_override _qt_version _target)
   endif()
 endfunction ()
 
-# Homebrew on macOS symlinks Qt4 into /usr/local so that the 
+# Homebrew on macOS symlinks Qt4 into /usr/local so that the
 # include directories are on the standard paths. When we add
 # /usr/local/include using target_include_directories clang sees
 # the duplicate but removes them from the end and keeps the paths
@@ -427,5 +432,5 @@ function ( prune_usr_local_include )
         set_property ( TARGET ${_it} PROPERTY INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_tmp}" )
       endif ()
     endif ()
-  endforeach () 
+  endforeach ()
 endfunction ()
diff --git a/buildconfig/Jenkins/buildscript b/buildconfig/Jenkins/buildscript
index 55233124c66de5fb1293edc0aa94af0593c18acb..b58e9792fa91c7e29546a44d4a10e99a8d6a8aa6 100755
--- a/buildconfig/Jenkins/buildscript
+++ b/buildconfig/Jenkins/buildscript
@@ -253,7 +253,7 @@ if [[ ${JOB_NAME} == *coverity_build_and_submit* ]]; then
        --form file=@mantid.tgz --form version=$GIT_COMMIT \
        https://scan.coverity.com/builds?project=mantidproject%2Fmantid)
   status=$(echo ${status} | sed -e 's/^ *//' -e 's/ *$//')
-  if [[ -z $status ]]; then
+  if [[ -z $status ]] || [[ ${status} == "Build successfully submitted." ]]; then
     exit 0
   else
     echo "$status"
diff --git a/buildconfig/Jenkins/jenkins-slave.sh b/buildconfig/Jenkins/jenkins-slave.sh
index c116e59307b431fefdeafa551ea157c9ec0b99f4..8d94719c1b255951dbd4bd7599dcbd17bbe0f612 100755
--- a/buildconfig/Jenkins/jenkins-slave.sh
+++ b/buildconfig/Jenkins/jenkins-slave.sh
@@ -30,7 +30,7 @@ SECRET=${2}
 # URL of jnlp file for slave
 SLAVE_AGENT_URL="${JENKINS_URL}/computer/${NODE_NAME}/slave-agent.jnlp"
 # name of the slave jar - full path is determined later
-JAR_FILE=slave.jar
+JAR_FILE=agent.jar
 # Some versions of cron don't set the USER environment variable
 # required by vnc
 [ -z "$USER" ] && export USER=$(whoami)
@@ -75,10 +75,10 @@ else
   if [ ! -f ${JAR_FILE} ]; then
     echo "Downloading slave jar file to ${JAR_FILE}"
     if [ $(command -v curl) ]; then
-      echo "curl -o ${JAR_FILE} ${JENKINS_URL}/jnlpJars/slave.jar"
-      curl -o ${JAR_FILE} ${JENKINS_URL}/jnlpJars/slave.jar
+      echo "curl -o ${JAR_FILE} ${JENKINS_URL}/jnlpJars/${JAR_FILE}"
+      curl -o ${JAR_FILE} ${JENKINS_URL}/jnlpJars/${JAR_FILE}
     else
-      echo "Need curl to download ${JENKINS_URL}/jnlpJars/slave.jar"
+      echo "Need curl to download ${JENKINS_URL}/jnlpJars/${JAR_FILE}"
       exit -1
     fi
   fi
diff --git a/buildconfig/windows/command-prompt.bat b/buildconfig/windows/command-prompt.bat
index 8c35b59b96fb2222182f0f022629b08eb522af73..0711ebd4c16a7b2c1849b2140fcc2454f484739a 100755
--- a/buildconfig/windows/command-prompt.bat
+++ b/buildconfig/windows/command-prompt.bat
@@ -5,6 +5,6 @@
 
 :: Assume the buildenv.bat script exists and is in the same directory
 call %~dp0buildenv.bat
-
+set VCVARS=@MSVC_VAR_LOCATION@
 :: Start command line
-%COMSPEC% /k "%VS140COMNTOOLS%\..\..\VC\vcvarsall.bat" amd64
+%COMSPEC% /k ""%VCVARS%\vcvarsall.bat"" amd64
diff --git a/buildconfig/windows/visual-studio.bat b/buildconfig/windows/visual-studio.bat
index 048959e782b78bef7ce810873328d5014beebaf5..7d6cbae1ca0b073f80748c5ac35297d92116aca2 100755
--- a/buildconfig/windows/visual-studio.bat
+++ b/buildconfig/windows/visual-studio.bat
@@ -5,6 +5,6 @@
 
 :: Assume the buildenv.bat script exists and is in the same directory
 call %~dp0buildenv.bat
-
+set MSVC_IDE=@MSVC_IDE_LOCATION@
 :: Start IDE
-start "" "%VS140COMNTOOLS%\..\IDE\devenv.exe" Mantid.sln
+start "" "%MSVC_IDE%/devenv.exe" Mantid.sln
diff --git a/dev-docs/CMakeLists.txt b/dev-docs/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..38c441b03eccf42c5135415969b882da37b0558a
--- /dev/null
+++ b/dev-docs/CMakeLists.txt
@@ -0,0 +1,15 @@
+###############################################################################
+# Developer documentation
+###############################################################################
+# targets
+set ( BUILDER html )
+set ( OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/${BUILDER} )
+set ( DOCTREE_DIR ${CMAKE_CURRENT_BINARY_DIR}/doctree )
+
+add_custom_target ( dev-docs-${BUILDER}
+                   COMMAND python -m sphinx -b ${BUILDER} -d ${DOCTREE_DIR} ${CMAKE_CURRENT_LIST_DIR}/source ${OUT_DIR}
+                   COMMENT "Building html developer documentation" )
+# Group within VS and exclude from whole build
+set_target_properties ( dev-docs-html PROPERTIES FOLDER "Documentation"
+                       EXCLUDE_FROM_DEFAULT_BUILD 1
+                       EXCLUDE_FROM_ALL 1 )
diff --git a/docs/source/development/AlgorithmMPISupport.rst b/dev-docs/source/AlgorithmMPISupport.rst
similarity index 92%
rename from docs/source/development/AlgorithmMPISupport.rst
rename to dev-docs/source/AlgorithmMPISupport.rst
index d1537164bcb853f74dfadbaf0a6127028d2684b1..74d750cf64dea9777b7e147ad1aceec2809a8892 100644
--- a/docs/source/development/AlgorithmMPISupport.rst
+++ b/dev-docs/source/AlgorithmMPISupport.rst
@@ -45,7 +45,7 @@ Only a subset of all spectra is stored on the local MPI rank, so a detector may
 Storage Mode
 ------------
 
-.. figure:: ../images/MPI-storage-modes.png
+.. figure:: images/MPI-storage-modes.png
    :figwidth: 25%
    :align: right
 
@@ -73,45 +73,45 @@ Usage examples for the storage modes could include:
 Execution Mode
 --------------
 
-.. figure:: ../images/MPI-execution-mode-identical.png
+.. figure:: images/MPI-execution-mode-identical.png
    :figwidth: 25%
    :align: right
 
    ``ExecutionMode::Identical`` based on an input and output workspace with ``StorageMode::Cloned``. Example: ``ConvertUnits``, ``Rebin``, or many other algorithm that do not load or save data.
 
 
-.. figure:: ../images/MPI-execution-mode-distributed-load.png
+.. figure:: images/MPI-execution-mode-distributed-load.png
    :figwidth: 25%
    :align: right
 
    ``ExecutionMode::Distributed`` creating an output workspace with ``StorageMode::Distributed``. Example: ``LoadEventNexus``.
 
-.. figure:: ../images/MPI-execution-mode-distributed.png
+.. figure:: images/MPI-execution-mode-distributed.png
    :figwidth: 25%
    :align: right
 
    ``ExecutionMode::Distributed`` based on an input and output workspace with ``StorageMode::MasterOnly``. Example: ``ConvertUnits`` or ``Rebin``.
 
-.. figure:: ../images/MPI-execution-mode-distributed-gather.png
+.. figure:: images/MPI-execution-mode-distributed-gather.png
    :figwidth: 25%
    :align: right
 
    ``ExecutionMode::Distributed`` based on an input workspace with ``StorageMode::Distributed`` creating an output workspace with ``StorageMode::MasterOnly``. Example: ``DiffractionFocussing``.
 
 
-.. figure:: ../images/MPI-execution-mode-master-only-load.png
+.. figure:: images/MPI-execution-mode-master-only-load.png
    :figwidth: 25%
    :align: right
 
    ``ExecutionMode::MasterOnly`` creating an output workspace with ``StorageMode::Distributed``. Example: ``LoadEventNexus`` or other load algorithms.
 
-.. figure:: ../images/MPI-execution-mode-master-only.png
+.. figure:: images/MPI-execution-mode-master-only.png
    :figwidth: 25%
    :align: right
 
    ``ExecutionMode::MasterOnly`` based on an input and output workspace with ``StorageMode::MasterOnly``. Example: ``ConvertUnits``, ``Rebin``, or many other algorithm that do not load or save data.
 
-.. figure:: ../images/MPI-execution-mode-master-only-store.png
+.. figure:: images/MPI-execution-mode-master-only-store.png
    :figwidth: 25%
    :align: right
 
@@ -161,7 +161,7 @@ For example:
 .. code-block:: python
 
   from mantid.simpleapi import *
-  
+
   dataX = [1,2,3,4,2,3,4,5,3,4,5,6,4,5,6,7]
   dataY = [1,1,1,1,1,1,1,1,1,1,1,1]
   dataE = [1,1,1,1,1,1,1,1,1,1,1,1]
@@ -253,7 +253,6 @@ MPI support for an algorithm is implemented by means of a couple of virtual meth
   protected:
     virtual void execDistributed();
     virtual void execMasterOnly();
-    virtual void execNonMaster();
     virtual Parallel::ExecutionMode getParallelExecutionMode(
         const std::map<std::string, Parallel::StorageMode> &storageModes) const;
     // ...
@@ -339,23 +338,15 @@ Master-only execution
 ---------------------
 
 Master-only execution is handled by ``Algorithm::execMasterOnly()``.
-By default this simply calls ``Algorithm::exec()`` on rank 0 and ``Algorithm::execNonMaster()`` on all other ranks.
+By default this simply calls ``Algorithm::exec()`` on rank 0 and does nothing on all other ranks.
 
 To support running existing Python scripts without significant modification, and to be able to automatically determine execution modes based on input workspaces, workspaces with storage mode ``StorageMode::MasterOnly`` also exist on the non-master ranks.
-The default implementation of ``Algorithm::execNonMaster()`` creates an **uninitialized** (in the case of ``MatrixWorkspace``) workspace of the same type as the input workspace.
-If ``Algorithm::execNonMaster()`` is overridden, any workspaces that are created shall also be uninitialized and should have storage mode ``StorageMode::MasterOnly``.
-
-Given that the workspace on non-master ranks are not initialized, no methods of the workspace should be called, apart from ``Workspace::storageMode()``.
-Validators on the non-master ranks are thus also disabled.
-
-A typical implementation could look as follows:
 
-.. code-block:: c++
-
-  void MyAlg::execNonMaster() {
-    setProperty("OutputWorkspace", Kernel::make_unique<Workspace2D>(
-                                       Parallel::StorageMode::MasterOnly));
-  }
+Given that no algorithm execution happens on non-master ranks, workspaces with ``StorageMode::MasterOnly`` do not exist on non-master ranks.
+This implies:
+- No methods of such a workspace can be called, except when wrapped in an algorithm that has ``ExecutionMode::MasterOnly``.
+- Retrieving such a workspace from the AnalysisDataService is not possible.
+- Algorithms that transform a workspace into a workspace with ``StorageMode::MasterOnly`` such as ``DiffractionFocussing2`` should delete the input workspace when using in-place operation.
 
 
 Setting spectrum numbers
@@ -443,11 +434,11 @@ A typical example could look as follows:
     Workspace_const_sptr ws = alg->getProperty("OutputWorkspace");
     TS_ASSERT_EQUALS(ws->storageMode(), Parallel::StorageMode::Distributed);
   }
-  
+
   class MyAlgTest : public CxxTest::TestSuite {
   public:
     // ...
-  
+
     void test_parallel() {
       // Runs run_algorithm in multiple threads. The first argument passed to
       // run_algorithm is of type Parallel::Communicator and is guaranteed to
@@ -491,6 +482,9 @@ Supported Algorithms
 ====================================== ======================= ========
 Algorithm                              Supported modes         Comments
 ====================================== ======================= ========
+AlignAndFocusPowder                    all
+AlignAndFocusPowderFromFiles           Distributed
+AlignDetectors                         all                     with ``StorageMode::Distributed`` this touches only detectors that have spectra on this rank, i.e., the modified instrument is not in an identical state on all ranks
 BinaryOperation                        all                     not supported if ``AllowDifferentNumberSpectra`` is enabled
 CalculateChiSquared                    MasterOnly, Identical   see ``IFittingAlgorithm``
 CalculateCostFunction                  MasterOnly, Identical   see ``IFittingAlgorithm``
@@ -500,6 +494,7 @@ CloneWorkspace                         all
 Comment                                all
 CompareWorkspace                       MasterOnly, Identical   if one input has ``StorageMode::Cloned`` and the other has ``StorageMode::MasterOnly`` then ``ExecutionMode::MasterOnly`` is used, with ``ExecutionMode::MasterOnly`` the workspaces always compare equal on non-master ranks
 CompressEvents                         all
+ConvertDiffCal                         MasterOnly, Identical
 ConvertToHistogram                     all
 ConvertToPointData                     all
 ConvertUnits                           all                     ``AlignBins`` not supported; for indirect energy mode the number of resulting bins is in general inconsistent across MPI ranks
@@ -509,6 +504,7 @@ CreateWorkspace                        all
 CropToComponent                        all
 CropWorkspace                          all                     see ``ExtractSpectra`` regarding X cropping
 DeleteWorkspace                        all
+DetermineChunking                      MasterOnly, Identical
 Divide                                 all                     see ``BinaryOperation``
 EstimateFitParameters                  MasterOnly, Identical   see ``IFittingAlgorithm``
 EvaluateFunction                       MasterOnly, Identical   see ``IFittingAlgorithm``
@@ -516,12 +512,16 @@ ExponentialCorrection                  all                     see ``UnaryOperat
 ExtractSingleSpectrum                  all                     in practice ``ExecutionMode::Distributed`` not supported due to current nonzero-spectrum-count limitation
 ExtractSpectra2                        all                     currently not available via algorithm factory or Python
 ExtractSpectra                         all                     not supported with ``DetectorList``, cropping in X may exhibit inconsistent behavior in case spectra have common boundaries within some ranks but not within all ranks or across ranks
+FFTSmooth2                             MasterOnly, Identical
 FilterBadPulses                        all
 FilterByLogValue                       all
 FilterByTime                           all
 FilterEventsByLogValuePreNexus         Identical               see ``IFileLoader``
 FindDetectorsInShape                   all
+FindPeakBackground                     MasterOnly, Identical
+FindPeaks                              MasterOnly, Identical
 Fit                                    MasterOnly, Identical   see ``IFittingAlgorithm``
+GeneratePythonScript                   MasterOnly
 GroupWorkspaces                        all                     grouping workspaces with mixed ``StorageMode`` is not supported
 IFileLoader                            Identical               implicitly adds support for many load-algorithms inheriting from this
 IFittingAlgorithm                      MasterOnly, Identical   implicitly adds support for several fit-algorithms inheriting from this
@@ -529,9 +529,12 @@ Load                                   all                     actual supported
 LoadAscii2                             Identical               see ``IFileLoader``
 LoadAscii                              Identical               see ``IFileLoader``
 LoadBBY                                Identical               see ``IFileLoader``
+LoadCalFile                            Identical
 LoadCanSAS1D                           Identical               see ``IFileLoader``
 LoadDaveGrp                            Identical               see ``IFileLoader``
+LoadDiffCal                            Identical
 LoadEmptyInstrument                    Identical               see ``IFileLoader``
+LoadEventAndCompress                   Distributed
 LoadEventNexus                         Distributed             storage mode of output cannot be changed via a parameter currently, min and max bin boundary are not globally the same
 LoadEventPreNexus2                     Identical               see ``IFileLoader``
 LoadFITS                               Identical               see ``IFileLoader``
@@ -579,17 +582,24 @@ MaskDetectorsInShape                   all
 MaskSpectra                            all
 Minus                                  all                     see ``BinaryOperation``
 MoveInstrumentComponent                all
+MultipleScatteringCylinderAbsorption   all
 Multiply                               all                     see ``BinaryOperation``
+NormaliseByCurrent                     all
 OneMinusExponentialCor                 all                     see ``UnaryOperation``
-Q1D2                                   all                     not all optional normalization inputs are supported
+PDDetermineCharacterizations           all
+PDLoadCharacterizations                Identical
 Plus                                   all                     see ``BinaryOperation``
 PoissonErrors                          all                     see ``BinaryOperation``
 PolynomialCorrection                   all                     see ``UnaryOperation``
+Q1D2                                   all                     not all optional normalization inputs are supported
 Power                                  all                     see ``UnaryOperation``
 PowerLawCorrection                     all                     see ``UnaryOperation``
-Rebin                                  all                     min and max bin boundaries must be given explicitly
+RealFFT                                MasterOnly, Identical
+Rebin                                  all
 RebinToWorkspace                       all                     ``WorkspaceToMatch`` must have ``StorageMode::Cloned``
+RemoveLowResTOF                        all
 RemovePromptPulse                      all
+RenameWorkspace                        all
 ReplaceSpecialValues                   all                     see ``UnaryOperation``
 RotateInstrumentComponent              all
 SANSCalculateTransmission              MasterOnly, Identical
@@ -609,11 +619,19 @@ SANSScale                              all
 SANSSingleReduction                    all
 SANSSliceEvent                         all
 SANSStitch                             MasterOnly, Identical
+SaveFocusedXYE                         MasterOnly
+SaveGSS                                MasterOnly
 SaveNexus                              MasterOnly
 SaveNexusProcessed                     MasterOnly
 Scale                                  all
+SetSampleMaterial                      all
+SetUncertainties                       MasterOnly, Identical
 SignalOverError                        all                     see ``UnaryOperation``
+SNSPowderReduction                     Distributed
 SortEvents                             all
+SortTableWorkspace                     MasterOnly, Identical
+StripPeaks                             MasterOnly, Identical
+StripVanadiumPeaks2                    MasterOnly, Identical
 SumSpectra                             MasterOnly, Identical
 UnaryOperation                         all
 WeightedMean                           all                     see ``BinaryOperation``
@@ -628,5 +646,3 @@ Currently none of the above algorithms works with ``StorageMode::Distributed`` i
 .. [#spectrum-index] Some will argue that this should be ``GlobalWorkspaceIndex``.
   However it is not an index of a workspace so the term ``GlobalSpectrumIndex`` has been chosen for clarity.
   On the user interface side this will still be named 'workspace index', dropping the 'global' since the distinction between global and local indices is irrelevant for users.
-
-.. categories:: Development
diff --git a/dev-docs/source/GettingStarted.rst b/dev-docs/source/GettingStarted.rst
new file mode 100644
index 0000000000000000000000000000000000000000..bfbbe1927dcd6659d91b26cb9aaa644c0afe7443
--- /dev/null
+++ b/dev-docs/source/GettingStarted.rst
@@ -0,0 +1,58 @@
+===========================
+Getting Started with Mantid
+===========================
+
+.. contents::
+  :local:
+
+Prerequisites
+#############
+
+Some intial setup is required before being able to build the code. This is platform
+specific and described here.
+
+Windows
+-------
+
+Install the following:
+
+* `Visual Studio 2015 Community Edition <https://go.microsoft.com/fwlink/?LinkId=532606&clcid=0x409>`_. If you are at RAL then
+  ask for the location of the locally-cached offline version.
+* `Git <https://git-scm.com/download/win>`_
+* `Git LFS <https://git-lfs.github.com/>`_
+
+  * After installation open Git Bash and run ``git lfs install``
+
+* `CMake <https://cmake.org/download/>`_
+* `MiKTeX <https://miktex.org/download>`_. Instructions are available
+  `here <https://miktex.org/howto/install-miktex>`_.
+* `NSIS <http://nsis.sourceforge.net/Download>`_ (optional). Used for building packages
+
+Linux
+-----
+
+Red Hat/Cent OS/Fedora
+^^^^^^^^^^^^^^^^^^^^^^
+
+Follow the instructions at http://download.mantidproject.org/redhat.html to add the
+stable release yum repository and then install the ``mantid-developer`` package:
+
+.. code-block:: sh
+
+   yum install mantid-developer
+
+Ubuntu
+^^^^^^
+
+Follow the instructions at http://download.mantidproject.org/ubuntu.html to add the
+stable release repository and mantid ppa. Download the latest
+`mantid-developer <https://sourceforge.net/projects/mantid/files/developer>`_
+package and install it:
+
+.. code-block:: sh
+
+   apt install gdebi-core
+   apt install ~/Downloads/mantid-developer.X.Y.Z.deb
+
+where ``X.Y.Z`` should be replaced with the version that was downloaded.
+
diff --git a/docs/source/development/ISISSANSReductionBackend.rst b/dev-docs/source/ISISSANSReductionBackend.rst
similarity index 99%
rename from docs/source/development/ISISSANSReductionBackend.rst
rename to dev-docs/source/ISISSANSReductionBackend.rst
index bc8bec49dbcbe12879a48e0ea5932ab9ca2ebeea..a8c7eea083ffeaddf73c112e97a288e6b14cc990 100644
--- a/docs/source/development/ISISSANSReductionBackend.rst
+++ b/dev-docs/source/ISISSANSReductionBackend.rst
@@ -1283,6 +1283,3 @@ The sub-steps of this algorithm are:
     uses the data workspace as well as all of the adjustment workspaces which have been provided earlier
     on. The resulting *OutputWorkspace* and the *SumOfCounts* as well as *SumOfNormFactors* counts
     are provided as outputs.
-
-
-.. categories:: Development
diff --git a/docs/source/concepts/IndexProperty.rst b/dev-docs/source/IndexProperty.rst
similarity index 89%
rename from docs/source/concepts/IndexProperty.rst
rename to dev-docs/source/IndexProperty.rst
index 966cf8ebe3ba981133954926d01abc761220fec7..d24921152925234a336f24b3a8bf07a2eebc60c0 100644
--- a/docs/source/concepts/IndexProperty.rst
+++ b/dev-docs/source/IndexProperty.rst
@@ -12,18 +12,18 @@ Why IndexProperty?
 In many cases, indices are captured from the user with the ``ArrayProperty<int>`` property type. However, this lacks a few key behaviours which
 a property collecting workspace index information should have. Firstly, there is no automatic validation of the indices with respect to
 the workspace itself. Therefore users could enter invalid input e.g negative numbers, or numbers outside of
-the range of spectra held by the workspace, unless bounded validators or used. Additionally, the ``ArrayProperty<int>`` 
-does not lend itself to a distributed method for accessing workspace data, this would require manual conversion 
+the range of spectra held by the workspace, unless bounded validators or used. Additionally, the ``ArrayProperty<int>``
+does not lend itself to a distributed method for accessing workspace data, this would require manual conversion
 of global indices to local indices on each mpi rank. Finally, any conversion between "index types" i.e. conversion from
 spectrum numbers to workspace indices (also known as spectrum indices) must be managed by the algorithm developer. This style
 of development is very error prone, particularly in the MPI case, and could lead to inconsitencies across algorithms as
 each developer may have a different approach to addressing the aforementioned issues.
 
-The ``IndexProperty`` in Mantid provides a consistent interface for algorithms 
-which need to access a subset of the workspace spectra for processing.The ``IndexProperty`` facilitates 
-the retrieval of a set of workspace indices, called a ``SpectrumIndexSet``, once provided with a set of workspace indices or 
-spectrum numbers [#]_. Therefore algorithm developers do not need to perform any conversions manually.  In situations where data is 
-distributed across several clusters (distributed processing) [#]_, the underlying ``IndexInfo`` object, which is used to 
+The ``IndexProperty`` in Mantid provides a consistent interface for algorithms
+which need to access a subset of the workspace spectra for processing.The ``IndexProperty`` facilitates
+the retrieval of a set of workspace indices, called a ``SpectrumIndexSet``, once provided with a set of workspace indices or
+spectrum numbers [#]_. Therefore algorithm developers do not need to perform any conversions manually.  In situations where data is
+distributed across several clusters (distributed processing) [#]_, the underlying ``IndexInfo`` object, which is used to
 obtain a ``SpectrumIndexSet``, hides these mpi-specific details by automatically selecting the correct indices for
 a specific mpi rank. Therefore access to the workspace data remains unchanged for developers.
 
@@ -38,9 +38,9 @@ which define the workspace and the input type of the indices which represent
 the subset of the data. However, developers do not need to concern themselves
 with maintaining these properties on their own. There are few special methods in
 ``Algorithm`` which handle this. Namely, ``Algorithm::declareWorkspaceInputProperties``,
-``Algorithm::setWorkspaceInputProperties`` and ``Algorithm::getWorkspaceAndIndices`` [#]_. 
+``Algorithm::setWorkspaceInputProperties`` and ``Algorithm::getWorkspaceAndIndices`` [#]_.
 
-Property declaration is as shown below: 
+Property declaration is as shown below:
 
 .. code-block:: cpp
 
@@ -62,10 +62,10 @@ Property declaration is as shown below:
 
 Internally, a ``WorkspaceProperty`` is created along with an ``IndexTypeProperty`` for
 managing the workspace and the type of user-defined input index list respectively. Their names are
-automatically generated based on the property name in the declaration. 
+automatically generated based on the property name in the declaration.
 A toy example algorithm dialog in the GUI would have the following inputs defined:
 
-.. image:: ../images/IndexPropertyDialogExample.png
+.. image:: images/IndexPropertyDialogExample.png
    :height: 300px
    :alt: Toy Example of an Algorithm Dialog using IndexProperty
 
@@ -77,12 +77,12 @@ within the algorithm as follows:
   //Declare workspace and index set
   MatrixWorkspace_sptr inputWs;
   SpectrumIndexSet indexSet;
-  
+
   //Method returns a tuple of the workspace
   //and index set simultaneously
-  std::tie(inputWs, indexSet) = 
+  std::tie(inputWs, indexSet) =
         getWorkspaceAndIndices<MatrixWorkspace>("InputWorkspace");
-        
+
   for(auto index: indexSet){
     auto &spec = inputWs->getSpectrum(index);
     //do something with spectrum.
@@ -95,22 +95,19 @@ For setting the property values, there are 4 valid options:
    //Set Property with workspace_sptr and string of indices
    setWorkspaceInputProperties<MatrixWorkspace, std::string>(
       "InputWorkspace", ws, IndexType::WorkspaceIndex, "1:5")
-      
+
    //Set Property with workspace name and string of indices
    setWorkspaceInputProperties<MatrixWorkspace, std::string>(
       "InputWorkspace", "ws", IndexType::WorkspaceIndex, "1:5")
-      
+
    //Set Property with workspace_sptr and vector of indices
    setWorkspaceInputProperties<MatrixWorkspace, std::vector<int>>(
-      "InputWorkspace", ws, IndexType::WorkspaceIndex, 
+      "InputWorkspace", ws, IndexType::WorkspaceIndex,
        std::vector<int>{1, 2, 3, 4, 5})
-       
+
    //Set Property with workspace name and vector of indices
    setWorkspaceInputProperties<MatrixWorkspace, std::vector<int>>(
-      "InputWorkspace", "ws", IndexType::WorkspaceIndex, 
+      "InputWorkspace", "ws", IndexType::WorkspaceIndex,
        std::vector<int>{1, 2, 3, 4, 5})
 
-.. categories:: Concepts
-
 .. [#] It is important to note that any attempt to access the ``IndexProperty`` or the ``WorkspaceProperty`` in isolation will fail. Once defined using the ``Algorithm::declareWorkspaceInputProperties`` method, all access must be via the three methods mentioned above.
-
diff --git a/dev-docs/source/_static/color_ranking_definitions.js b/dev-docs/source/_static/color_ranking_definitions.js
new file mode 100644
index 0000000000000000000000000000000000000000..50f7cb2d8085c509830103e2fdffda9db0f6f4a7
--- /dev/null
+++ b/dev-docs/source/_static/color_ranking_definitions.js
@@ -0,0 +1,8 @@
+
+$(document).ready(function() {
+             $('.ranking-top-1').parent().addClass('ranking-top-1-parent');
+             $('.ranking-top-2').parent().addClass('ranking-top-2-parent');
+             $('.ranking-med-3').parent().addClass('ranking-med-3-parent');
+             $('.ranking-low-4').parent().addClass('ranking-low-4-parent');
+             $('.ranking-low-5').parent().addClass('ranking-low-5-parent');
+});
diff --git a/dev-docs/source/_static/custom.css b/dev-docs/source/_static/custom.css
new file mode 100644
index 0000000000000000000000000000000000000000..48cb336b6e4fb584279f775865414555a3eafb6d
--- /dev/null
+++ b/dev-docs/source/_static/custom.css
@@ -0,0 +1,128 @@
+/*-------------------- $GENERAL --------------------*/
+
+#table-of-contents { border: none; }
+
+/*-------------------- $NAV --------------------*/
+
+.navbar-version { display: none; }
+
+/*-------------------- $LINKS --------------------*/
+
+a {
+  color: #009933;
+  text-decoration: none;
+}
+
+a:hover {
+  color: #009933;
+  text-decoration: underline;
+}
+
+/*-------------------- $SPACING --------------------*/
+
+body { font-size: 1.6em; } /* 16px */
+
+h1{
+  margin-top: 0;
+}
+
+/*-------------------- $MATH ----------------------*/
+
+img.math {
+    vertical-align: middle;
+}
+
+div.math p {
+    text-align: center;
+}
+
+span.eqno {
+    float: right;
+}
+
+/*-------------------- $IMAGES --------------------*/
+
+.screenshot {
+  display: block;
+  margin-left: auto;
+  margin-right: auto;
+  padding-bottom: 0.1em;
+}
+
+.caption {
+  text-align: center;
+  font-size: 0.9em;
+}
+
+.navbar-brand {
+  padding: 4px 15px 0px 15px;
+}
+
+.navbar-brand > img{
+  width: 85px;
+}
+
+/*-------------------- $TABLES --------------------*/
+
+table {
+  width: 100%;
+  border-collapse: collapse;
+  margin: 1.6em 0;
+}
+
+/* Zebra striping */
+tr:nth-of-type(odd) { background: white; }
+
+tr:hover { background-color: whiteSmoke; }
+
+th {
+  background: #E0DFDE;
+  font-size: 1.3em;
+}
+
+td, th {
+  padding: .75em;
+  border: 1px solid #CCC;
+  text-align: left;
+}
+
+/*-------------------- $SPHINX-OVERRIDES --------------------*/
+
+.warning { overflow: hidden; }
+
+/*-------------------- $MEDIA-QUERIES --------------------*/
+
+@media (min-width: 1200px) {
+  #table-of-contents {
+    width: auto;
+    max-width: 40%;
+  }
+}
+/*---------------------Coloured_text-------------------------*/
+
+.red {
+    color:red;
+}
+.blue {
+    color:blue;
+}
+.green {
+    color:green;
+}
+
+/*-------Introduced here for colors in table cells, for the comparisons of minimizers-----*/
+.ranking-top-1 {
+    background-color: #fef0d9;
+}
+.ranking-top-2 {
+    background-color: #fdcc8a;
+}
+.ranking-med-3 {
+    background-color: #fc8d59;
+}
+.ranking-low-4 {
+    background-color: #e34a33;
+}
+.ranking-low-5 {
+    background-color: #b30000;
+}
diff --git a/dev-docs/source/_static/epub.css b/dev-docs/source/_static/epub.css
new file mode 100644
index 0000000000000000000000000000000000000000..84357b9ca76f963e9a8727e0463b2831a53c26dc
--- /dev/null
+++ b/dev-docs/source/_static/epub.css
@@ -0,0 +1,494 @@
+/*
+ * epub.css_t
+ * ~~~~~~~~~~
+ *
+ * Sphinx stylesheet -- epub theme.
+ *
+ * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+/* -- main layout ----------------------------------------------------------- */
+
+div.clearer {
+    clear: both;
+}
+
+a:link, a:visited {
+    color: #3333ff;
+    text-decoration: underline;
+}
+
+img {
+    border: 0;
+    max-width: 100%;
+}
+
+/* -- relbar ---------------------------------------------------------------- */
+
+div.related {
+    width: 100%;
+    font-family: sans-serif;
+    font-size: 90%;
+}
+
+div.related h3 {
+    display: none;
+}
+
+div.related ul {
+    margin: 0;
+    padding: 0 0 0 10px;
+    list-style: none;
+}
+
+div.related li {
+    display: inline;
+}
+
+div.related li.right {
+    float: right;
+    margin-right: 5px;
+}
+
+/* -- sidebar --------------------------------------------------------------- */
+
+div.sphinxsidebarwrapper {
+    padding: 10px 5px 0 10px;
+}
+
+div.sphinxsidebar {
+    float: left;
+    width: 230px;
+    margin-left: -100%;
+    font-size: 90%;
+}
+
+div.sphinxsidebar ul {
+    list-style: none;
+}
+
+div.sphinxsidebar ul ul,
+div.sphinxsidebar ul.want-points {
+    margin-left: 20px;
+    list-style: square;
+}
+
+div.sphinxsidebar ul ul {
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+div.sphinxsidebar form {
+    margin-top: 10px;
+}
+
+div.sphinxsidebar input {
+    border: 1px solid #98dbcc;
+    font-family: sans-serif;
+    font-size: 100%;
+}
+
+img {
+    border: 0;
+    max-width: 100%;
+}
+
+/* -- search page ----------------------------------------------------------- */
+
+ul.search {
+    margin: 10px 0 0 20px;
+    padding: 0;
+}
+
+ul.search li {
+    padding: 5px 0 5px 20px;
+    background-image: url(file.png);
+    background-repeat: no-repeat;
+    background-position: 0 7px;
+}
+
+ul.search li a {
+    font-weight: bold;
+}
+
+ul.search li div.context {
+    color: #888;
+    margin: 2px 0 0 30px;
+    text-align: left;
+}
+
+ul.keywordmatches li.goodmatch a {
+    font-weight: bold;
+}
+
+/* -- index page ------------------------------------------------------------ */
+
+table.contentstable {
+    width: 90%;
+}
+
+table.contentstable p.biglink {
+    line-height: 150%;
+}
+
+a.biglink {
+    font-size: 130%;
+}
+
+span.linkdescr {
+    font-style: italic;
+    padding-top: 5px;
+    font-size: 90%;
+}
+
+/* -- general index --------------------------------------------------------- */
+
+table.indextable td {
+    text-align: left;
+    vertical-align: top;
+}
+
+table.indextable dl, table.indextable dd {
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+table.indextable tr.pcap {
+    height: 10px;
+}
+
+table.indextable tr.cap {
+    margin-top: 10px;
+    background-color: #f2f2f2;
+}
+
+img.toggler {
+    margin-right: 3px;
+    margin-top: 3px;
+    cursor: pointer;
+}
+
+/* -- general body styles --------------------------------------------------- */
+
+a.headerlink {
+    visibility: hidden;
+}
+
+div.body p.caption {
+    text-align: inherit;
+}
+
+div.body td {
+    text-align: left;
+}
+
+.field-list ul {
+    padding-left: 100%;
+}
+
+.first {
+    margin-top: 0 !important;
+}
+
+p.rubric {
+    margin-top: 30px;
+    font-weight: bold;
+}
+
+/* -- sidebars -------------------------------------------------------------- */
+
+div.sidebar {
+    margin: 0 0 0.5em 1em;
+    border: 1px solid #ddb;
+    padding: 7px 7px 0 7px;
+    background-color: #ffe;
+    width: 40%;
+    float: right;
+}
+
+p.sidebar-title {
+    font-weight: bold;
+}
+
+/* -- topics ---------------------------------------------------------------- */
+
+div.topic {
+    border: 1px solid #ccc;
+    padding: 7px 7px 0 7px;
+    margin: 10px 0 10px 0;
+}
+
+p.topic-title {
+    font-size: 110%;
+    font-weight: bold;
+    margin-top: 10px;
+}
+
+/* -- admonitions ----------------------------------------------------------- */
+
+div.admonition {
+    margin-top: 10px;
+    margin-bottom: 10px;
+    padding: 7px;
+}
+
+div.admonition dt {
+    font-weight: bold;
+}
+
+div.admonition dl {
+    margin-bottom: 0;
+}
+
+p.admonition-title {
+    margin: 0px 10px 5px 0px;
+    font-weight: bold;
+}
+
+div.body p.centered {
+    text-align: center;
+    margin-top: 25px;
+}
+
+/* -- tables ---------------------------------------------------------------- */
+
+table.docutils {
+    border: 0;
+    border-collapse: collapse;
+}
+
+table.docutils td, table.docutils th {
+    padding: 1px 8px 1px 5px;
+    border-top: 0;
+    border-left: 0;
+    border-right: 0;
+    border-bottom: 1px solid #aaa;
+}
+
+table.field-list td, table.field-list th {
+    border: 0 !important;
+}
+
+table.footnote td, table.footnote th {
+    border: 0 !important;
+}
+
+th {
+    text-align: left;
+    padding-right: 5px;
+}
+
+/* -- other body styles ----------------------------------------------------- */
+
+dl {
+    margin-bottom: 15px;
+}
+
+dd p {
+    margin-top: 0px;
+}
+
+dd ul, dd table {
+    margin-bottom: 10px;
+}
+
+dd {
+    margin-top: 3px;
+    margin-bottom: 10px;
+    margin-left: 30px;
+}
+
+dt:target, .highlighted {
+    background-color: #ddd;
+}
+
+dl.glossary dt {
+    font-weight: bold;
+    font-size: 110%;
+}
+
+.field-list ul {
+    margin: 0;
+    padding-left: 1em;
+}
+
+.field-list p {
+    margin: 0;
+}
+
+.optional {
+    font-size: 130%;
+}
+
+.versionmodified {
+    font-style: italic;
+}
+
+.system-message {
+    background-color: #fda;
+    padding: 5px;
+    border: 3px solid red;
+}
+
+.footnote:target  {
+    background-color: #dddddd;
+}
+
+/* -- code displays --------------------------------------------------------- */
+
+pre {
+    font-family: monospace;
+    overflow: auto;
+    overflow-y: hidden;
+}
+
+td.linenos pre {
+    padding: 5px 0px;
+    border: 0;
+    background-color: transparent;
+    color: #aaa;
+}
+
+table.highlighttable {
+    margin-left: 0.5em;
+}
+
+table.highlighttable td {
+    padding: 0 0.5em 0 0.5em;
+}
+
+tt {
+    font-family: monospace;
+}
+
+tt.descname {
+    background-color: transparent;
+    font-weight: bold;
+    font-size: 1.2em;
+}
+
+tt.descclassname {
+    background-color: transparent;
+}
+
+tt.xref, a tt {
+    background-color: transparent;
+    font-weight: bold;
+}
+
+h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
+    background-color: transparent;
+}
+
+/* -- math display ---------------------------------------------------------- */
+
+img.math {
+    vertical-align: middle;
+}
+
+div.body div.math p {
+    text-align: center;
+}
+
+span.eqno {
+    float: right;
+}
+
+/* -- special divs  --------------------------------------------------------- */
+
+div.quotebar {
+    background-color: #e3eff1;
+    max-width: 250px;
+    float: right;
+    font-family: sans-serif;
+    padding: 7px 7px;
+    border: 1px solid #ccc;
+}
+div.footer {
+    background-color: #e3eff1;
+    padding: 3px 8px 3px 0;
+    clear: both;
+    font-family: sans-serif;
+    font-size: 80%;
+    text-align: right;
+}
+
+div.footer a {
+    text-decoration: underline;
+}
+
+img.align-left, .figure.align-left, object.align-left {
+    clear: left;
+    float: left;
+    margin-right: 1em;
+}
+
+img.align-right, .figure.align-right, object.align-right {
+    clear: right;
+    float: right;
+    margin-left: 1em;
+}
+
+img.align-center, .figure.align-center, object.align-center {
+  display: block;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.align-left {
+    text-align: left;
+}
+
+.align-center {
+    text-align: center;
+}
+
+.align-right {
+    text-align: right;
+}
+
+/* -- link-target ----------------------------------------------------------- */
+
+.link-target {
+    font-size: 80%;
+}
+
+table .link-target {
+    /* Do not show links in tables, there is not enough space */
+    display: none;
+}
+
+/* -- font-face ------------------------------------------------------------- */
+
+/*
+@font-face {
+    font-family: "LiberationNarrow";
+    font-style: normal;
+    font-weight: normal;
+    src: url("res:///Data/fonts/LiberationNarrow-Regular.otf")
+        format("opentype");
+}
+@font-face {
+    font-family: "LiberationNarrow";
+    font-style: oblique, italic;
+    font-weight: normal;
+    src: url("res:///Data/fonts/LiberationNarrow-Italic.otf")
+        format("opentype");
+}
+@font-face {
+    font-family: "LiberationNarrow";
+    font-style: normal;
+    font-weight: bold;
+    src: url("res:///Data/fonts/LiberationNarrow-Bold.otf")
+        format("opentype");
+}
+@font-face {
+    font-family: "LiberationNarrow";
+    font-style: oblique, italic;
+    font-weight: bold;
+    src: url("res:///Data/fonts/LiberationNarrow-BoldItalic.otf")
+        format("opentype");
+}
+*/
\ No newline at end of file
diff --git a/dev-docs/source/_templates/category.html b/dev-docs/source/_templates/category.html
new file mode 100644
index 0000000000000000000000000000000000000000..084d0d034eca5fa3744b1b90e11314f37714efb1
--- /dev/null
+++ b/dev-docs/source/_templates/category.html
@@ -0,0 +1,49 @@
+{# Generates a better view for category pages #}
+
+{% extends "layout.html" %}
+
+{% macro split_to_three_columns(item_list) -%}
+{# Split list into 3 columns #}
+{# Uses bootstrap col-md-4 for each column along with slice(3) to divide input list #}
+{# It currently assumes the items in the list are mantiddoc.directives.categories.PageRef classes #}
+    <div class="row">
+    {%- for column in item_list|slice(3) %}
+      <div class="col-md-4">
+      {%- set first = True %}
+      {%- for item in column %}
+        {%- if (item.name[0] != section or first) %}
+          {%- set section = item.name[0] %}
+          {%- if first != true %}
+            </ul>
+          {%- endif %}
+          <h3 style="font-weight:bold">{{ section }}</h3>
+          <ul>
+        {%- endif %}
+        <li><a href="{{ item.link(outpath) }}">{{ item.name }}</a></li>
+        {%- set first = False -%}
+      {%- endfor -%}
+      </ul>
+      </div>
+    {%- endfor -%}
+    </div>
+{%- endmacro %}
+
+{%- block body -%}
+    <h1> Category: {{ title }} </h1>
+    {% if subcategories %}
+    <br>
+    <h2> Subcategories </h2>
+    {{ split_to_three_columns(subcategories) }}
+    <hr>
+    {% endif %}
+
+    {% if pages %}
+    <h2> Pages </h2>
+    {{ split_to_three_columns(pages) }}
+    {% endif %}
+    
+    {% if parentcategory %}
+    <p> {{ parentcategory }} </p>
+    {% endif %}
+
+{%- endblock -%}
\ No newline at end of file
diff --git a/dev-docs/source/_templates/globaltoc.html b/dev-docs/source/_templates/globaltoc.html
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/dev-docs/source/_templates/layout.html b/dev-docs/source/_templates/layout.html
new file mode 100644
index 0000000000000000000000000000000000000000..81c75a3d306762706c42f27765f4581ad5b4b3e9
--- /dev/null
+++ b/dev-docs/source/_templates/layout.html
@@ -0,0 +1,52 @@
+{% extends "!layout.html" %}
+
+{% block extrahead %}
+{% if builder == "html" %}
+<script>
+  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+  ga('create', 'UA-59110517-1', 'auto');
+  ga('send', 'pageview');
+
+</script>
+{% endif %}
+{% endblock %}
+
+{% block footer %}
+<footer class="footer">
+  <div class="container">
+      <ul class="nav navbar-nav" style=" float: right;">
+      {% if theme_source_link_position == "footer" %}
+        <li>{% include "sourcelink.html" %}</li>
+      {% endif %}
+      {% if theme_navbar_sidebarrel %}
+          {% block sidebarrel %}
+            {% include "relations.html" %}
+          {% endblock %}
+       {% endif %}
+          <li><a href="#">Back to top</a></li>
+       </ul>
+    <p>
+    {%- if show_copyright %}
+      {%- if hasdoc('copyright') %}
+        {% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}<br/>
+      {%- else %}
+        {% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}.{% endtrans %}<br/>
+      {%- endif %}
+    {%- endif %}
+    {%- if last_updated %}
+      {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}<br/>
+    {%- endif %}
+    {%- if show_sphinx %}
+      {% trans sphinx_version=sphinx_version|e %}Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> {{ sphinx_version }}.{% endtrans %}<br/>
+    {%- endif %}
+    </p>
+  </div>
+</footer>
+{%- endblock %}
+
+{# Custom CSS overrides #}
+{% set bootswatch_css_custom = ['_static/custom.css'] %}
diff --git a/dev-docs/source/_templates/navbar.html b/dev-docs/source/_templates/navbar.html
new file mode 100644
index 0000000000000000000000000000000000000000..ace61b0efd278b23fbcd4f78704c3941a802c5df
--- /dev/null
+++ b/dev-docs/source/_templates/navbar.html
@@ -0,0 +1,58 @@
+{# Copied from base bootstrap version and modified to: #}
+{#   Remove search box when in embedded mode (qthelp etc) #}
+{#   Point the main logo anchor to www.mantidproject.org in standard mode and the master document in embedded mode #}
+
+  <div id="navbar" class="{{ theme_navbar_class }} navbar-default {% if theme_navbar_fixed_top == 'true' -%} navbar-fixed-top{%- endif -%}">
+    <div class="container">
+      <div class="navbar-header">
+        {% if (not embedded) %}
+        <!-- .btn-navbar is used as the toggle for collapsed navbar content -->
+        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".nav-collapse">
+          <span class="icon-bar"></span>
+          <span class="icon-bar"></span>
+          <span class="icon-bar"></span>
+        </button>
+        <a class="navbar-brand" href="http://www.mantidproject.org">
+        {%- else -%}
+        <a class="navbar-brand" href="{{ pathto(master_doc) }}">
+        {%- endif -%}
+          {%- block sidebarlogo %}
+            {%- if logo %}<img src="{{ pathto('_static/' + logo, 1) }}">{%- endif %}
+          {%- endblock %}
+          {% if theme_navbar_title -%}{{ theme_navbar_title|e }}{%- else -%}{{ project|e }}{%- endif -%}
+        </a>
+        <span class="navbar-text navbar-version pull-left"><b>{{ version|e }}</b></span>
+      </div>
+
+      {% if (not embedded) %}
+        <div class="collapse navbar-collapse nav-collapse">
+      {% else %}
+        <div>
+      {% endif %}
+          <ul class="nav navbar-nav">
+            <li class="divider-vertical"></li>
+            {% if theme_navbar_links %}
+              {%- for link in theme_navbar_links %}
+                <li><a href="{{ pathto(*link[1:]) }}">{{ link[0] }}</a></li>
+              {%- endfor %}
+            {% endif %}
+            {% block navbartoc %}
+              {% include "globaltoc.html" %}
+              {% if theme_navbar_pagenav %}
+                {% include "navbartoc.html" %}
+              {% endif %}
+            {% endblock %}
+            {% block navbarextra %}
+            {% endblock %}
+            {% if theme_source_link_position == "nav" %}
+              <li class="hidden-sm">{% include "sourcelink.html" %}</li>
+            {% endif %}
+          </ul>
+          {%- if (not embedded) -%}
+            {% block navbarsearch %}
+              {% include "navbarsearchbox.html" %}
+            {% endblock %}
+          {%- endif -%}
+        </div>
+    </div>
+  </div>
diff --git a/dev-docs/source/_templates/redirect.html b/dev-docs/source/_templates/redirect.html
new file mode 100644
index 0000000000000000000000000000000000000000..e73ecf33d325c754c9507041d2c4702bd03c5689
--- /dev/null
+++ b/dev-docs/source/_templates/redirect.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html lang="en-US">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="refresh" content="1;{{ target }}">
+    <script type="text/javascript">
+        window.location.href = "{{ target }}"
+    </script>
+    <title>Page Redirection</title>
+</head>
+<body>
+  <!-- Note: don't tell people to `click` the link, just tell them that it is a link. -->
+  If you are not redirected automatically, follow the <a href="{{ target }}">link to {{ name }}</a>
+</body>
+</html>
\ No newline at end of file
diff --git a/dev-docs/source/conf.py b/dev-docs/source/conf.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad87f97943d16c0e7ae6a3662abdb6d491ef5639
--- /dev/null
+++ b/dev-docs/source/conf.py
@@ -0,0 +1,185 @@
+# -*- mode: python -*-
+# -*- coding: utf-8 -*-
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import re
+import os
+from warnings import warn
+
+import sphinx_bootstrap_theme
+
+
+VERSION_STR_RE = re.compile(r'^\s*return "(\d+\.\d+\.(\d{8}\.\d{4}|\d+))";$')
+
+
+def _version_string_from_cpp(filename):
+    vers_cpp_lines = open(filename, 'r').readlines()
+    version_str = '0.0.0'
+    for line in vers_cpp_lines:
+        match = VERSION_STR_RE.match(line.rstrip())
+        if match is not None:
+            version_str = match.group(1)
+            break
+
+    return version_str
+
+# -- General configuration ------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    # we use pngmath over mathjax so that the the offline help isn't reliant on
+    # anything external and we don't need to include the large mathjax package
+    'sphinx.ext.pngmath',
+    'sphinx.ext.autodoc',
+    'sphinx.ext.intersphinx'
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'MantidProject'
+copyright = u'2007-2018, Mantid'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+# This is read from Framework/Kernel/src/MantidVersion.cpp to avoid the requirement of having
+# a built copy of mantid
+version_cpp_path = os.path.relpath(os.path.join('..', '..', 'Framework',
+                                                'Kernel', 'src', 'MantidVersion.cpp'))
+try:
+    version_str = _version_string_from_cpp(version_cpp_path)
+except StandardError as ex:
+    warn("WARNING: Unable to parse version from MantidVersion: {}\nSetting version string to 0.0.0".format(str(ex)))
+    version_str = '0.0.0'
+
+# The short X.Y version.
+version = ".".join(version_str.split(".")[:2])
+# The full version, including alpha/beta/rc tags.
+release = version_str
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# -- Options for pngmath --------------------------------------------------
+
+# Load the preview package into latex
+pngmath_latex_preamble=r'\usepackage[active]{preview}'
+
+# Ensures that the vertical alignment of equations is correct.
+# See http://sphinx-doc.org/ext/math.html#confval-pngmath_use_preview
+pngmath_use_preview = True
+
+# -- HTML output ----------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'bootstrap'
+
+# Add any paths that contain custom themes here, relative to this directory.
+html_theme_path = sphinx_bootstrap_theme.get_html_theme_path()
+
+# The "title" for HTML documentation generated with Sphinx' templates. This is appended to the <title> tag of individual pages
+# and used in the navigation bar as the "topmost" element.
+html_title = ""
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+html_logo = os.path.relpath(os.path.join('..', '..', 'images', 'Mantid_Logo_Transparent.png'))
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+#html_extra_path = []
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+html_use_smartypants = True
+
+# Hide the Sphinx usage as we reference it on github instead.
+html_show_sphinx = False
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+html_show_copyright = False
+
+# Theme-specific options to customize the look and feel of a theme.
+# We config the bootstrap settings here, and apply CSS changes in
+# custom.css rather than here.
+html_theme_options = {
+    # Navigation bar title.
+    'navbar_title': " ", # deliberate single space so it's not visible
+    # Tab name for entire site.
+    'navbar_site_name': "Mantid",
+    # Add links to the nav bar. Third param of tuple is true to create absolute url.
+    'navbar_links': [
+        ("Home", "http://www.mantidproject.org", True),
+        ("Download", "http://download.mantidproject.org", True),
+        ("Documentation", "http://www.mantidproject.org/Documentation", True),
+        ("Contact Us", "http://www.mantidproject.org/Contact", True),
+    ],
+    # Do not show the "Show source" button.
+    'source_link_position': "no",
+    # Remove the local TOC from the nav bar
+    'navbar_pagenav': False,
+    # Hide the next/previous in the nav bar.
+    'navbar_sidebarrel': True,
+    # Use the latest version.
+    'bootstrap_version': "3",
+    # Ensure the nav bar always stays on top of page.
+    'navbar_fixed_top': "false",
+}
+
+# -- Options for Epub output ---------------------------------------------------
+# This flag determines if a toc entry is inserted again at the beginning of its nested toc listing.
+# This allows easier navigation to the top of a chapter, but can be confusing because it mixes entries of different depth in one list.
+# The default value is True.
+epub_tocdup = True
+
+#This setting control the scope of the epub table of contents
+epub_tocscope = 'includehidden'
+
+#The author of the document. This is put in the Dublin Core metadata. The default value is 'unknown'.
+epub_author = "The Mantid Project"
+
+#The publisher of the document. This is put in the Dublin Core metadata. You may use any sensible string, e.g. the project homepage.
+epub_publisher = "The Mantid Project"
+
+#An identifier for the document. This is put in the Dublin Core metadata.
+#For published documents this is the ISBN number, but you can also use an alternative scheme, e.g. the project homepage.
+#The default value is 'unknown'.
+epub_identifier = "www.mantidproject.org"
+
+#The publication scheme for the epub_identifier. This is put in the Dublin Core metadata.
+#For published books the scheme is 'ISBN'. If you use the project homepage, 'URL' seems reasonable.
+#The default value is 'unknown'.
+epub_scheme='URL'
+
+#A unique identifier for the document. This is put in the Dublin Core metadata. You may use a random string.
+#The default value is 'unknown'.
+epub_uid = "Mantid Reference: " + version
+
+# -- Link to other projects ----------------------------------------------------
+
+intersphinx_mapping = {
+    'h5py': ('http://docs.h5py.org/en/latest/', None),
+    'matplotlib': ('http://matplotlib.org', None),
+    'numpy': ('https://docs.scipy.org/doc/numpy/', None),
+    'python': ('https://docs.python.org/3/', None),
+    'SciPy': ('http://docs.scipy.org/doc/scipy/reference', None),
+    'mantid': ('http://docs.mantidproject.org/', None)
+}
diff --git a/docs/source/images/IndexPropertyDialogExample.png b/dev-docs/source/images/IndexPropertyDialogExample.png
similarity index 100%
rename from docs/source/images/IndexPropertyDialogExample.png
rename to dev-docs/source/images/IndexPropertyDialogExample.png
diff --git a/docs/source/images/MPI-execution-mode-distributed-gather.png b/dev-docs/source/images/MPI-execution-mode-distributed-gather.png
similarity index 100%
rename from docs/source/images/MPI-execution-mode-distributed-gather.png
rename to dev-docs/source/images/MPI-execution-mode-distributed-gather.png
diff --git a/docs/source/images/MPI-execution-mode-distributed-load.png b/dev-docs/source/images/MPI-execution-mode-distributed-load.png
similarity index 100%
rename from docs/source/images/MPI-execution-mode-distributed-load.png
rename to dev-docs/source/images/MPI-execution-mode-distributed-load.png
diff --git a/docs/source/images/MPI-execution-mode-distributed.png b/dev-docs/source/images/MPI-execution-mode-distributed.png
similarity index 100%
rename from docs/source/images/MPI-execution-mode-distributed.png
rename to dev-docs/source/images/MPI-execution-mode-distributed.png
diff --git a/docs/source/images/MPI-execution-mode-identical.png b/dev-docs/source/images/MPI-execution-mode-identical.png
similarity index 100%
rename from docs/source/images/MPI-execution-mode-identical.png
rename to dev-docs/source/images/MPI-execution-mode-identical.png
diff --git a/docs/source/images/MPI-execution-mode-master-only-load.png b/dev-docs/source/images/MPI-execution-mode-master-only-load.png
similarity index 100%
rename from docs/source/images/MPI-execution-mode-master-only-load.png
rename to dev-docs/source/images/MPI-execution-mode-master-only-load.png
diff --git a/docs/source/images/MPI-execution-mode-master-only-store.png b/dev-docs/source/images/MPI-execution-mode-master-only-store.png
similarity index 100%
rename from docs/source/images/MPI-execution-mode-master-only-store.png
rename to dev-docs/source/images/MPI-execution-mode-master-only-store.png
diff --git a/docs/source/images/MPI-execution-mode-master-only.png b/dev-docs/source/images/MPI-execution-mode-master-only.png
similarity index 100%
rename from docs/source/images/MPI-execution-mode-master-only.png
rename to dev-docs/source/images/MPI-execution-mode-master-only.png
diff --git a/docs/source/images/MPI-storage-modes.png b/dev-docs/source/images/MPI-storage-modes.png
similarity index 100%
rename from docs/source/images/MPI-storage-modes.png
rename to dev-docs/source/images/MPI-storage-modes.png
diff --git a/dev-docs/source/images/Mantid_Logo_Transparent.png b/dev-docs/source/images/Mantid_Logo_Transparent.png
new file mode 100644
index 0000000000000000000000000000000000000000..5d487d969ae677cc389d81f598cde6a888439d5d
Binary files /dev/null and b/dev-docs/source/images/Mantid_Logo_Transparent.png differ
diff --git a/dev-docs/source/index.rst b/dev-docs/source/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..ebf236f4ac0c7affdda5a1112c1bec7d36493a52
--- /dev/null
+++ b/dev-docs/source/index.rst
@@ -0,0 +1,37 @@
+.. Documentation master file
+   It contains a hidden root toctree directive so that Sphinx
+   has an internal index of all of the pages and doesn't
+   produce a plethora of warnings about most documents not being in
+   a toctree.
+   See http://sphinx-doc.org/tutorial.html#defining-document-structure
+
+.. _contents:
+
+=======================
+Developer Documentation
+=======================
+
+These pages contain the developer documentation for mantid version |release|.
+
+======
+Guides
+======
+
+.. toctree::
+   :hidden:
+
+   GettingStarted
+
+:doc:`GettingStarted`
+   Describes the process of obtaining and building the mantid code base
+
+===================
+Component Overviews
+===================
+
+.. toctree::
+   :maxdepth: 1
+
+   AlgorithmMPISupport
+   IndexProperty
+   ISISSANSReductionBackend
diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt
index f136cda339dd0628b9a65418b7f2a92125814b90..d9973eba96916a0d5e6520ae73ee21d3cbcaad83 100644
--- a/docs/CMakeLists.txt
+++ b/docs/CMakeLists.txt
@@ -1,169 +1,149 @@
 ###############################################################################
 # Sphinx documentation
 ###############################################################################
-find_package ( Sphinx 1.2 )
-
-if ( SPHINX_FOUND )
-  # run python to see if the theme is installed
-  execute_process ( COMMAND ${PYTHON_EXECUTABLE} -c "import sphinx_bootstrap_theme"
-                    OUTPUT_VARIABLE SPHINX_BOOTSTRAP_THEME_OUT
-                    ERROR_VARIABLE SPHINX_BOOTSTRAP_THEME_ERR
-                    OUTPUT_STRIP_TRAILING_WHITESPACE
-                    ERROR_STRIP_TRAILING_WHITESPACE )
-
-  # require the bootstrap theme
-  if (SPHINX_BOOTSTRAP_THEME_ERR)
-    message (ERROR " Did not find sphinx_bootstrap_theme")
-    message (STATUS "${PYTHON_EXECUTABLE} -c \"import sphinx_bootstrap_theme\"")
-    message (STATUS "${SPHINX_BOOTSTRAP_THEME_ERR}")
-    message (FATAL_ERROR " Install instructions at https://pypi.python.org/pypi/sphinx-bootstrap-theme/")
-  endif (SPHINX_BOOTSTRAP_THEME_ERR)
-
-  # We generate a target per build type, i.e docs-html
-  set ( SPHINX_CONF_DIR ${CMAKE_CURRENT_SOURCE_DIR}/source )
-  set ( SPHINX_BUILD_DIR ${DOCS_BUILDDIR} )
-  set ( SCREENSHOTS_DIR ${SPHINX_BUILD_DIR}/screenshots )
-  set ( DIAGRAMS_DIR ${SPHINX_BUILD_DIR}/diagrams )
-  set ( DOT_EXECUTABLE ${DOXYGEN_DOT_EXECUTABLE} )
-
-  # targets
-  set ( TARGET_PREFIX docs)
-  # runner
-  if ( APPLE )
-    set ( DOCS_RUNNER_EXE ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}/${BIN_DIR}/MantidPlot )
-  elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
-    set ( DOCS_RUNNER_EXE ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}/launch_mantidplot.sh )
-  else ()
-    set ( DOCS_RUNNER_EXE ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}/MantidPlot )
-  endif()
-
-  # qthelp target
-  if (QT_QCOLLECTIONGENERATOR_EXECUTABLE)
-    set ( BUILDER qthelp )
-    configure_file ( runsphinx.py.in runsphinx_qthelp.py @ONLY )
-
-    add_custom_command ( OUTPUT qthelp/MantidProject.qhcp
-                                qthelp/MantidProject.qhp
-                       COMMAND ${DOCS_RUNNER_EXE} -xq runsphinx_qthelp.py
-                       DEPENDS Framework AllQt4 ${CMAKE_CURRENT_BINARY_DIR}/runsphinx_qthelp.py
-                       COMMENT "Building qt-assistant index files")
-
-    add_custom_command ( OUTPUT qthelp/MantidProject.qhc
-                        COMMAND ${QT_QCOLLECTIONGENERATOR_EXECUTABLE} qthelp/MantidProject.qhcp
-                        DEPENDS qthelp/MantidProject.qhcp
-                        COMMENT "Compiling Qt help documentation")
-
-    add_custom_target ( ${TARGET_PREFIX}-qthelp
-                        DEPENDS qthelp/MantidProject.qhc
-                        ${SPHINX_CONF_DIR}/conf.py ${SPHINX_CONF_DIR}/conf-qthelp.py )
-
-    # Group within VS and exclude from whole build
-    set_target_properties ( ${TARGET_PREFIX}-qthelp PROPERTIES FOLDER "Documentation"
-                           EXCLUDE_FROM_DEFAULT_BUILD 1
-                           EXCLUDE_FROM_ALL 1)
-
-  else (QT_QCOLLECTIONGENERATOR_EXECUTABLE)
-    message (WARNING " Did not find qcollectiongenerator - cannot create Qt help files")
-  endif ( QT_QCOLLECTIONGENERATOR_EXECUTABLE )
 
-  # HTML target
-  option (DOCS_HTML "Add target for building html docs" ON)
-  if (DOCS_HTML)
-    set ( BUILDER html )
-    configure_file ( runsphinx.py.in runsphinx_html.py @ONLY )
-    add_custom_target ( ${TARGET_PREFIX}-html
-                       COMMAND ${DOCS_RUNNER_EXE} -xq runsphinx_html.py
-                       DEPENDS Framework AllQt4 ${CMAKE_CURRENT_BINARY_DIR}/runsphinx_html.py
-                               ${SPHINX_CONF_DIR}/conf.py ${SPHINX_CONF_DIR}/conf-html.py
-                       COMMENT "Building html documentation"
-                       )
-    # Group within VS and exclude from whole build
-    set_target_properties ( ${TARGET_PREFIX}-html PROPERTIES FOLDER "Documentation"
-                           EXCLUDE_FROM_DEFAULT_BUILD 1
-                           EXCLUDE_FROM_ALL 1)
-  endif (DOCS_HTML)
-  
-  
-  # EPUB target
-  option (DOCS_EPUB "Add target for building epub docs" OFF)
-  if (DOCS_EPUB)
-    set ( BUILDER epub )
-    configure_file ( runsphinx.py.in runsphinx_epub.py @ONLY )
-    add_custom_target ( ${TARGET_PREFIX}-epub
-                       COMMAND ${DOCS_RUNNER_EXE} -xq runsphinx_epub.py
-                       DEPENDS Framework AllQt4 ${CMAKE_CURRENT_BINARY_DIR}/runsphinx_epub.py
-                               ${SPHINX_CONF_DIR}/conf.py
-                       COMMENT "Building html documentation"
-                       )
-    # Group within VS and exclude from whole build
-    set_target_properties ( ${TARGET_PREFIX}-epub PROPERTIES FOLDER "Documentation"
-                           EXCLUDE_FROM_DEFAULT_BUILD 1
-                           EXCLUDE_FROM_ALL 1)
-  endif (DOCS_EPUB)
-
-  
-
-  # LINKCHECK target
-  set ( BUILDER linkcheck )
-  configure_file ( runsphinx.py.in runsphinx_linkcheck.py @ONLY )
-  add_custom_target ( ${TARGET_PREFIX}-linkcheck
-                      COMMAND ${DOCS_RUNNER_EXE} -xq runsphinx_linkcheck.py
-                      DEPENDS Framework AllQt4 ${CMAKE_CURRENT_BINARY_DIR}/runsphinx_linkcheck.py
-                              ${SPHINX_CONF_DIR}/conf.py
-                      COMMENT "Checking documentation links"
-                      )
+# We generate a target per build type, i.e docs-html
+set ( SPHINX_CONF_DIR ${CMAKE_CURRENT_SOURCE_DIR}/source )
+set ( SPHINX_BUILD_DIR ${DOCS_BUILDDIR} )
+set ( SCREENSHOTS_DIR ${SPHINX_BUILD_DIR}/screenshots )
+set ( DIAGRAMS_DIR ${SPHINX_BUILD_DIR}/diagrams )
+set ( DOT_EXECUTABLE ${DOXYGEN_DOT_EXECUTABLE} )
+
+# targets
+set ( TARGET_PREFIX docs)
+# runner
+if ( APPLE )
+  set ( DOCS_RUNNER_EXE ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}/${BIN_DIR}/MantidPlot )
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set ( DOCS_RUNNER_EXE ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}/launch_mantidplot.sh )
+else ()
+  set ( DOCS_RUNNER_EXE ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}/MantidPlot )
+endif()
+
+# qthelp target
+if (QT_QCOLLECTIONGENERATOR_EXECUTABLE)
+  set ( BUILDER qthelp )
+  configure_file ( runsphinx.py.in runsphinx_qthelp.py @ONLY )
+
+  add_custom_command ( OUTPUT qthelp/MantidProject.qhcp
+                              qthelp/MantidProject.qhp
+                     COMMAND ${DOCS_RUNNER_EXE} -xq runsphinx_qthelp.py
+                     DEPENDS Framework AllQt4 ${CMAKE_CURRENT_BINARY_DIR}/runsphinx_qthelp.py
+                     COMMENT "Building qt-assistant index files")
+
+  add_custom_command ( OUTPUT qthelp/MantidProject.qhc
+                      COMMAND ${QT_QCOLLECTIONGENERATOR_EXECUTABLE} qthelp/MantidProject.qhcp
+                      DEPENDS qthelp/MantidProject.qhcp
+                      COMMENT "Compiling Qt help documentation")
+
+  add_custom_target ( ${TARGET_PREFIX}-qthelp
+                      DEPENDS qthelp/MantidProject.qhc
+                      ${SPHINX_CONF_DIR}/conf.py ${SPHINX_CONF_DIR}/conf-qthelp.py )
+
   # Group within VS and exclude from whole build
-  set_target_properties ( ${TARGET_PREFIX}-linkcheck PROPERTIES FOLDER "Documentation"
-                          EXCLUDE_FROM_DEFAULT_BUILD 1
-                          EXCLUDE_FROM_ALL 1)
-
-  ###############################################################################
-  # Tests ( It would be nice to have this is a test sub directory but the
-  #        configure of the runsphinx file doesn't like being in two directories.
-  #        Ninja complains about multiple targets creating runsphinx.py.in)
-  ###############################################################################
-
-  ###############################################################################
-  # Overall doctest target that executes in MantidPlot
-  ###############################################################################
-  set ( BUILDER doctest )
-  configure_file ( runsphinx.py.in runsphinx_doctest.py @ONLY )
-  add_custom_target ( ${TARGET_PREFIX}-test
-    COMMAND ${DOCS_RUNNER_EXE} -xq runsphinx_doctest.py
-    DEPENDS Framework AllQt4 ${CMAKE_CURRENT_BINARY_DIR}/runsphinx_doctest.py
-            ${SPHINX_CONF_DIR}/conf.py
-    COMMENT "Running documentation tests"
-  )
+  set_target_properties ( ${TARGET_PREFIX}-qthelp PROPERTIES FOLDER "Documentation"
+                         EXCLUDE_FROM_DEFAULT_BUILD 1
+                         EXCLUDE_FROM_ALL 1)
+
+else (QT_QCOLLECTIONGENERATOR_EXECUTABLE)
+  message (WARNING " Did not find qcollectiongenerator - cannot create Qt help files")
+endif ( QT_QCOLLECTIONGENERATOR_EXECUTABLE )
+
+# HTML target
+option (DOCS_HTML "Add target for building html docs" ON)
+if (DOCS_HTML)
+  set ( BUILDER html )
+  configure_file ( runsphinx.py.in runsphinx_html.py @ONLY )
+  add_custom_target ( ${TARGET_PREFIX}-html
+                     COMMAND ${DOCS_RUNNER_EXE} -xq runsphinx_html.py
+                     DEPENDS Framework AllQt4 ${CMAKE_CURRENT_BINARY_DIR}/runsphinx_html.py
+                             ${SPHINX_CONF_DIR}/conf.py ${SPHINX_CONF_DIR}/conf-html.py
+                     COMMENT "Building html documentation"
+                     )
   # Group within VS and exclude from whole build
-  set_target_properties ( ${TARGET_PREFIX}-test PROPERTIES FOLDER "Documentation"
-                                              EXCLUDE_FROM_DEFAULT_BUILD 1
-                                              EXCLUDE_FROM_ALL 1)
-
-  ###############################################################################
-  # Installation settings
-  ###############################################################################
-  # Allow control over whether the documentation is packaged
-  set ( PACKAGE_DOCS False CACHE BOOL
-        "If true the html documentation is installed in the share/doc directory of the package" )
-
-  if ( PACKAGE_DOCS )
-    set ( HTML_DOCS_DEST share/doc )
-    if ( QT_QCOLLECTIONGENERATOR_EXECUTABLE )
-     # must "make docs-qthelp" before "make package" otherwise there will be a build failure
-     install (FILES ${SPHINX_BUILD_DIR}/qthelp/MantidProject.qhc
-                    ${SPHINX_BUILD_DIR}/qthelp/MantidProject.qch
-              DESTINATION ${INBUNDLE}${HTML_DOCS_DEST} )
-     install ( DIRECTORY  ${SPHINX_BUILD_DIR}/qthelp/
-              DESTINATION ${INBUNDLE}${HTML_DOCS_DEST}
-              FILES_MATCHING PATTERN "*.html" )
-     install ( DIRECTORY  ${SPHINX_BUILD_DIR}/qthelp/_images/
-                          ${SPHINX_BUILD_DIR}/qthelp/_static/
-              DESTINATION ${INBUNDLE}${HTML_DOCS_DEST} )
-    else( QT_QCOLLECTIONGENERATOR_EXECUTABLE)
-      install ( DIRECTORY ${SPHINX_BUILD_DIR}/html/
-               DESTINATION ${INBUNDLE}${HTML_DOCS_DEST} )
-    endif ( QT_QCOLLECTIONGENERATOR_EXECUTABLE )
-  endif ( PACKAGE_DOCS )
-else ( SPHINX_FOUND )
-  message ( WARNING "Sphinx package (version >= 1.2) not found, unable to build documentation." )
-endif ( SPHINX_FOUND )
+  set_target_properties ( ${TARGET_PREFIX}-html PROPERTIES FOLDER "Documentation"
+                         EXCLUDE_FROM_DEFAULT_BUILD 1
+                         EXCLUDE_FROM_ALL 1)
+endif (DOCS_HTML)
+
+
+# EPUB target
+option (DOCS_EPUB "Add target for building epub docs" OFF)
+if (DOCS_EPUB)
+  set ( BUILDER epub )
+  configure_file ( runsphinx.py.in runsphinx_epub.py @ONLY )
+  add_custom_target ( ${TARGET_PREFIX}-epub
+                     COMMAND ${DOCS_RUNNER_EXE} -xq runsphinx_epub.py
+                     DEPENDS Framework AllQt4 ${CMAKE_CURRENT_BINARY_DIR}/runsphinx_epub.py
+                             ${SPHINX_CONF_DIR}/conf.py
+                     COMMENT "Building html documentation"
+                     )
+  # Group within VS and exclude from whole build
+  set_target_properties ( ${TARGET_PREFIX}-epub PROPERTIES FOLDER "Documentation"
+                         EXCLUDE_FROM_DEFAULT_BUILD 1
+                         EXCLUDE_FROM_ALL 1)
+endif (DOCS_EPUB)
+
+
+
+# LINKCHECK target
+set ( BUILDER linkcheck )
+configure_file ( runsphinx.py.in runsphinx_linkcheck.py @ONLY )
+add_custom_target ( ${TARGET_PREFIX}-linkcheck
+                    COMMAND ${DOCS_RUNNER_EXE} -xq runsphinx_linkcheck.py
+                    DEPENDS Framework AllQt4 ${CMAKE_CURRENT_BINARY_DIR}/runsphinx_linkcheck.py
+                            ${SPHINX_CONF_DIR}/conf.py
+                    COMMENT "Checking documentation links"
+                    )
+# Group within VS and exclude from whole build
+set_target_properties ( ${TARGET_PREFIX}-linkcheck PROPERTIES FOLDER "Documentation"
+                        EXCLUDE_FROM_DEFAULT_BUILD 1
+                        EXCLUDE_FROM_ALL 1)
+
+###############################################################################
+# Tests ( It would be nice to have this is a test sub directory but the
+#        configure of the runsphinx file doesn't like being in two directories.
+#        Ninja complains about multiple targets creating runsphinx.py.in)
+###############################################################################
+
+###############################################################################
+# Overall doctest target that executes in MantidPlot
+###############################################################################
+set ( BUILDER doctest )
+configure_file ( runsphinx.py.in runsphinx_doctest.py @ONLY )
+add_custom_target ( ${TARGET_PREFIX}-test
+  COMMAND ${DOCS_RUNNER_EXE} -xq runsphinx_doctest.py
+  DEPENDS Framework AllQt4 ${CMAKE_CURRENT_BINARY_DIR}/runsphinx_doctest.py
+          ${SPHINX_CONF_DIR}/conf.py
+  COMMENT "Running documentation tests"
+)
+# Group within VS and exclude from whole build
+set_target_properties ( ${TARGET_PREFIX}-test PROPERTIES FOLDER "Documentation"
+                                            EXCLUDE_FROM_DEFAULT_BUILD 1
+                                            EXCLUDE_FROM_ALL 1)
+
+###############################################################################
+# Installation settings
+###############################################################################
+# Allow control over whether the documentation is packaged
+set ( PACKAGE_DOCS False CACHE BOOL
+      "If true the html documentation is installed in the share/doc directory of the package" )
+
+if ( PACKAGE_DOCS )
+  set ( HTML_DOCS_DEST share/doc )
+  if ( QT_QCOLLECTIONGENERATOR_EXECUTABLE )
+   # must "make docs-qthelp" before "make package" otherwise there will be a build failure
+   install (FILES ${SPHINX_BUILD_DIR}/qthelp/MantidProject.qhc
+                  ${SPHINX_BUILD_DIR}/qthelp/MantidProject.qch
+            DESTINATION ${INBUNDLE}${HTML_DOCS_DEST} )
+   install ( DIRECTORY  ${SPHINX_BUILD_DIR}/qthelp/
+            DESTINATION ${INBUNDLE}${HTML_DOCS_DEST}
+            FILES_MATCHING PATTERN "*.html" )
+   install ( DIRECTORY  ${SPHINX_BUILD_DIR}/qthelp/_images/
+                        ${SPHINX_BUILD_DIR}/qthelp/_static/
+            DESTINATION ${INBUNDLE}${HTML_DOCS_DEST} )
+  else( QT_QCOLLECTIONGENERATOR_EXECUTABLE)
+    install ( DIRECTORY ${SPHINX_BUILD_DIR}/html/
+             DESTINATION ${INBUNDLE}${HTML_DOCS_DEST} )
+  endif ( QT_QCOLLECTIONGENERATOR_EXECUTABLE )
+endif ( PACKAGE_DOCS )
diff --git a/docs/source/algorithms/ApplyDetectorScanEffCorr-v1.rst b/docs/source/algorithms/ApplyDetectorScanEffCorr-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..e458f48465933ddb46fedd9091667162b6d309dc
--- /dev/null
+++ b/docs/source/algorithms/ApplyDetectorScanEffCorr-v1.rst
@@ -0,0 +1,31 @@
+.. algorithm::
+
+.. summary::
+
+.. alias::
+
+.. properties::
+
+Description
+-----------
+
+This algorithm applies a calibration file to a workspace with a detector scan. This is currently used to support powder
+diffractometers D2B and D20 at the ILL, but could be used for other detector scan instruments too. This can also be used
+to apply a calibration to a non-detector scan instrument, but in this case the algorithm is just the multiplication of
+the two workspaces.
+
+The calibration file would normally be generated by using the algorithm
+:ref:`PowderDiffILLDetEffCorr <algm-PowderDiffILLDetEffCorr>`. The structure is a single column with an entry for each
+detector.
+
+The data to apply the calibration to would normally be loaded by
+:ref:`LoadILLDiffraction <algm-LoadILLDiffraction>`. The structure expected is a spectra for each time index for the
+first detector, followed by a spectra for each time index for the next detector and so on. This includes the monitors,
+which must be the first entries in the workspace, and which have no ntries in the calibration file.
+
+The calibration currently files have no errors associated, so the error in the input workspace is scaled by the
+calibration factor.
+
+.. categories::
+
+.. sourcelink::
diff --git a/docs/source/algorithms/CalculateDIFC-v1.rst b/docs/source/algorithms/CalculateDIFC-v1.rst
index fa6df9c66a9ae2bd6b56e550e0d54c83fa70833c..6191fad999410a6355d750ef3f6e15b00001f69f 100644
--- a/docs/source/algorithms/CalculateDIFC-v1.rst
+++ b/docs/source/algorithms/CalculateDIFC-v1.rst
@@ -22,6 +22,10 @@ This is used in the equation
 This algorithm uses the same underlying calculation as :ref:`algm-ConvertUnits`
 and :ref:`algm-AlignDetectors`.
 
+When specifying the ``CalibrationWorkspace``, this algorithm uses the
+values in the ``detid`` and ``difc`` columns of the calibration
+only. It ignores any other calibration constants.
+
 Assumptions: There are no assumptions and this algorithm works on the results
 of :ref:`algm-LoadEmptyInstrument`.
 
@@ -46,6 +50,17 @@ Output:
   The output workspace has 101376 spectra
   DIFC of pixel 100 is 1228
 
+**Determining traditional offsets**
+
+.. code-block:: python
+
+   ws = LoadEmptyInstrument(Filename="NOMAD_Definition.xml", OutputWorkspace="ws")
+   LoadDiffCal(InputWorkspace=ws, Filename='NOM_cal.h5', WorkspaceName='NOM')
+   uncalibrated = CalculateDIFC(ws, OutputWorkspace="uncalibrated")
+   calibrated = CalculateDIFC(ws, CalibrationWorkspace="NOM_cal", OutputWorkspace="calibrated")
+
+   offsets = calibrated/uncalibrated - 1.
+
 .. categories::
 
 .. sourcelink::
diff --git a/docs/source/algorithms/ConjoinXRuns-v1.rst b/docs/source/algorithms/ConjoinXRuns-v1.rst
index 70ecdef91d9c0eace2fa77d5becd15b319c67750..28b2c8b7803cd91ccd3505198b3ce595f8cdc69e 100644
--- a/docs/source/algorithms/ConjoinXRuns-v1.rst
+++ b/docs/source/algorithms/ConjoinXRuns-v1.rst
@@ -19,6 +19,7 @@ This can be a mixed list of workspaces and workspace groups on AnalysisDataServi
 - the same instrument
 - the same number of histograms
 - the same units
+- each workspace must have the same amount of points per spectrum, however the x-axes may differ
 
 SampleLogAsXAxis
 ----------------
@@ -48,7 +49,7 @@ Usage
 **Example - ConjoinXRuns**
 
 .. testcode:: ConjoinXRunsExample
-   
+
     # Create input workspaces
     list = []
     for i in range(3):
@@ -168,4 +169,3 @@ Related Algorithms
 .. categories::
 
 .. sourcelink::
-
diff --git a/docs/source/algorithms/LoadNXSPE-v1.rst b/docs/source/algorithms/LoadNXSPE-v1.rst
index ea892f2fa363604dedbb8f8d1d54d314edc4f880..97930210ae72d5261b839d527c36ccc73adebe1a 100644
--- a/docs/source/algorithms/LoadNXSPE-v1.rst
+++ b/docs/source/algorithms/LoadNXSPE-v1.rst
@@ -19,6 +19,13 @@ For indirect data, we recommend to save the data in NXS format instead.
 **NOTE:** In the current implementation, the rendering of the NXSPE
 instrument is VERY memory intensive.
 
+**NOTE:** This algorithm also sets to true the `distribution` flag on the output 
+workspace. This is because all examples of workspaces saved to `NXSPE` format
+by the reduction algorithms are distributions (signal is count rate and should
+be multiplied by bin widths to get counts). :ref:`algm-SaveNXSPE` does not
+require its input is a distribution, however, and the `NXSPE` format does not
+have a distribution flag.
+
 Usage
 -----
 
diff --git a/docs/source/algorithms/MuonMaxent-v1.rst b/docs/source/algorithms/MuonMaxent-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..5ea956f2d0d6eefaf68d3e59fb4a539aa6d68c56
--- /dev/null
+++ b/docs/source/algorithms/MuonMaxent-v1.rst
@@ -0,0 +1,67 @@
+.. algorithm::
+
+.. summary::
+
+.. alias::
+
+.. properties::
+
+Description
+-----------
+
+This algorithm calculates a single frequency spectrum from the time domain spectra recorded by multiple groups/detectors. 
+
+The time domian data :math:`D_k(t)`, where :math:`t` is time and :math:`k` is the spectrum number, has associated errors :math:`E_k(t)`. If the number of points chosen is greater than the number of time domain data points then extra points are
+added with infinite errors. The time domain data prior to :code:`FirstGoodTime` also have their errors set to infinity. The algorithm will produce the frequency spectra :math:`f(\omega)` and this is assumed to be real and positive. 
+The upper limit of the frequency spectra is determined by :code:`MaxField`. The maximum frequency, :math:`\omega_\mathrm{max}` can be less than the Nyquist limit :math:`\frac{\pi}{\delta T}` if the instrumental frequency response function for 
+:math:`\omega>\omega_\mathrm{max}` is approximatley zero. The inital estimate of the frequency spectrum is flat. 
+
+The algorithm calculates an estimate of each time domain spectra, :math:`g_k(t)` by the equation
+
+.. math::  g_k(t)=(1+A_k \Re(\mathrm{IFFT}(f(\omega) R(\omega))\exp(-j\phi_k) ) ),
+
+where :math:`\Re(z)` is the real part of :math:`z`, :math:`\mathrm{IFFT}` is the inverse fast Fourier transform (as defined by `numpy
+<https://docs.scipy.org/doc/numpy-1.12.0/reference/routines.fft.html#module-numpy.fft>`_), :math:`\phi_k` is the phase and :math:`A_k` is the asymmetry of the of the  :math:`k^\mathrm{th}` spectrum. 
+The asymmetry is normalised such that :math:`\sum_k A_k = 1`. 
+The instrumental frequency response function, :math:`R(\omega)`, is  is in general complex (due to a 
+non-symmetric pulse shape) and is the same for all spectra. The values of the phases and asymmetries are fitted in the outer loop of the algorithm. 
+
+The :math:`\chi^2` value is calculated via the equation
+
+.. math:: \chi^2 = F\frac{\sum_{k,t} (D_k(t)-g_k(t))^2 }{E_k(t)^2},
+
+where :math:`F` is the :code:`Factor` and is of order 1.0 (but can be adjusted by the user at the start of the algorithm for a better fit). 
+The entropy is given by
+
+.. math:: S = - \sum_\omega f(\omega) \log\left(\frac{f(\omega)}{A}\right),
+
+where :math:`A` is the :code:`DefaultLevel`; it is a parameter of the entropy function. It has a number of names in the literature, one of which
+is default-value since the maximum entropy solution with no data is :math:`f(\omega)=A` for all :math:`\omega`. The algorithm maximises
+:math:`S-\chi^2` and it is seen from the definition of :code:`Factor` above that this algorithm property acts a Lagrange multiplier, i.e. controlling the value :math:`\chi^2` converges to.
+
+
+Usage
+-----
+
+.. testcode::
+
+  # load data
+  Load(Filename='MUSR00022725.nxs', OutputWorkspace='MUSR00022725')
+  # estimate phases
+  CalMuonDetectorPhases(InputWorkspace='MUSR00022725', FirstGoodData=0.10000000000000001, LastGoodData=16, DetectorTable='phases', DataFitted='fitted', ForwardSpectra='9-16,57-64', BackwardSpectra='25-32,41-48')
+  MuonMaxent(InputWorkspace='MUSR00022725', InputPhaseTable='phases', Npts='16384', OuterIterations='9', InnerIterations='12', DefaultLevel=0.11, Factor=1.03, OutputWorkspace='freq', OutputPhaseTable='phasesOut', ReconstructedSpectra='time')
+  # get data
+  freq = AnalysisDataService.retrieve("freq")
+  print('frequency values {:.3f} {:.3f} {:.3f} {:.3f} {:.3f}'.format(freq.readY(0)[5], freq.readY(0)[690],freq.readY(0)[700], freq.readY(0)[710],freq.readY(0)[900]))
+
+Output
+######
+
+.. testoutput::
+
+  frequency values 0.110 0.789 0.871 0.821 0.105
+
+.. categories::
+
+.. sourcelink::
+
diff --git a/docs/source/algorithms/PolarizationEfficiencyCor-v1.rst b/docs/source/algorithms/PolarizationEfficiencyCor-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..6768e8f5fdd09bbcc4a2a7c1fb4a63c2224cc9bd
--- /dev/null
+++ b/docs/source/algorithms/PolarizationEfficiencyCor-v1.rst
@@ -0,0 +1,160 @@
+
+.. algorithm::
+
+.. summary::
+
+.. alias::
+
+.. properties::
+
+Description
+-----------
+
+This algorithm corrects for non-ideal instrument component efficiencies in a polarization analysis experiment by following the procedure and conventions introduced by Wildes [#WILDES]_. In the full polarization analysis case it solves the corrected count rates :math:`\Sigma^{++}`, :math:`\Sigma^{+-}`, :math:`\Sigma^{-+}` and :math:`\Sigma^{--}` from the equation
+
+.. math::
+   \begin{bmatrix}
+   \Sigma^{++} \\
+   \Sigma^{+-} \\
+   \Sigma^{-+} \\
+   \Sigma^{--}
+   \end{bmatrix}
+   = \bm{M}
+   \begin{bmatrix}
+   I^{00} \\
+   I^{01} \\
+   I^{10} \\
+   I^{11}
+   \end{bmatrix},
+
+where :math:`I^{jk}` are the experimental count rates for flipper configuration :math:`jk` and :math:`\bm{M}` is the four-by-four correction matrix as defined by equations (4) in [#WILDES]_.
+
+Flipper configurations
+######################
+
+*InputWorkspaces* is a list containing one to four workspace names (X unit: wavelength) corresponding to the instrument configurations given as *Flippers*. Supported configurations are:
+
+:literal:`'00, 01, 10, 11'`
+   Full polarization corrections. Four input workspaces are required. They should be in the input group in the following order: both flippers off, polarizer flipper on, analyzer flipper on, both flippers on.
+
+:literal:`'00, 01, 11'` and :literal:`'00, 10, 11'`
+   Polarization corrections with the assumption that the corrected count rates :math:`\Sigma^{+-} = \Sigma^{-+}`. In this case the intensity of the missing flipper configuration (01 or 10) can be solved from the other intensities. Workspaces in the input group should be in the following order: both flippers off, one flipper on, both flippers on.
+
+:literal:`'00, 11'`
+   Polarization corrections with the assumption that the corrected count rates :math:`\Sigma^{+-} = \Sigma^{-+} = 0`. In this case the intensities of the missing flipper configurations (01 and 11) can be solved from the other intensities. Workspaces in the input group should be in the following order: both flippers off, both flippers on.
+
+:literal:`'0, 1'`
+   Polarization corrections when no analyzer has been used. Workspaces in the input group should be in the following order: polarizer flipper off, polarizer flipper on.
+
+:literal:`'0'`
+   Polarization corrections for a direct beam measurement in a reflectometry experiment.
+
+Output
+######
+
+The algorithm's output is a group workspace containing the corrected workspaces. The names of each corrected workspace is prefixed by :literal:`_++`, :literal:`_+-`, :literal:`_-+` or :literal:`_--` depending on which :math:`\Sigma^{mn}` they correspond to.
+
+Efficiency factors
+##################
+
+The *Efficiencies* input property expects to get a workspace with the following properties:
+
+* Contains four histograms, each labeled by their vertical axis as :literal:`P1`, :literal:`P2`, :literal:`F1`, :literal:`F2`. Other histograms (if present) are ignored.
+* The Y values of each histogram should be the corresponding efficiencies as functions of wavelength as defined in [#WILDES]_.
+* The wavelength values (X values) should be the same is in the input workspaces.
+
+.. note::
+   Users at ILL can load a conforming efficiency workspace from disk by :ref:`algm-LoadILLPolarizationFactors`.
+
+Error propagation
+#################
+
+.. note::
+   Errors are calculated as per Wildes [#WILDES]_, except for the numerically solved intensity in :literal:`'00, 01, 11'` and :literal:`'00, 10, 11'` flipper configurations in which case the uncertainties of :math:`\Sigma^{+-}` or :math:`\Sigma^{-+}` are set to zero.
+
+Usage
+-----
+
+.. include:: ../usagedata-note.txt
+
+**Example - PolarizationEfficiencyCor**
+
+.. testcode:: PolarizationEfficiencyCorExample
+
+   LoadILLReflectometry(
+       Filename='ILL/D17/317370.nxs',
+       OutputWorkspace='direct_beam',
+       OutputBeamPosition='direct_beam_position',
+       XUnit='TimeOfFlight')
+   LoadILLReflectometry(
+       Filename='ILL/D17/317370.nxs',
+       OutputWorkspace='reflected_beam',
+       DirectBeamPosition='direct_beam_position',
+       XUnit='TimeOfFlight')
+   # Sum pixels containing the reflected intensity
+   GroupDetectors(
+       InputWorkspace='reflected_beam',
+       OutputWorkspace='reflected_beam',
+       WorkspaceIndexList=[199, 200, 201, 202, 203, 204, 205])
+   ConvertUnits(
+       InputWorkspace='reflected_beam',
+       OutputWorkspace='reflected_beam',
+       Target='Wavelength',
+       EMode='Elastic')
+   # There are some unphysical wavelengths
+   CropWorkspace(
+       InputWorkspace='reflected_beam',
+       OutputWorkspace='reflected_beam',
+       XMin=0.)
+   # Fake two flipper configurations 
+   RenameWorkspace(
+       InputWorkspace='reflected_beam',
+       OutputWorkspace='up'
+   )
+   CloneWorkspace(
+       InputWorkspace='up',
+       OutputWorkspace='down'
+   )
+   Scale(
+       InputWorkspace='down',
+       OutputWorkspace='down',
+       Factor=0.1
+   )
+   LoadILLPolarizationFactors(
+       Filename='ILL/D17/PolarizationFactors.txt',
+       OutputWorkspace='efficiencies',
+       WavelengthReference='up')
+   PolarizationEfficiencyCor(
+       InputWorkspaces='up, down',
+       OutputWorkspace='corrected',
+       Efficiencies='efficiencies',
+       Flippers='00, 11')
+   
+   orig = mtd['up']
+   corr = mtd['corrected_++']
+   index = orig.binIndexOf(15.)
+   ratio_up = corr.readY(0)[index] / orig.readY(0)[index]
+   print("Ratio of corrected and original 'up' intensity at 15A: {:.4}".format(ratio_up))
+   orig = mtd['down']
+   corr = mtd['corrected_--']
+   index = orig.binIndexOf(15.)
+   ratio_down = corr.readY(0)[index] / orig.readY(0)[index]
+   print("Ratio of corrected and original 'down' intensity at 15A: {:.4}".format(ratio_down))
+
+Output:
+
+.. testoutput:: PolarizationEfficiencyCorExample
+
+   Ratio of corrected and original 'up' intensity at 15A: 1.038
+   Ratio of corrected and original 'down' intensity at 15A: 1.062
+
+References
+----------
+
+.. [#WILDES] A. R. Wildes, *Neutron News*, **17** 17 (2006)
+             `doi: 10.1080/10448630600668738 <https://doi.org/10.1080/10448630600668738>`_
+
+.. categories::
+
+.. sourcelink::
+
diff --git a/docs/source/algorithms/SaveNXSPE-v1.rst b/docs/source/algorithms/SaveNXSPE-v1.rst
index 23f1dd2ac87b97de50841180a8a2bee9d84a63f9..61dcc9d20b3e998928066bf282fd6a8ef1fc7d57 100644
--- a/docs/source/algorithms/SaveNXSPE-v1.rst
+++ b/docs/source/algorithms/SaveNXSPE-v1.rst
@@ -39,6 +39,7 @@ Usage
    out_ws = CreateSimulationWorkspace(Instrument="IRIS", BinParams="0,500,2000")
    out_ws.setY(0, numpy.array([10.0, 50.0, 30.0, 60.0]))
    AddSampleLog(out_ws, 'Ei', LogText='321', LogType='Number')
+   out_ws.setDistribution(True)
    
    file_path = os.path.join(config["defaultsave.directory"], "NXSPEData.nxspe")
    
@@ -72,6 +73,12 @@ Output:
    Initial and loaded workspaces comparison is: True
    Loaded workspace has attached incident energy Ei=321.0 and rotation angle Psi= 32.0deg
    
+Note that :ref:`algm-LoadNXSPE` automatically applies the `distribution` flag to the loaded workspace.
+This is because all examples of workspaces saved to `NXSPE` format by the reduction algorithms
+are distributions (signal is count rate and should be multiplied by bin widths to get counts).
+`SaveNXSPE` does not require its input is a distribution, however, and the `NXSPE` format does
+not have a distribution flag.
+
 
 .. categories::
 
diff --git a/docs/source/algorithms/SofQWNormalisedPolygon-v1.rst b/docs/source/algorithms/SofQWNormalisedPolygon-v1.rst
index c4bbb3ff6638f68e9e6a75f468570a572381b255..2ff556913a5b67d6010ca3f94d71516a4f9c437e 100644
--- a/docs/source/algorithms/SofQWNormalisedPolygon-v1.rst
+++ b/docs/source/algorithms/SofQWNormalisedPolygon-v1.rst
@@ -12,25 +12,49 @@ Description
 Converts a 2D workspace from :ref:`units <Unit Factory>` 
 of spectrum number/**energy transfer** 
 to the intensity as a function of **momentum transfer** :math:`Q` 
-and **energy transfer** :math:`\Delta E`. The rebinning is done as a 
-weighted sum of overlapping polygons with
-fractional area tracking. The result is stored in a new workspace type:
-**RebinnedOutput**. The new workspace presents the data as the
-fractional counts divided by the fractional area. The biggest
-consequence of this method is that in places where there are no counts
-and no acceptance (no fractional areas), **nan**\ -s will result.
-
+and **energy transfer** :math:`\Delta E`. 
+
+.. figure:: /images/RebinnedOutput.png
+   :align: center
+
+As shown in the figure, the input grid (pink-shaded parallelopiped,
+aligned in scattering angle and energy transfer) is not parallel to the
+output grid (square grid, aligned in :math:`Q` and energy). This means
+that the output bins will only ever partially overlap the input data. To
+account for this, the signal :math:`Y` and errors :math:`E` in the output
+bin is calculated as the sum of all the input bins which overlap the
+output bin, weighted by the fractional overlap area :math:`F_i`:
+
+.. math:: Y^{\mathrm{out}} = (\sum_i Y^{\mathrm{in}}_i F_i) / \sum_i F_i
+.. math:: E^{\mathrm{out}} = \sqrt{\sum_i (E^{\mathrm{in}}_i F_i)^2} / \sum_i F_i
+
+.. warning:: Note that because the output bins contain fractions of multiple
+   input bins, the errors calculated for each output bins are no longer
+   independent, and so cannot be combined in quadrature. This means that
+   rebinning, summing, or integrating the output of this algorithm will 
+   give *incorrect error values* because those Mantid algorithms use the
+   quadrature formular and assume independent bins. The *signal*, on the
+   other hand should still be correct on rebinning. Unary operations, such
+   as scaling the signal will not encounter this problem.
+   
 The algorithm operates in non-PSD mode by default. This means that all
 azimuthal angles and widths are forced to zero. PSD mode will determine
 the azimuthal angles and widths from the instrument geometry. This mode
-is activated by placing the following named parameter in a Parameter
+is activated by placing the following named parameter in the instrument definition 
 file: *detector-neighbour-offset*. The integer value of this parameter
 should be the number of pixels that separates two pixels at the same
-vertical position in adjacent tubes.
-
-
-See  :ref:`algm-SofQWCentre` for centre-point binning  or :ref:`algm-SofQWPolygon`
-for simpler and less precise but faster binning strategies.
+vertical position in adjacent tubes. Note that in both non-PSD and PSD
+modes, the scattering angle widths are determined from the detector
+geometry and may vary from detector to detector as defined by the
+instrument definition files.
+
+See :ref:`algm-SofQWCentre` for centre-point binning or :ref:`algm-SofQWPolygon`
+for simpler and less precise but faster binning strategies. The speed-up
+is from ignoring the azimuthal positions of the detectors (as for the non-PSD
+mode in this algorithm) but in addition, :ref:`algm-SofQWPolygon` treats 
+all detectors as being the same, and characterised by a single width in
+scattering angle. Thereafter, it weights the signal and error by the fractional
+overlap, but does not then scale the weighted sum by :math:`\sum_i F_i`.
 
 Usage
 -----
diff --git a/docs/source/algorithms/SofQWPolygon-v1.rst b/docs/source/algorithms/SofQWPolygon-v1.rst
index 17c4c3c0327dc1715ab315fec32006f97ec241bb..8028ef3cc859063671848def542efe96a2e6706d 100644
--- a/docs/source/algorithms/SofQWPolygon-v1.rst
+++ b/docs/source/algorithms/SofQWPolygon-v1.rst
@@ -14,9 +14,30 @@ of spectrum number/**energy transfer** to
 the intensity as a function of momentum transfer 
 :math:`Q` and energy transfer :math:`\Delta E`. 
 
-The rebinning is done as a weighted sum of overlapping polygons. See 
-:ref:`algm-SofQWCentre` for centre-point binning  or :ref:`algm-SofQWNormalisedPolygon` for
-more complex and precise (but slower) binning strategy.
+The rebinning is done as a weighted sum of overlapping polygons.
+The polygon in :math:`Q-\Delta E` space is calculated from the
+energy bin boundaries and the detector scattering angle :math:`2\theta`.
+The detectors (pixels) are assumed to be uniform, and characterised
+by a single angular width :math:`\Delta2\theta`. The signal and error
+of the rebinned data (in :math:`Q-\Delta E` space) is then the
+sum of the contributing pixels in each bin weighted by their fractional
+overlap area:
+
+.. math:: Y^{\mathrm{out}} = (\sum_i Y^{\mathrm{in}}_i F_i)
+.. math:: E^{\mathrm{out}} = \sqrt{\sum_i (E^{\mathrm{in}}_i F_i)^2}
+
+Unlike the more precise :ref:`algm-SofQWNormalisedPolygon`
+algorithm, the final counts is not weighted by the sum of fractional
+areas which means that the signal will be underestimated where the
+bins are smaller, for example at the edges of detectors.
+However, like the other algorithm, the output workspace has bins which
+are no longer independent. Thus subsequent rebinning (or integration)
+of the output of the algorithm will give incorrect error values.
+
+See :ref:`algm-SofQWCentre` for centre-point binning.
+Alternatively, see :ref:`algm-SofQWNormalisedPolygon` for a
+more complex and precise (but slower) binning strategy, where the actual
+detector shape is calculated to obtain the input polygon.
 
 Usage
 -----
diff --git a/docs/source/api/python/mantid/api/MultipleFileProperty.rst b/docs/source/api/python/mantid/api/MultipleFileProperty.rst
index ea3f3588339e7e8696110df8811c021bb96deee5..3c1f1656f333a5b27046e3f601ee5d9f7d321407 100644
--- a/docs/source/api/python/mantid/api/MultipleFileProperty.rst
+++ b/docs/source/api/python/mantid/api/MultipleFileProperty.rst
@@ -25,21 +25,21 @@ The syntax for multi file loading involves the use of several
 context-sensitive operators.  Here is a run-down of those operators
 with some simple examples:
 
-+--------------------+-----------------------------+--------------------------------------------------------------------------------------------+-------------------+--------------------------------------+
-| Name               | Usage                       | Description                                                                                | Example Input     | Example Result                       |
-+====================+=============================+============================================================================================+===================+======================================+
-| List               | ``<run>,<run>``             | Used to list runs                                                                          | ``INST1,2,3.ext`` | Load runs 1, 2 and 3                 |
-+--------------------+-----------------------------+--------------------------------------------------------------------------------------------+-------------------+--------------------------------------+
-| Plus               | ``<run>+<run>``             | Used to specify which runs that are to be loaded and then summed together                  | ``INST1+2+3.ext`` | Load and sum runs 1, 2 and 3         |
-+--------------------+-----------------------------+--------------------------------------------------------------------------------------------+-------------------+--------------------------------------+
-| Range              | ``<run>:<run>``             | Used to specify a range of runs to load                                                    | ``INST1:4.ext``   | Load runs 1, 2, 3 and 4              |
-+--------------------+-----------------------------+--------------------------------------------------------------------------------------------+-------------------+--------------------------------------+
-| Stepped Range      | ``<run>:<run>:<step_size>`` | Used to specify a ''stepped'' range of runs to load                                        | ``INST1:5:2.ext`` | Load runs 1, 3 and 5                 |
-+--------------------+-----------------------------+--------------------------------------------------------------------------------------------+-------------------+--------------------------------------+
-| Added Range        | ``<run>-<run>``             | Used to specify a range of runs that are to be loaded and then summed together             | ``INST1-4.ext``   | Load and sum runs 1, 2, 3 and 4      |
-+--------------------+-----------------------------+--------------------------------------------------------------------------------------------+-------------------+--------------------------------------+
-|Stepped Added Range | ``<run>-<run>:<step_size>`` | Used to specify a ''stepped'' range of runs that are to be loaded and then summed together | ``INST1-5:2.ext`` | Load and sum runs 1, 3 and 5         |
-+--------------------+-----------------------------+--------------------------------------------------------------------------------------------+-------------------+--------------------------------------+
++---------------------+-----------------------------+--------------------------------------------------------------------------------------------+-------------------+--------------------------------------+
+| Name                | Usage                       | Description                                                                                | Example Input     | Example Result                       |
++=====================+=============================+============================================================================================+===================+======================================+
+| List                | ``<run>,<run>``             | Used to list runs                                                                          | ``INST1,2,3.ext`` | Load runs 1, 2 and 3                 |
++---------------------+-----------------------------+--------------------------------------------------------------------------------------------+-------------------+--------------------------------------+
+| Plus                | ``<run>+<run>``             | Used to specify which runs that are to be loaded and then summed together                  | ``INST1+2+3.ext`` | Load and sum runs 1, 2 and 3         |
++---------------------+-----------------------------+--------------------------------------------------------------------------------------------+-------------------+--------------------------------------+
+| Range               | ``<run>:<run>``             | Used to specify a range of runs to load                                                    | ``INST1:4.ext``   | Load runs 1, 2, 3 and 4              |
++---------------------+-----------------------------+--------------------------------------------------------------------------------------------+-------------------+--------------------------------------+
+| Stepped Range       | ``<run>:<run>:<step_size>`` | Used to specify a ''stepped'' range of runs to load                                        | ``INST1:5:2.ext`` | Load runs 1, 3 and 5                 |
++---------------------+-----------------------------+--------------------------------------------------------------------------------------------+-------------------+--------------------------------------+
+| Added Range         | ``<run>-<run>``             | Used to specify a range of runs that are to be loaded and then summed together             | ``INST1-4.ext``   | Load and sum runs 1, 2, 3 and 4      |
++---------------------+-----------------------------+--------------------------------------------------------------------------------------------+-------------------+--------------------------------------+
+| Stepped Added Range | ``<run>-<run>:<step_size>`` | Used to specify a ''stepped'' range of runs that are to be loaded and then summed together | ``INST1-5:2.ext`` | Load and sum runs 1, 3 and 5         |
++---------------------+-----------------------------+--------------------------------------------------------------------------------------------+-------------------+--------------------------------------+
 
 Optional Info
 -------------
@@ -86,6 +86,11 @@ The basic syntax outlined above can be combined in a variety of ways:
     # For TOSCA, adds together run 1 (found in c:/files/) and run 2 (found in c:/store/).
     Load(Filename='c:/files/TSC1.raw+c:/store/TSC2.raw', OutputWorkspace='Files')
 
+    # For TOSCA, adds together runs 1, 4, 5 and 6.
+    Load(Filename='TSC1+4-5.raw', OutputWorkspace='Files')
+
+    # For IRIS, adds together runs 1, 2, 3, 7, 8 and 9.
+    Load(Filename='IRS1-3+7-9.raw', OutputWorkspace='Files')
 
 Load Dialog
 ===========
diff --git a/docs/source/api/python/mantid/plots/index.rst b/docs/source/api/python/mantid/plots/index.rst
index 0fca0a4f7f1b663be5662d50b75cd792fcac660b..9d4a16dd103ca05f3f828a630f1a3a1919293adb 100644
--- a/docs/source/api/python/mantid/plots/index.rst
+++ b/docs/source/api/python/mantid/plots/index.rst
@@ -45,12 +45,12 @@ not all are used in all places.
    from matplotlib.colors import LogNorm
 
 First, load some diffraction data and see what the automatic axes will
-be using :func:`~mantid.plots.functions.getAxesLabels`.
+be using :func:`~mantid.plots.helperfunctions.get_axes_labels`.
 
 .. code-block:: python
 
    Load(Filename="PG3_733", OutputWorkspace="PG3_733")
-   print(plots.functions.getAxesLabels(mtd['PG3_733']))
+   print(plots.helperfunctions.get_axes_labels(mtd['PG3_733']))
 
 Which will print the ``y-label`` then the labels for all the other
 axes as properly escaped for use directly in
@@ -75,8 +75,8 @@ or without:
 .. code-block:: python
 
    fig, ax = plt.subplots()
-   plots.functions.plot(ax, mtd['PG3_733'], 'go-', specNum=1, label='user label')
-   plots.functions.errorbar(ax, mtd['PG3_733'],  wkspIndex=2)
+   plots.plotfunctions.plot(ax, mtd['PG3_733'], 'go-', specNum=1, label='user label')
+   plots.plotfunctions.errorbar(ax, mtd['PG3_733'],  wkspIndex=2)
    ax.legend()
    fig.show()
 
@@ -91,6 +91,28 @@ warned that every call to one of the plot functions will automatically
 annotate the axes with the last one called being the one that takes
 effect.
 
+The :func:`~mantid.plots.MantidAxes.plot` function also allows
+plotting sample logs.
+
+.. code-block:: python
+
+   from mantid import plots
+   import matplotlib.pyplot as plt
+   w = LoadEventNexus(Filename='CNCS_7860_event.nxs')
+   fig = plt.figure()
+   ax1 = fig.add_subplot(211, projection = 'mantid')
+   ax2 = fig.add_subplot(212, projection = 'mantid')
+   ax1.plot(w, LogName = 'ChopperStatus5')
+   ax1.set_title('From run start')
+   ax2.plot(w, LogName = 'ChopperStatus5', FullTime = True)
+   ax2.set_title('Absolute time')
+   fig.tight_layout()
+   fig.show()
+
+.. figure:: ../../../../images/mantid_plots_1Dlogs.png
+   :align: center
+   :figwidth: image
+
 Two common ways to look at 2D plots are :func:`~mantid.plots.MantidAxes.contourf` and
 :func:`~mantid.plots.MantidAxes.pcolormesh`. The difference between these is the
 :func:`~mantid.plots.MantidAxes.contourf` calculates smooth lines of constant
@@ -145,13 +167,28 @@ and :func:`~mantid.plots.MantidAxes.pcolorfast`:
   **axisaligned** behavior (cannot be overriden). :func:`~mantid.plots.MantidAxes.contour`
   and the like cannot plot these type of workspaces.
 
+In addition to the ``mantid`` projection, there is also the ``mantid3d`` projection for 3d plots.
+Can be used much the same as the ``mantid`` projection, but by instead specifying ``mantid3d``
+when giving the projection:
+
+.. code-block:: python
+
+    import matplotlib.pyplot as plt
+    from mantid import plots
+
+    #some code here to get a workspace, and x, y, yerr arrays
+
+    fig, ax = plt.subplots(subplot_kw={'projection':'mantid3d'})
+    ax.plot_wireframe(workspace)   #for workspaces
+    ax.plot_wireframe(x,y,z)       #for arrays
+    fig.show()
 
 Types of functions
 ==================
 
 **Informational**
 
-* :func:`~mantid.plots.functions.getAxesLabels`
+* :func:`~mantid.plots.helperfunctions.get_axes_labels`
 
 **1D Plotting**
 
@@ -170,6 +207,15 @@ Types of functions
 * :func:`~mantid.plots.MantidAxes.tricontour` - Draw contours at specified levels on an unstructured triangular grid
 * :func:`~mantid.plots.MantidAxes.tricontourf` - Draw contours at calculated levels on an unstructured triangular grid
 
+**3D Plotting**
+
+* :func:`~mantid.plots.MantidAxes3D.plot` - Draws a line plot in 3D space
+* :func:`~mantid.plots.MantidAxes3D.scatter` - Draws a scatter plot in 3d space
+* :func:`~mantid.plots.MantidAxes3D.plot_wireframe` - Draws a wire frame linking all adjacent data plots
+* :func:`~mantid.plots.MantidAxes3D.plot_surface` - Draws a surface linking all adjacent data points
+* :func:`~mantid.plots.MantidAxes3D.contour` - Draws contour lines at specified levels of the data
+* :func:`~mantid.plots.MantidAxes3D.contourf` - Draws filled contour lines at specified levels of the data
+
 matplotlib demonstrates the difference between uniform and nonuniform
 grids well in `this example
 <https://matplotlib.org/gallery/images_contours_and_fields/tricontour_vs_griddata.html#sphx-glr-gallery-images-contours-and-fields-tricontour-vs-griddata-py>`_
@@ -185,10 +231,29 @@ When using ``mantid`` projection
              contourf, pcolor, pcolorfast, pcolormesh, tripcolor,
              tricontour, tricontourf
 
+When using ``mantid3d`` projection
+----------------------------------
+
+.. autoclass:: mantid.plots.MantidAxes3D
+   :members: plot, scatter, plot_wireframe, plot_surface, contour,
+             contourf
+
 Functions to use when **mantid** projection is not available
 ------------------------------------------------------------
 
-.. automodule:: mantid.plots.functions
-   :members: getAxesLabels, plot, errorbar, scatter, contour,
-             contourf, pcolor, pcolorfast, pcolormesh, tripcolor,
-             tricontour, tricontourf
+.. automodule:: mantid.plots.plotfunctions
+   :members: plot, errorbar, scatter, contour, contourf, pcolor,
+             pcolorfast, pcolormesh, tripcolor, tricontour, tricontourf
+
+             
+Functions to use when **mantid3d** projection is not available
+--------------------------------------------------------------
+
+.. automodule:: mantid.plots.plotfunctions3D
+   :members: plot, scatter, plot_wireframe, plot_surface,
+             contour, contourf
+
+Helper functions
+----------------
+.. automodule:: mantid.plots.helperfunctions
+
diff --git a/docs/source/development/index.rst b/docs/source/development/index.rst
deleted file mode 100644
index 2ccdeafd4af4d483c8e747aa257a76fca8e03bb7..0000000000000000000000000000000000000000
--- a/docs/source/development/index.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-.. Concepts master file
-   It contains a hidden root toctree directive so that Sphinx
-   has an internal index of all of the pages and doesn't
-   produce a plethora of warnings about most documents not being in
-   a toctree.
-   See http://sphinx-doc.org/tutorial.html#defining-document-structure
-
-.. _development contents:
-
-=============
- Development
-=============
-
-.. toctree::
-   :hidden:
-   :glob:
-   :maxdepth: 1
-
-   *
diff --git a/docs/source/fitfunctions/CrystalFieldSusceptibility.rst b/docs/source/fitfunctions/CrystalFieldSusceptibility.rst
index 9820e65ea8f47e8696a077b2073d188cf923a77c..f06644e26df6d1765bfdc3857a0e222771559408 100644
--- a/docs/source/fitfunctions/CrystalFieldSusceptibility.rst
+++ b/docs/source/fitfunctions/CrystalFieldSusceptibility.rst
@@ -31,14 +31,15 @@ be along the quantisation axis of the crystal field (which is usually defined to
 :math:`B_x`, :math:`B_y`, and :math:`B_z` are the components of the unit vector pointing in the direction of the applied 
 magnetic field in this coordinate system.
 
-Finally, in order to account for the effect of any exchange interactions in the system which will shift the susceptibility curve
-up or down (analogous to the Curie-Weiss temperature), the actual magnetic susceptibility calculated by this function is:
+Finally, in order to account for the effect of any exchange interactions in the system which will shift the susceptiblity curve
+up or down (analogous to the Curie-Weiss temperature), and any residual (background) susceptibility in the sample (perhaps from
+an impurity), the actual magnetic susceptibility calculated by this function is:
 
-.. math:: \chi^{\mathrm{eff}} = \frac{\chi(T)}{1 - \lambda \chi(T)}
+.. math:: \chi^{\mathrm{eff}} = \frac{\chi(T)}{1 - \lambda \chi(T)} + \chi_0
 
-where :math:`\lambda` parameterises an effective exchange interaction and :math:`\chi` is the bare (paramagnetic Crystal Field)
-susceptibility. A negative :math:`\lambda` indicates overall antiferromagnetic interactions, whilst a positive :math:`\lambda`
-corresponds to overall ferromagnetic interactions.
+where :math:`\lambda` parameterises an effective exchange interaction with :math:`\chi` the bare (paramagnetic Crystal Field)
+susceptibility, and :math:`\chi_0` the residual susceptibility. A negative :math:`\lambda` indicates overall antiferromagnetic
+interactions, whilst a positive :math:`\lambda` corresponds to overall ferromagnetic interactions.
 
 Example
 -------
diff --git a/docs/source/images/MuonAnalysisSettingsGeneral.png b/docs/source/images/MuonAnalysisSettingsGeneral.png
index e9adde61e674b8366aca59daaa599aee6effa6a2..0b1534dbb2e2cd888fa5a1809c8266d1cb2d70ad 100644
Binary files a/docs/source/images/MuonAnalysisSettingsGeneral.png and b/docs/source/images/MuonAnalysisSettingsGeneral.png differ
diff --git a/docs/source/images/RebinnedOutput.png b/docs/source/images/RebinnedOutput.png
new file mode 100644
index 0000000000000000000000000000000000000000..39e1a7c688435941fc70623fb6faf712097f5c18
Binary files /dev/null and b/docs/source/images/RebinnedOutput.png differ
diff --git a/docs/source/images/mantid_plots_1Dlogs.png b/docs/source/images/mantid_plots_1Dlogs.png
new file mode 100644
index 0000000000000000000000000000000000000000..432e7e0a3b99e245ca738c26f35f9bebde7a8cfa
Binary files /dev/null and b/docs/source/images/mantid_plots_1Dlogs.png differ
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 46ce414828b41c635564775c77fbcfc68b5389c1..ff86cf51538db3da71893d3582861484213f1d48 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -23,7 +23,6 @@ Mantid Documentation
    algorithms/index
    algorithms/*
    concepts/index
-   development/index
    interfaces/index
    fitfunctions/*
    fitminimizers/index
@@ -52,5 +51,3 @@ This is the documentation for Mantid |release|.
     - `Python <api/python/index.html>`_
     - `C++ <http://doxygen.mantidproject.org/>`_ (Doxygen)
 * `Release Notes <release/index.html>`_
-
-
diff --git a/docs/source/interfaces/Crystal Field Python Interface.rst b/docs/source/interfaces/Crystal Field Python Interface.rst
index 461865af1b369b6d0a2ae94c0b93d8f7f2b09ec8..2dc1d1bf1a38283327bcbb37c8182965098d2b27 100644
--- a/docs/source/interfaces/Crystal Field Python Interface.rst	
+++ b/docs/source/interfaces/Crystal Field Python Interface.rst	
@@ -7,18 +7,18 @@ Crystal Field Python Interface
   :local:
 
 The python facilities for Crystal Field calculations are available in Mantid from module `CrystalField`.
-There are two main classes the module provides: `CrystalFiled` that defines various properties of a crystal
-field and `CrystalFieldFit` that manages the fitting process.
+The module provides two main classes: `CrystalField` defines various properties of a crystal field and
+`CrystalFieldFit` manages the fitting process.
 
 
 Setting up crystal field parameters
 -----------------------------------
 
 A crystal field computation starts with creating an instance of the `CrystalField` class. The constructor
-has two mandatory arguments: `Ion` - a symbolic name of the ion, and `Symmetry` - a name of the symmetry group
-of the field. The rest of the parameters are optional.
+has two mandatory arguments: `Ion` - the symbolic name of the ion, and `Symmetry` - the name of the point symmetry
+group of the field. The rest of the parameters are optional.
 
-Possible values for the `Ion` argument::
+Possible values for the `Ion` argument are::
 
  Ce, Pr, Nd, Pm, Sm, Eu, Gd, Tb, Dy, Ho, Er, Tm, Yb
 
@@ -30,7 +30,7 @@ or half-integer value, e.g. `Ion=S2` or `Ion=S1.5`. In these cases, the g-factor
 The prefix letter can also be `J` instead of `S`, and lower case letters are also supported. (e.g. `Ion=j1`,
 `Ion=s2.5` and `Ion=J0.5` are all valid).
  
-Allowed values for `Symmetry`::
+Allowed values for `Symmetry` are::
 
   C1, Ci, C2, Cs, C2h, C2v, D2, D2h, C4, S4, C4h, D4, C4v, D2d, D4h, C3,
   S6, D3, C3v, D3d, C6, C3h, C6h, D6, C6v, D3h, D6h, T, Td, Th, O, Oh
@@ -41,7 +41,7 @@ The minimum code to create a crystal field object is::
   cf = CrystalField('Ce', 'C2v')
   
 Names of the crystal field parameters have the form `Bnn` and `IBnn` where `nn` are two digits between 0 and 6.
-The `Bnn` is a real and the `IBnn` is an imaginary part of a complex parameter. If a parameter isn't set explicitly
+`Bnn` is the real and `IBnn` is the imaginary part of a complex parameter. If a parameter isn't set explicitly
 its default value is 0. To set a parameter pass it to the `CrystalField` constructor as a keyword argument, e.g.::
 
   cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770)
@@ -58,7 +58,7 @@ Which can also be used to query the value of a parameter::
 Calculating the Eigensystem
 ---------------------------
 
-`CrystalField` class has methods to calculate the Hamiltonian and its eigensystem::
+The `CrystalField` class has methods to calculate the Hamiltonian and its eigensystem::
 
   # Calculate and return the Hamiltonian matrix as a 2D numpy array.
   h = cf.getHamiltonian()
@@ -91,7 +91,7 @@ Knowing the temperature allows us to calculate a peak list: a list of transition
 
   print cf.getPeakList()
   
-The output is::
+Which produces the output::
 
  [[  0.00000000e+00   2.44006198e+01   4.24977124e+01   1.80970926e+01 -2.44006198e+01]
   [  2.16711565e+02   8.83098530e+01   5.04430056e+00   1.71153708e-01  1.41609425e-01]]
@@ -112,9 +112,9 @@ The new output::
  [[   0.           24.40061976   42.49771237]
   [ 216.71156467   88.30985303    5.04430056]]
   
-To calculate a spectrum we need to define a shape of each peak (peak profile function) and its default width (`FWHM`).
+To calculate a spectrum we need to define the shape of each peak (peak profile function) and its default width (`FWHM`).
 The width can be set either via a keyword argument or a property with name `FWHM`. If the peak shape isn't set the default
-of Lorentzian is assumed. To set a different shape use the `PeakShape` property::
+of `Lorentzian` is assumed. To set a different shape use the `PeakShape` property::
 
   cf.PeakShape = 'Gaussian'
   cf.FWHM = 0.9
@@ -128,8 +128,9 @@ After the peak shape is defined a spectrum can be calculated::
   
 The output is a tuple of two 1d numpy arrays (x, y) that can be used with `matplotlib` to plot::
 
-  pyplot.plot(*sp)
-  pyplot.show()
+  import matplotlib.pyplot as plt
+  plt.plot(*sp)
+  plt.show()
   
 .. image:: /images/CrystalFieldSpectrum1.png
    :height: 300
@@ -170,7 +171,13 @@ Plotting in MantidPlot
 ----------------------
 
 To plot a spectrum using MantidPlot's graphing facilities `CrystalField` has method `plot`. It has the same arguments as `getSpectrum`
-and opens a window with a plot.
+and opens a window with a plot, e.g.::
+
+  cf.plot()
+
+In addition to plotting, the `plot` method creates a workspace named `CrystalField_<Ion>` with the plot data. Subsequent calls to `plot` 
+for the same `CrystalField` object will use the same plot window as created by the first call unless this window has been closed in the 
+mean time.
 
 
 Adding a Background
@@ -194,7 +201,7 @@ Setting Ties and Constraints
 ----------------------------
 
 Setting ties and constraints are done by calling the `ties` and `constraints` methods of the `CrystalField` class or its components.
-To `Bnn` parameters are tied by the `CrystalField` class directly specifying the tied parameter as a keyword argument::
+The `Bnn` parameters are tied by the `CrystalField` class directly specifying the tied parameter as a keyword argument::
 
   cf.ties(B20=1.0, B40='B20/2')
   
@@ -238,14 +245,16 @@ which is equivalent to::
 Setting Resolution Model
 ------------------------
 
-Resolution model here is a way to constrain widths of the peaks to realistic numbers which agree with a measured or
+A resolution model is a way to constrain the widths of the peaks to realistic numbers which agree with a measured or
 calculated instrument resolution function. A model is a function that returns a FWHM for a peak centre. The Crystal
-Field python interface defines helper class `ResolutionModel` to help define and set resolution models.
+Field python interface defines the helper class `ResolutionModel` to help define and set resolution models.
 
 To construct an instance of `ResolutionModel` one needs to provide up to four input parameters. The first parameter, `model`, is
-mandatory and can be either of the two
+mandatory and can be either of:
 
-1. A tuple containing two arrays (lists) of real numbers which will be interpreted as tabulated values of the model function. The first element of the tuple is a list of increasing values for peak centres, and the second element is a list of corresponding widths. Values between the tabulated peak positions will be linearly interpolated.
+1. A tuple containing two arrays (lists) of real numbers which will be interpreted as tabulated values of the model function. 
+   The first element of the tuple is a list of increasing values for peak centres, and the second element is a list of corresponding
+   widths. Values between the tabulated peak positions will be linearly interpolated.
 
 2. A python function that takes a :class:`numpy.ndarray` of peak positions and returns a numpy array of widths.
 
@@ -254,21 +263,38 @@ function. `xstart` and `xend` define the interval of interpolation which must in
 :math:`10^{-4}` and defines an approximate desired accuracy of the approximation. The interval will be split until the largest error of the interpolation
 is smaller than `accuracy`. Note that subdivision cannot go on to infinity as the number of points is limited by the class member `ResolutionModel.max_model_size`.
 
-Example of setting a resolution model::
+Example of setting a resolution model using a tuple of two arrays::
 
+    from CrystalField import CrystalField, ResolutionModel
     rm = ResolutionModel(([1, 2, 3, ...., 100], [0.1, 0.3, 0.35, ..., 2.1]))
     cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, ..., Temperature=44.0, ResolutionModel=rm)
 
-    ...
+Or using an arbitrary function `my_func`::
+
+    def my_func(en):
+        return (25-en)**(1.5) / 200 + 0.1
+
+    rm = ResolutionModel(my_func, xstart=0.0, xend=24.0, accuracy=0.01)
+    cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, ..., Temperature=44.0, ResolutionModel=rm)
+
+Finally, the :ref:`PyChop` interface may be used to generate the resolution function for a particular spectrometer::
 
-    rm = ResolutionModel(my_func, xstart=0.0, xend=120.0, accuracy=0.01)
+    from PyChop import PyChop2
+    marires = PyChop2('MARI')
+    marires.setChopper('S')
+    marires.setFrequency(250)
+    marires.setEi(30)
+    rm = ResolutionModel(marires.getResolution, xstart=0.0, xend=29.0, accuracy=0.01)
     cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, ..., Temperature=44.0, ResolutionModel=rm)
 
-When a resolution model is set the peak width will be constrained to have a value close to the model. The degree of deviation is controlled by the
-`FWHMVariation` parameter. It has the default of 0.1 and is an absolute maximum difference a width can have. If set to 0 the widths will be fixed
-to their calculated values (depending on the instant values of their peak centres). For example::
+When a resolution model is set, the peak width will be constrained to have a value close to the model. The degree of deviation is controlled by the
+`FWHMVariation` parameter. It has the default of 0.1 and is the maximum difference from the value given by the resolution model a width can have. 
+If set to 0 the widths will be fixed to their calculated values (depending on the instant values of their peak centres). For example::
 
-    cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, ..., Temperature=44.0, ResolutionModel=rm, FWHMVariation=0.001)
+    cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, ..., Temperature=44.0, ResolutionModel=rm, FWHMVariation=0.1)
+
+will allow the peak widths to vary between :math:`\Delta(E)-0.1` and :math:`\Delta(E)+0.1` where :math:`\Delta(E)` is the value of the 
+resolution model at the peak position :math:`E`.
 
 
 
@@ -311,22 +337,22 @@ The resolution model also needs to be initialised from a list::
     # or
 
     rm = ResolutionModel([func0, func1], 0, 100, accuracy = 0.01)
-
+    cf.ResolutionModel = rm
 
 To calculate a spectrum call the same method `getSpectrum` but pass the spectrum index as its first parameter::
 
-  # Calculate second spectrum, use the generated x-values
-  sp = cf.getSpectrum(1)
+    # Calculate second spectrum, use the generated x-values
+    sp = cf.getSpectrum(1)
 
-  # Calculate third spectrum, use a list for x-values
-  x = [0, 1, 2, 3, ...]
-  sp = cf.getSpectrum(2, x)
-  
-  # Calculate second spectrum, use the first spectrum of a workspace
-  sp = cf.getSpectrum(1, ws)
-  
-  # Calculate first spectrum, use the i-th spectrum of a workspace
-  sp = cf.getSpectrum(0, ws, i)
+    # Calculate third spectrum, use a list for x-values
+    x = [0, 1, 2, 3, ...]
+    sp = cf.getSpectrum(2, x)
+    
+    # Calculate second spectrum, use the first spectrum of a workspace
+    sp = cf.getSpectrum(1, ws)
+    
+    # Calculate first spectrum, use the i-th spectrum of a workspace
+    sp = cf.getSpectrum(0, ws, i)
 
 Note that the attributes `Temperature`, `FWHM`, `peaks` and `background` may be set separately from the constructor, e.g.::
 
@@ -380,9 +406,11 @@ means that the intensity of `cf1` should be twice that of `cf2`.
 
 Alternatively, you can create a `CrystalFieldMultiSite` object directly. This takes Ions, Symmetries, Temperatures and peak widths as lists::
 
+    from CrystalField import CrystalFieldMultiSite
     cfms = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0], FWHMs=[1.1])
 
-To access parameters of a CrystalFieldMultiSite object, prefix with the ion index::
+Note that `Temperature` and `FWHM` (without plural) can also be used in place of the equivalent plural parameters.
+To access parameters of a CrystalFieldMultiSite object, prefix them with the ion index::
 
     cfms['ion0.B40'] = -0.031
     cfms['ion1.B20'] = 0.37737
@@ -393,7 +421,7 @@ Parameters can be set when creating the object by passing in a dictionary using
 
     cfms = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0], FWHMs=[1.1],
                                  parameters={'ion0.B20': 0.37737, 'ion0.B22': 3.9770, 'ion1.B40':-0.031787,
-                                               'ion1.B42':-0.11611, 'ion1.B44':-0.12544})
+                                             'ion1.B42':-0.11611, 'ion1.B44':-0.12544})
 
 A background can also be set this way, or using `cfms.background.` It can be passed as a string, a Function object(s), or a 
 CompositeFunction object::
@@ -407,7 +435,7 @@ CompositeFunction object::
     cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0],
                                    Background= Gaussian(PeakCentre=1) + LinearBackground())
 
-Ties and constraints are set similiarly to `CrystalField` objects. `f` prefixes have been changed to be more descriptive::
+Ties and constraints are set similarly to `CrystalField` objects. `f` prefixes have been changed to be more descriptive::
 
     cfms = CrystalFieldMultiSite(Ions=['Ce','Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44, 50], FWHMs=[1.1, 0.9],
                                    Background=FlatBackground(), BackgroundPeak=Gaussian(Height=10, Sigma=0.3),
@@ -418,8 +446,11 @@ Ties and constraints are set similiarly to `CrystalField` objects. `f` prefixes
     cfms.constraints('ion0.sp0.pk1.FWHM < 2.2')
     cfms.ties({'ion0.sp1.pk2.FWHM': '2*ion0.sp1.pk1.FWHM', 'ion1.sp1.pk3.FWHM': '2*ion1.sp1.pk2.FWHM'})
 
-When fitting, all parameters are assumed to be free. Parameters must be fixed explicitly. Fitting example::
+Parameters which are not allowed by the specified symmetry will be fixed to be zero, but unlike for the single-site case,
+all other parameters are assumed to be free (in the single-site case, parameters which are unset are assumed to be fixed
+to be zero). For the multi-site case, parameters must be fixed explicitly. For example::
 
+    params = {'ion0.B20': 0.37737, 'ion0.B22': 3.9770, 'ion1.B40':-0.031787, 'ion1.B42':-0.11611, 'ion1.B44':-0.12544}
     cf = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0, 50.0],
                                     FWHMs=[1.0, 1.0], ToleranceIntensity=6.0, ToleranceEnergy=1.0,  FixAllPeaks=True,
                                    parameters=params)
@@ -567,14 +598,14 @@ gives::
 
     {'O1': [0.125, 0.125, 0.375],
      'O2': [0.125, 0.375, 0.375],
-     'Yb1': [0.25, 0.25, 0.25],
-     'Yb2': [0.021, 0.0, 0.25],
-     'Yb3': [0.542, 0.0, 0.25]}
+     'Sm1': [0.25, 0.25, 0.25],
+     'Sm2': [0.021, 0.0, 0.25],
+     'Sm3': [0.542, 0.0, 0.25]}
 
 You can then define the charges for each site, the magnetic ion and the maximum distance, and calculate::
 
-    cif_pc_model.Charges = {'O1':-2, 'O2':-2, 'Yb1':3, 'Yb2':3, 'Yb3':3}
-    cif_pc_model.IonLabel = 'Yb2'
+    cif_pc_model.Charges = {'O1':-2, 'O2':-2, 'Sm1':3, 'Sm2':3, 'Sm3':3}
+    cif_pc_model.IonLabel = 'Sm2'
     cif_pc_model.Neighbour = 1
     cif_blm = cif_pc_model.calculate()
     print(cif_blm)
@@ -591,14 +622,14 @@ then the value for ``MaxDistance`` will be used regardless of where it appears i
 
 For ``Charges``, instead of listing the charges of each site, you can just give the charge for each element, e.g.::
 
-    cif_pc_model.Charges = {'O':-2, 'Yb':3}
+    cif_pc_model.Charges = {'O':-2, 'Sm':3}
     cif_blm = cif_pc_model.calculate()
 
 The result of the ``calculate()`` method can be put directly into a ``CrystalField`` object and used either
 to calculate a spectrum or as the starting parameters in a fit::
 
-    cf = CrystalField('Yb', 'C2', Temperature=5, FWHM=10, **cif_pc_model.calculate())
-    plot(**cf.getSpectrum())
+    cf = CrystalField('Sm', 'C2', Temperature=5, FWHM=10, **cif_pc_model.calculate())
+    plot(*cf.getSpectrum())
     fit = CrystalFieldFit(cf, InputWorkspace=ws)
     fit.fit()
 
@@ -620,12 +651,14 @@ susceptibility, and magnetisation. The calculated values can be invoked using th
 
 To calculate the heat capacity use::
 
+    import matplotlib.pyplot as plt
+    cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, Temperature=44.0)
     Cv = cf.getHeatCapacity()       # Calculates Cv(T) for 1<T<300K in 1K steps  (default)
-    pyplot.plot(*Cv)                # Returns a tuple of (x, y) values
+    plt.plot(*Cv)                   # Returns a tuple of (x, y) values
 
     T = np.arange(1,900,5)
     Cv = cf.getHeatCapacity(T)      # Calculates Cv(T) for specified values of T (1 to 900K in 5K steps here)
-    pyplot.plot(T, Cv[1])
+    plt.plot(T, Cv[1])
 
     # Temperatures from a single spectrum workspace
     ws = CreateWorkspace(T, T, T)
@@ -710,22 +743,26 @@ class as the `PhysicalProperty` attribute of `CrystalField`, either as a keyword
 
 or separately after construction::
 
-    cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544)
+    params = {'B20':0.37737, 'B22':3.9770, 'B40':-0.031787, 'B42':-0.11611, 'B44':-0.12544}
+    cf = CrystalField('Ce', 'C2v', **params)
     cf.PhysicalProperty = PhysicalProperties('Cv')
     fitcv = CrystalFieldFit(Model=cf, InputWorkspace=ws)
     fitcv.fit()
 
     # Fits a susceptibility dataset. Data is the volume susceptibility in SI units
+    cf = CrystalField('Ce', 'C2v', **params)
     cf.PhysicalProperty = PhysicalProperties('susc', Hdir='powder', Unit='SI')
     fit_chi = CrystalFieldFit(Model=cf, InputWorkspace=ws)
     fit_chi.fit()
 
     # Fits a magnetisation dataset. Data is in emu/mol, and was measured at 5K with the field || [111].
+    cf = CrystalField('Ce', 'C2v', **params)
     cf.PhysicalProperty = PhysicalProperties('M(H)', Temperature=5, Hdir=[1, 1, 1], Unit='cgs')
     fit_mag = CrystalFieldFit(Model=cf, InputWorkspace=ws)
     fit_mag.fit()
 
     # Fits a magnetisation vs temperature dataset. Data is in Am^2/mol, measured with a 0.1T field || [110]
+    cf = CrystalField('Ce', 'C2v', **params)
     cf.PhysicalProperty = PhysicalProperties('M(T)', Hmag=0.1, Hdir=[1, 1, 0], Unit='SI')
     fit_moment = CrystalFieldFit(Model=cf, InputWorkspace=ws)
     fit_moment.fit()
@@ -754,9 +791,9 @@ properties should be a list. E.g.::
     fit.fit()
 
     # Fits two INS spectra (at 44K and 50K) and the heat capacity, susceptibility and magnetisation simultaneously.
-    PPCv = PhysicalProperty('Cv')
-    PPchi = PhysicalProperty('susc', 'powder', Unit='cgs')
-    PPMag = PhysicalProperty('M(H)', 5, [1, 1, 1], 'bohr')
+    PPCv = PhysicalProperties('Cv')
+    PPchi = PhysicalProperties('susc', 'powder', Unit='cgs')
+    PPMag = PhysicalProperties('M(H)', [1, 1, 1], 5, 'bohr')
     cf = CrystalField('Ce', 'C2v', B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544,
                       Temperature=[44.0, 50], FWHM=[1.1, 0.9], PhysicalProperty=[PPCv, PPchi, PPMag] )
     fit = CrystalFieldFit(Model=cf, InputWorkspace=[ws_ins_44K, ws_ins_50K, ws_cp, ws_chi, ws_mag])
@@ -764,7 +801,7 @@ properties should be a list. E.g.::
 
 Note that `PhysicalProperty` requires the type of physical property (either `'Cv'` or `'Cp'` or `'heatcap'` for
 heat capacity; `'susc'` or `'chi'` for susceptibility; `'mag'` or `'M(H)'` for magnetic moment vs applied field;
-or `'mom'` or `'M(T)'` for moment vs temperature) as the first argument. subsequent arguments are optional, and
+or `'mom'` or `'M(T)'` for moment vs temperature) as the first argument. Subsequent arguments are optional, and
 are in the following order::
 
     PhysicalProperties('Cp')  # No further parameters required for heat capacity
diff --git a/docs/source/interfaces/Muon Analysis.rst b/docs/source/interfaces/Muon Analysis.rst
index 7667d1af375c2722fe391c11d8d3b663e37f49af..fde4cdef7afa59f0d069c5649c4b77aa751d0b3b 100644
--- a/docs/source/interfaces/Muon Analysis.rst	
+++ b/docs/source/interfaces/Muon Analysis.rst	
@@ -655,6 +655,20 @@ General
 |       |                             | the new one described above, enabling fits of multiple datasets to  |
 |       |                             | be made.                                                            |
 +-------+-----------------------------+---------------------------------------------------------------------+
+| **5** | **Load all groups**         | By default, this option is disabled and the workspaces are generated|
+|       |                             | when they are plotted.                                              |
+|       |                             |                                                                     |
+|       |                             | Enabling the option will automatically add workspaces for each group|
+|       |                             | to the grouped workspace. If a new group is created it will be added|
+|       |                             | to the grouped workspace.                                           |
++-------+-----------------------------+---------------------------------------------------------------------+
+| **5** | **Load all pairs**          | By default, this option is disabled and the workspaces are generated|
+|       |                             | when they are plotted.                                              |
+|       |                             |                                                                     |
+|       |                             | Enabling the option will automatically add workspaces for each pair |
+|       |                             | to the grouped workspace. If a new pair  is created it will be added|
+|       |                             | to the grouped workspace.                                           |
++-------+-----------------------------+---------------------------------------------------------------------+
 
 Feedback & Comments
 -------------------
diff --git a/docs/source/release/v3.11.0/framework.rst b/docs/source/release/v3.11.0/framework.rst
index 54620829f62c24978c6d389e5cf5edee2df3c5a4..c334488a216a425f81cf5fc258d20facf3b6fbb6 100644
--- a/docs/source/release/v3.11.0/framework.rst
+++ b/docs/source/release/v3.11.0/framework.rst
@@ -75,7 +75,7 @@ Performance
 Core functionality
 ------------------
 
-- The :ref:`IndexProperty` has been added to the list of property types.
+- The ``IndexProperty`` has been added to the list of property types.
 - The ``blocksize()`` of a workspace now throws an exception if the number of bins is not constant. ``size()`` has been modified to the sum of the number of bins in each ``Histogram``.
 
 
diff --git a/docs/source/release/v3.12.0/diffraction.rst b/docs/source/release/v3.12.0/diffraction.rst
index 19defb42a24096a2ca50168b09cc10bf31f29bb8..26b8c0e070560ef4890e41bf0a0cdc368baa5d26 100644
--- a/docs/source/release/v3.12.0/diffraction.rst
+++ b/docs/source/release/v3.12.0/diffraction.rst
@@ -18,6 +18,12 @@ Powder Diffraction
 
   + Scripts now support creation of grouping .cal files from ceria run(s)
   + Absorption corrections enabled for all samples, not just vanadium
+  + ``subtract_empty_instrument`` parameter added for disabling empty subtraction, useful for focusing empties
+- Improvements in ISIS Powder for HRPD:
+
+  + The prompt pulse is now masked out for the long window
+  + Extra TOF windows 10-50 and 180-280 now supported
+  + Default bin widths have been updated
 - 'suffix' parameter added for output filenames from all ISIS_Powder instruments
 - The ``CalibrationFile`` is now optional in :ref:`SNSPowderReduction <algm-SNSPowderReduction>`. In this case time focussing will use :ref:`ConvertUnits <algm-ConvertUnits>` and the instrument geometry. Care must be taken to supply a ``GroupingFile`` otherwise all of the spectra will be kept separate.
 - :ref:`SaveGSS <algm-SaveGSS>` is relaxed to accept non-TOF point data workspaces as well.
@@ -25,13 +31,16 @@ Powder Diffraction
 - :ref:`PDCalibration <algm-PDCalibration>` returns three more diagnostic workspaces: one for the fitted peak heights, one for the fitted peak widths, and one for observed resolution.
 - :ref:`LoadILLDiffraction <algm-LoadILLDiffraction>` now supports D2B files with calibrated data.
 - :ref:`PowderDiffILLReduction <algm-PowderDiffILLReduction>` and :ref:`PowderDiffILLDetEffCorr <algm-PowderDiffILLDetEffCorr>` enable the basic data reduction for D20 scanning powder diffractometer at ILL.
+- :ref:`ApplyDetectorScanEffCorr <algm-ApplyDetectorScanEffCorr>` applies the calibration file generated by :ref:`PowderDiffILLDetEffCorr <algm-PowderDiffILLDetEffCorr>` to detector scan workspaces.
 - :ref:`PowderDiffILLDetScanReduction <algm-PowderDiffILLDetScanReduction>` supports D2B and D20 (when doing a detector scan) powder diffraction reduction at the ILL.
 - New algorithm :ref:`algm-SumOverlappingTubes` combines a detector scan for D2B into a single workspace.
+- :ref:`CalculateDIFC <algm-CalculateDIFC>` has been extended to allow for calibration workspaces from :ref:`PDCalibration <algm-PDCalibration>`
 - ISIS Powder scripts for HRPD now support extra TOF windows 10-50 and 180-280, and default bin widths have been updated
 - After calling create_vanadium and focus in ISIS Powder scripts on POLARIS, the output workspaces always contain the sample material if it is set using set_sample_material. (To view the sample material, right click the workspace and click 'Sample Material...')
 - Using grouping files with only one bank was enabled in ISIS Powder, and related errors to do with mismatched number of parameters were made more readable
 - It is now possible to set beam parameters (height and width) using instrument_object.set_beam_parameters(height=123, width=456).
 - The ``mode`` parameter for POLARIS in ISIS Powder now behaves as described in the documentation - it persists through function calls and is case insensitive
+- For instruments in ISIS Powder, offset files may now be specified by an absolute path. The default behaviour of assuming they live in calibration/label has been retained
 
 Engineering Diffraction
 -----------------------
@@ -47,7 +56,7 @@ Engineering Diffraction
   + Improved progress reporting for Calibration and Focus
   + Enabled multi-run fitting and plotting in the Fitting tab
   + Improved unit conversions when using the peak picker
-  
+
 Single Crystal Diffraction
 --------------------------
 - :ref:`FilterPeaks <algm-FilterPeaks>` now supports filtering peaks by TOF, d-spacing, and wavelength.
@@ -68,6 +77,8 @@ Single Crystal Diffraction
 
 - SCD Event Data Reduction interface now uses the Indexing Tolerance for Index Peaks to index the peaks for the Select Cell options in Choose Cell tab.  Previously it used a constant, 0.12, for the tolerance.
 
+- :ref:`SaveLauenorm <algm-SaveLauenorm>` now has option to save peaks in geasc format for input into exchge and then lauescale from the laue package.
+
 - :ref:`FindPeaksMD <algm-FindPeaksMD>` now has an option to calculate the Goniometer rotation (around y-axis only) for a constant wavelength source.
 
 
diff --git a/docs/source/release/v3.12.0/direct_inelastic.rst b/docs/source/release/v3.12.0/direct_inelastic.rst
index 1949d7a42fb508f15d9e3e929330f700345edfdf..3db959ac18f8b2ae6732015484109151c990c6c8 100644
--- a/docs/source/release/v3.12.0/direct_inelastic.rst
+++ b/docs/source/release/v3.12.0/direct_inelastic.rst
@@ -27,6 +27,12 @@ Algorithms
 Crystal Field
 #############
 
+Multi-site calculations and fitting are now supported by the crystal field (Python commandline) interface. 
+
+Calculation of dipole transition matrix elements has been added, together with the addition of a :math:`\chi_0` term in the :ref:`CrystalFieldSusceptibility <func-CrystalFieldSusceptibility>` function. 
+
+Several bugs in the Python and C++ code has been fixed - see the `github page <https://github.com/mantidproject/mantid/pull/21604>`_ for details.
+
 Features Removed
 ----------------
 
diff --git a/docs/source/release/v3.12.0/framework.rst b/docs/source/release/v3.12.0/framework.rst
index 0bc285ad4401f3a706713bd88e87025c69978dec..4ed363bd59250c2215e94c8bb976dc303fbe9247 100644
--- a/docs/source/release/v3.12.0/framework.rst
+++ b/docs/source/release/v3.12.0/framework.rst
@@ -45,6 +45,7 @@ Algorithms
 - :ref:`LoadLamp <algm-LoadLamp>` is a new algorithm to load processed HDF5 files produced by LAMP program at ILL.
 - :ref:`SaveNexus <algm-SaveNexus>` will no longer crash when passed a ``PeaksWorkspace`` with integrated peaks that have missing radius information.
 - :ref:`SaveReflections <algm-LoadLamp>` is a new algorithm to save PeaksWorkspaces to Fullprof, Jana, GSAS, and SHELX text formats.
+- :ref:`ConjoinXRuns <algm-ConjoinXRuns>` will now accept workspaces with varying x-axes per spectrum.
 
 Fitting
 -------
@@ -65,6 +66,7 @@ Core Functionality
 - Added new classes ``ConfigObserver`` for listening for changes to any configuration property and ``ConfigPropertyObserver`` for listening to changes to an individual config property of interest.
 - Fixed the calculation of scattering length and scattering length squared for :py:obj:`Material <mantid.kernel.Material>`
 - Fixed the behaviour of ``UpdateInstrumentDefinitions.OnStartup`` in the :ref:`properties file <Properties File>`. It was not being used correctly for using the updated ``Facilities.xml`` file.
+- ``MultiFileProperty`` now accepts complex summation ranges for run numbers, such as ``111-113+115`` and ``111-115+123-132``.
 
 Performance
 -----------
diff --git a/docs/source/release/v3.12.0/lowq.rst b/docs/source/release/v3.12.0/lowq.rst
index ab92238779c6eb8f07edc79e323c2cc5a982f9bc..38260f1159b077706f239f3462031762563d3872 100644
--- a/docs/source/release/v3.12.0/lowq.rst
+++ b/docs/source/release/v3.12.0/lowq.rst
@@ -19,7 +19,7 @@ Features Removed
 
 Small Angle Scattering
 ----------------------
-
+- Grid lines are now displayed in ISIS SANS V2
 - Added the option to hide the period selection columns in the SANS GUI V2
 - BeamCentreFinder has been implemented to work with the new backend in the python script window.
 - Added functionality to specify q values between which merged data is used and outside of which pure HAB and LAB are used.
@@ -27,6 +27,7 @@ Small Angle Scattering
 - Have added functionality to continually plot latest results to new GUI.
 - Fixed a bug where specifying fit range was not working for merged reductions. Previously the user specified range was being ignored.
 - Fixed a bug in the old GUI where loading files on UNIX systems would not work unless the file name was in uppercase letters.
+- Fixed a bug in the old GUI where merged reductions of time sliced data was not working.
 - Added find beam centre tab to SANS GUI V2.
 - Fixed an issue where merged or all reductions were overwriting each other as they were being given the same name.
 
diff --git a/docs/source/release/v3.12.0/muon.rst b/docs/source/release/v3.12.0/muon.rst
index 034523d3794aa40b8e958a84c414086732af0827..7a3db13078c4dd858615f3730d2757685a3b1faf 100644
--- a/docs/source/release/v3.12.0/muon.rst
+++ b/docs/source/release/v3.12.0/muon.rst
@@ -13,10 +13,11 @@ MuSR Changes
 Interfaces
 ----------
 - Added a cancel button to the MaxEnt widget in Frequency Domain Analysis.
+- Added checkboxes for "add all pairs" and "add all groups" to the settings tab. 
 
 Bug Fixes
 ---------
--:ref:`CalMuonDetectorPhases <algm-CalMuonDetectorPhases>` has had the sign of the phase shift changed, this produces data with a positive frequency spike as expected.
+- :ref:`CalMuonDetectorPhases <algm-CalMuonDetectorPhases>` has had the sign of the phase shift changed, this produces data with a positive frequency spike as expected.
 
 Interface
 ---------
@@ -26,6 +27,7 @@ Interface
 
 Algorithms
 ----------
--:ref:`MuonProcess <algm-MuonProcess>` now has a flag to determine if to crop the input workspace (default is true). In the Muon Analysis interface this flag has been set to false.
+- :ref:`MuonProcess <algm-MuonProcess>` now has a flag to determine if to crop the input workspace (default is true). In the Muon Analysis interface this flag has been set to false.
+- :ref:`MuonMaxent <algm-MuonMaxent>` calculates a single frequency spectrum from multiple time domain spectra. 
 
 :ref:`Release 3.12.0 <v3.12.0>`
diff --git a/docs/source/release/v3.12.0/reflectometry.rst b/docs/source/release/v3.12.0/reflectometry.rst
index 14a88dcb0fcd72e01ead7bfd569c7808aa889214..6599075ebef7c90cc6adfc61c95eb4a634b159b0 100644
--- a/docs/source/release/v3.12.0/reflectometry.rst
+++ b/docs/source/release/v3.12.0/reflectometry.rst
@@ -32,11 +32,14 @@ New features
 Improvements
 ############
 
-- Menu items and toolbar buttons are now enabled/disabled when appropriate, e.g. to prevent table modification during processing. Directly editing table rows is also disabled during processing.
+- Grid lines are now displayed in the runs tab.
+- Menu items and toolbar buttons are now enabled/disabled when appropriate, e.g. to prevent table modification during processing. Directly editing table rows and settings is also disabled during processing.
 - Removed the 'DirectBeam' box from the settings tab of the ISIS Reflectometry interface because this is not used.
 - Properties on the Runs tab now take precedence over properties on the Settings tab.
 - Output workspace names have been improved. Names now use '+' to indicate preprocessed (i.e. summed) workspaces, rather than '_', which is used to indicate postprocessed (i.e. stitched) workspaces.
 - The Python code generated when you tick `Output Notebook` has been improved to support special characters (e.g. `+`) in workspace names. Output workspaces are now set using the output properties of the algorithm rather than by variable assignment. This avoids the possibility of invalid characters being used in Python variable names.
+- Added a new `?` button to the ISIS Reflectometry Interface which links to the documentation page.
+- Added extra tooltips to the ISIS Reflectometry Interface.
 
 
 Bug fixes
@@ -61,6 +64,7 @@ Algorithms
 New features
 ############
 
+- The new algorithm :ref:`algm-PolarizationEfficiencyCor` corrects for efficiencies in polarization analysis.
 - The new algorithm :ref:`algm-LoadILLPolarizationFactors` can load the polarization efficiency files used on D17 at ILL.
 - The new algorithm :ref:`algm-MRInspectData` takes in raw event data and determines reduction parameters.
 
diff --git a/docs/source/release/v3.12.0/spectroscopy.rst b/docs/source/release/v3.12.0/spectroscopy.rst
index 7839e668f7836233ec949792c25f4b5cdbbb69b9..e19120bf466500cda86a7a1eb4fb099ed38830c6 100644
--- a/docs/source/release/v3.12.0/spectroscopy.rst
+++ b/docs/source/release/v3.12.0/spectroscopy.rst
@@ -9,6 +9,10 @@ Spectroscopy Changes
     putting new features at the top of the section, followed by
     improvements, followed by bug fixes.
 
+- The algorithms :ref:`algm-SofQWCentre`, :ref:`algm-SofQWPolygon` and :ref:`algm-SofQWNormalisedPolygon`, which rebin an inelastic workspace (has a `DeltaE` axis) from spectrum numbers (angle) to `MomentumTransfer` may now rebin the energy (`DeltaE`) axis as well as the :math:`|Q|` (`MomentumTransfer`) axes.
+- :ref:`algm-SofQWNormalisedPolygon` now has uses a faster method for calculating the polygon intersections.
+- The crystal field computation and fitting engine is now feature complete. It can now handle multi-site computation and simultaneous fitting of inelastic spectra and physical properties dataset. See the :ref:`Crystal Field Python Interface` help page for details, and `<http://www.mantidproject.org/Crystal_Field_Examples>`_ for examples of use.
+
 Direct Geometry
 ---------------
 
diff --git a/docs/source/release/v3.12.0/ui.rst b/docs/source/release/v3.12.0/ui.rst
index ee2b3de6c4852aaf8660da0a2851a95b65a4cc56..7f03dfacd54e540119a9632071dfe859e4fb4e25 100644
--- a/docs/source/release/v3.12.0/ui.rst
+++ b/docs/source/release/v3.12.0/ui.rst
@@ -65,4 +65,17 @@ SpectrumView
    sv.show()
    app.exec_()
 
+HFIR HB3A Interface
+-------------------
+
+- Various issues reported from users have been fixed.  These issues include
+  * How to define, modify and delete region of interest on 2D image.
+  * User-specified wave length is not reflected to UB matrix calculation table.
+  * Peak integration report window cannot be launched.
+- User can load back previously saved region of interest.
+- UI sets up default value of scale factor related to normalization type.
+- User can choose to save peak intensities in Fullprof in  (3i4,2f8.2,i4) or (3i4,2f18.5,i4).
+- The 'Max Counts' in survey tab is now normalized by counting time.
+- In scans processing tab, the column name of corrected will be changed to 'F2;'Error' will be modified to 'F2 Error'.
+
 :ref:`Release 3.12.0 <v3.12.0>`
diff --git a/docs/source/techniques/ISISPowder-Pearl-v1.rst b/docs/source/techniques/ISISPowder-Pearl-v1.rst
index 11c42489774f841b8fd263648dc8192c5d3dd2fa..00a88473e622cca8feb482ee042643ac4f1f8bf6 100644
--- a/docs/source/techniques/ISISPowder-Pearl-v1.rst
+++ b/docs/source/techniques/ISISPowder-Pearl-v1.rst
@@ -990,6 +990,22 @@ On PEARL this is set to the following:
 
   spline_coefficient: 60
 
+.. _subtract_empty_instrument_pearl_isis-powder-diffraction-ref:
+
+subtract_empty_instrument
+^^^^^^^^^^^^^^^^^^^^^^^^^
+Provides the option to disable subtracting empty instrument runs from
+the run being focused. This is useful for focusing empties, as
+subtracting an empty from itself, or subtracting the previous cycle's
+empty from this cycle's, creates meaningless data. Set to **False** to
+disable empty subtraction.
+
+On PEARL this is set to the following:
+
+.. code-block:: python
+
+  subtract_empty_instrument: True
+
 .. _tt35_grouping_filename_pearl_isis-powder-diffraction-ref:
 
 tt35_grouping_filename
diff --git a/docs/source/techniques/ISISPowder-Tutorials.rst b/docs/source/techniques/ISISPowder-Tutorials.rst
index 4ad46b6d33e45886fb5880c62c7825680e3a17fd..90a01b0deb5e4e002b1f899e26b6b9a38df41184 100644
--- a/docs/source/techniques/ISISPowder-Tutorials.rst
+++ b/docs/source/techniques/ISISPowder-Tutorials.rst
@@ -612,8 +612,9 @@ you must update the calibration directory. Using the cycle mapping from Peal:
 
 The relevant fields from the cycle mapping are the ``label`` and 
 ``offset_file_name``. Within the calibration directory a folder
-with the ``label`` name must exist and contain a cal file with
-the ``offset_file_name``.
+with the ``label`` name must exist. ``offset_file_name`` must either
+be the name of a cal file within that folder, or the full path to a
+cal file elsewhere.
 
 In this example we need a folder within the calibration 
 directory called *1_2* which holds a
diff --git a/instrument/D17_Definition.xml b/instrument/D17_Definition.xml
index 5ec9dff75ecc921a191b95a3f4c131b90626fb56..c2d871096d9abd6825ea7bea1975f5e6283f167d 100644
--- a/instrument/D17_Definition.xml
+++ b/instrument/D17_Definition.xml
@@ -1,8 +1,8 @@
 <?xml version='1.0' encoding='ASCII'?>
-<instrument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.mantidproject.org/IDF/1.0" last-modified="2017-08-24 12:16:32.682412" name="D17" valid-from="2017-01-31 23:59:59" valid-to="2100-01-31 23:59:59" xsi:schemaLocation="http://www.mantidproject.org/IDF/1.0 http://schema.mantidproject.org/IDF/1.0/IDFSchema.xsd">
+<instrument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.mantidproject.org/IDF/1.0" last-modified="2018-02-20 11:04:45.101942" name="D17" valid-from="2017-01-31 23:59:59" valid-to="2100-01-31 23:59:59" xsi:schemaLocation="http://www.mantidproject.org/IDF/1.0 http://schema.mantidproject.org/IDF/1.0/IDFSchema.xsd">
   <!-- This is the instrument definition file of the D17 reflectometer at the ILL.
        Generated file, PLEASE DO NOT EDIT THIS FILE!
-       This file was automatically generated by mandidgeometry/ILL/IDF/d17_generatorIDF.py
+       This file was automatically generated by mantidgeometry/ILL/IDF/d17_generateIDF.py
 
        z axis defines the direction of the direct beam
        y axis will be the axis used for rotation in the LoadILLReflectometry algorithm
@@ -25,6 +25,12 @@
        Beam area at sample position 10 mm x 50 mm (width x height)
        Q-range 0.002 - 2 A-1
 
+       Collimation
+       Slits generally do not move (no motor attached)
+       Slit separation 3.4 m
+       Slit 2 to sample distance: 3.605 m
+       Slit 3 to sample distance: 0.205 m
+
        For more information, please visit
        https://www.ill.eu/instruments-support/instruments-groups/instruments/d17/characteristics/
        -->
@@ -73,6 +79,15 @@
     <id val="100000"/>
     <id val="100001"/>
   </idlist>
+  <!--2 Slits-->
+  <component type="slit2">
+    <location x="0.0" y="0.0" z="-3.605"/>
+  </component>
+  <type name="slit2"/>
+  <component type="slit3">
+    <location x="0.0" y="0.0" z="-0.205"/>
+  </component>
+  <type name="slit3"/>
   <!--DETECTORS-->
   <!--64 TUBES FORM ONE DETECTOR-->
   <component idfillbyfirst="x" idstart="1" idstepbyrow="1" type="detector">
@@ -90,4 +105,3 @@
     <algebra val="pixel-shape"/>
   </type>
 </instrument>
-
diff --git a/instrument/Facilities.xml b/instrument/Facilities.xml
index f3736785a05cf7e9dba3356da2ee73d15cff8baf..60aea426605f12376d0b1b110b07164f3d75bb6d 100644
--- a/instrument/Facilities.xml
+++ b/instrument/Facilities.xml
@@ -797,7 +797,10 @@
     <technique>TOF Direct Geometry Spectroscopy</technique>
     <technique>Single Crystal Diffraction</technique>
   </instrument>
-  <instrument name="V20_DIFFRACTION">
+  <instrument name="V20_4-tubes_90deg">
+    <technique>Neutron Diffraction</technique>
+  </instrument>
+  <instrument name="V20_4-tubes_150deg">
     <technique>Neutron Diffraction</technique>
   </instrument>
   <instrument name="V20_IMAGING">
diff --git a/instrument/V20_4-tubes_150deg_Definition_v01.xml b/instrument/V20_4-tubes_150deg_Definition_v01.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f124bc641ea87c3f4a44f41ad39e96f2a8d04e50
--- /dev/null
+++ b/instrument/V20_4-tubes_150deg_Definition_v01.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- For help on the notation used to specify an Instrument Definition File
+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 Schema/IDFSchema.xsd"
+name="IMAT" valid-from ="1900-01-31 23:59:59"
+valid-to ="2099-12-31 23:59:59"
+last-modified="2018-02-09 09:00:00">
+  <defaults>
+    <length unit="meter"/>
+    <angle unit="degree"/>
+    <reference-frame>
+      <!-- The z-axis is set parallel to and in the direction of the beam. the
+           y-axis points up and the coordinate system is right handed. -->
+      <along-beam axis="z"/>
+      <pointing-up axis="y"/>
+      <handedness val="right"/>
+    </reference-frame>
+    <default-view axis-view="z"/>
+  </defaults>
+
+  <component type="source-chopper">
+    <location x="0.0" y="0.0" z="-50.6"/>
+  </component>
+  <type name="source-chopper"/>
+
+  <component type="wfm-chopper">
+    <location x="0.0" y="0.0" z="-22.05"/>
+  </component>
+  <type name="wfm-chopper" is="Source" />
+
+  <component type="some-sample-holder">
+    <location x="0.0" y="0.0" z="0"/>
+  </component>
+  <type name="some-sample-holder" is="SamplePos" />
+
+  <component type="150 degree bank" idlist="150 degree bank">
+    <location x="0.0" y="0.0" z="0" rot="150.0" axis-x="0.0" axis-y="1.0" axis-z="0.0"/>
+  </component>  
+  
+  <type name="150 degree bank"> 
+    <component type="tall He3 element">  
+      <location x="0.070" z="0.775"/>
+      <location x="0.035" z="0.775"/>
+      <location x="0.000" z="0.775"/>
+      <location x="-0.035" z="0.775"/>
+    </component>
+  </type>
+  
+  <type name="tall He3 element" is="detector">     
+    <cylinder id="shape">
+      <centre-of-bottom-base x="0.0" y="-0.15" z="0.0" />
+      <axis x="0.0" y="1.0" z="0" /> 
+      <radius val="0.0127" />
+      <height val="0.3200" />
+    </cylinder> 
+  </type>  
+
+  <idlist idname="150 degree bank">
+    <id start="5" end="8" />
+  </idlist>
+
+</instrument>
diff --git a/instrument/V20_DIFFRACTION_Definition.xml b/instrument/V20_4-tubes_90deg_Definition_v01.xml
similarity index 86%
rename from instrument/V20_DIFFRACTION_Definition.xml
rename to instrument/V20_4-tubes_90deg_Definition_v01.xml
index c2c177f626619fcca6414d8abdba09bd1e89029c..f798c1ca10ba3ef246ea8d465661d3a5c2b51c94 100644
--- a/instrument/V20_DIFFRACTION_Definition.xml
+++ b/instrument/V20_4-tubes_90deg_Definition_v01.xml
@@ -34,15 +34,15 @@ last-modified="2018-02-09 09:00:00">
   <type name="some-sample-holder" is="SamplePos" />
 
   <component type="90 degree bank" idlist="90 degree bank">
-    <location x="1.0" y="0.0" z="0" rot="90.0" axis-x="0.0" axis-y="1.0" axis-z="0.0"/>
+    <location x="0.41" y="0.0" z="0" rot="90.0" axis-x="0.0" axis-y="1.0" axis-z="0.0"/>
   </component>  
   
   <type name="90 degree bank"> 
     <component type="tall He3 element">  
-      <location x="-0.06" />
-      <location x="-0.02" />
-      <location x="0.02" />
-      <location x="0.06" />
+      <location x="-0.070" />
+      <location x="-0.035" />
+      <location x="0.000" />
+      <location x="-0.035" />
     </component>
   </type>
   
@@ -50,8 +50,8 @@ last-modified="2018-02-09 09:00:00">
     <cylinder id="shape">
       <centre-of-bottom-base x="0.0" y="-0.15" z="0.0" />
       <axis x="0.0" y="1.0" z="0" /> 
-      <radius val="0.0125" />
-      <height val="0.3" />
+      <radius val="0.0127" />
+      <height val="0.3200" />
     </cylinder> 
   </type>  
 
diff --git a/instrument/V20_IMAGING_Definition.xml b/instrument/V20_IMAGING_Definition.xml
index 10df1ad74d834057c4206f7ea0daa644084aaaee..a4675133a52ee131094d48a53963dc76438ca226 100644
--- a/instrument/V20_IMAGING_Definition.xml
+++ b/instrument/V20_IMAGING_Definition.xml
@@ -58,14 +58,5 @@ last-modified="2018-02-08 09:00:00">
     </cuboid>
     <algebra val="shape" />
   </type>
-
-  <!-- DEFINITION OF TYPES -->
-
-  
-
-  <!-- shape for monitors, borrowed from GEM -->
-  
-
-  <!-- DETECTOR and MONITOR ID LISTS -->
   
 </instrument>
diff --git a/qt/applications/workbench/workbench/plugins/jupyterconsole.py b/qt/applications/workbench/workbench/plugins/jupyterconsole.py
index 7226e26b17b93cafc30d340537a9202701c92eab..4fee6d842dfb03e3b9fd01e690c6d6983cb2f281 100644
--- a/qt/applications/workbench/workbench/plugins/jupyterconsole.py
+++ b/qt/applications/workbench/workbench/plugins/jupyterconsole.py
@@ -21,7 +21,11 @@ import sys
 
 # third-party library imports
 from mantidqt.widgets.jupyterconsole import InProcessJupyterConsole
-from IPython.core.usage import quick_guide, release as ipy_release
+try:
+    from IPython.core.usage import quick_guide
+except ImportError: # quick_guide was removed in IPython 6.0
+    quick_guide = ''
+from IPython.core.usage import release as ipy_release
 from matplotlib import __version__ as mpl_version
 from numpy.version import version as np_version
 from qtpy.QtWidgets import QVBoxLayout
diff --git a/qt/paraview_ext/PVPlugins/Filters/PeaksFilter/CMakeLists.txt b/qt/paraview_ext/PVPlugins/Filters/PeaksFilter/CMakeLists.txt
index e0971994ed1fd95ee37d23991abf9351463358dc..3615a8391602c18e00bf195c58da96898b72977b 100644
--- a/qt/paraview_ext/PVPlugins/Filters/PeaksFilter/CMakeLists.txt
+++ b/qt/paraview_ext/PVPlugins/Filters/PeaksFilter/CMakeLists.txt
@@ -5,7 +5,7 @@ SERVER_MANAGER_XML PeaksFilter.xml
 SERVER_MANAGER_SOURCES vtkPeaksFilter.cxx vtkPeaksFilter.h)
 set_pvplugin_properties (MantidParaViewPeaksFilterSMPlugin QT_VERSION 4)
 
-target_link_libraries( MantidParaViewPeaksFilterSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} 
+target_link_libraries( MantidParaViewPeaksFilterSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME}
 ${CORE_MANTIDLIBS}
 DataObjects
 VatesAPI
@@ -15,7 +15,11 @@ ${vtkjsoncpp_LIBRARIES}
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( MantidParaViewPeaksFilterSMPlugin PROPERTIES INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+  set_target_properties(MantidParaViewPeaksFilterSMPlugin PROPERTIES
+                        INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(MantidParaViewPeaksFilterSMPlugin PROPERTIES
+                        INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR}")
 endif ()
 
 install_pvplugin (MantidParaViewPeaksFilterSMPlugin QT_VERSION 4)
diff --git a/qt/paraview_ext/PVPlugins/Filters/ScaleWorkspace/CMakeLists.txt b/qt/paraview_ext/PVPlugins/Filters/ScaleWorkspace/CMakeLists.txt
index ba3867bd51718e26d338fe6e8d93107919ba53be..c8c5f7b6d108c609b81d5a4dda1f656dad5ab942 100644
--- a/qt/paraview_ext/PVPlugins/Filters/ScaleWorkspace/CMakeLists.txt
+++ b/qt/paraview_ext/PVPlugins/Filters/ScaleWorkspace/CMakeLists.txt
@@ -5,7 +5,7 @@ SERVER_MANAGER_XML ScaleWorkspace.xml
 SERVER_MANAGER_SOURCES vtkScaleWorkspace.cxx)
 set_pvplugin_properties (MantidParaViewScaleWorkspaceSMPlugin QT_VERSION 4)
 
-target_link_libraries( MantidParaViewScaleWorkspaceSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} 
+target_link_libraries( MantidParaViewScaleWorkspaceSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME}
 ${CORE_MANTIDLIBS}
 DataObjects
 VatesAPI
@@ -13,7 +13,11 @@ ${vtkjsoncpp_LIBRARIES}
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( MantidParaViewScaleWorkspaceSMPlugin PROPERTIES INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+  set_target_properties(MantidParaViewScaleWorkspaceSMPlugin PROPERTIES
+                        INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(MantidParaViewScaleWorkspaceSMPlugin PROPERTIES
+                        INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR}")
 endif ()
 
 install_pvplugin (MantidParaViewScaleWorkspaceSMPlugin QT_VERSION 4)
diff --git a/qt/paraview_ext/PVPlugins/Filters/SplatterPlot/CMakeLists.txt b/qt/paraview_ext/PVPlugins/Filters/SplatterPlot/CMakeLists.txt
index 5c8b6c4eba7f8b2cd1770209b511718afa78a75b..af27f2a7b8020c71d4737b8b4a03feb3c996cf89 100644
--- a/qt/paraview_ext/PVPlugins/Filters/SplatterPlot/CMakeLists.txt
+++ b/qt/paraview_ext/PVPlugins/Filters/SplatterPlot/CMakeLists.txt
@@ -9,7 +9,7 @@ set_pvplugin_properties (MantidParaViewSplatterPlotSMPlugin QT_VERSION 4)
 
 include_directories ( SYSTEM ${QWT5_INCLUDE_DIR} )
 
-target_link_libraries( MantidParaViewSplatterPlotSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME}  
+target_link_libraries( MantidParaViewSplatterPlotSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME}
 ${CORE_MANTIDLIBS}
 DataObjects
 VatesAPI
@@ -20,8 +20,11 @@ ${vtkjsoncpp_LIBRARIES}
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( MantidParaViewSplatterPlotSMPlugin PROPERTIES INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+  set_target_properties(MantidParaViewSplatterPlotSMPlugin PROPERTIES
+                        INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(MantidParaViewSplatterPlotSMPlugin PROPERTIES
+                        INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR}")
 endif ()
 
 install_pvplugin (MantidParaViewSplatterPlotSMPlugin QT_VERSION 4)
-
diff --git a/qt/paraview_ext/PVPlugins/Readers/MDEWNexusReader/CMakeLists.txt b/qt/paraview_ext/PVPlugins/Readers/MDEWNexusReader/CMakeLists.txt
index 8b0b72ddb5c4867bffec1498b6e6e85ba408efbe..4c370ea7821d84fe21e00f7cee66a8066d5d5d66 100644
--- a/qt/paraview_ext/PVPlugins/Readers/MDEWNexusReader/CMakeLists.txt
+++ b/qt/paraview_ext/PVPlugins/Readers/MDEWNexusReader/CMakeLists.txt
@@ -8,7 +8,7 @@ set_pvplugin_properties (MantidParaViewMDEWNexusReaderSMPlugin QT_VERSION 4)
 
 include_directories ( SYSTEM ${QWT5_INCLUDE_DIR} )
 
-target_link_libraries( MantidParaViewMDEWNexusReaderSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} 
+target_link_libraries( MantidParaViewMDEWNexusReaderSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME}
 ${vtkjsoncpp_LIBRARIES}
 ${CORE_MANTIDLIBS}
 DataObjects
@@ -20,7 +20,11 @@ Qt4::QtCore
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( MantidParaViewMDEWNexusReaderSMPlugin PROPERTIES INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+  set_target_properties(MantidParaViewMDEWNexusReaderSMPlugin PROPERTIES
+                        INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(MantidParaViewMDEWNexusReaderSMPlugin PROPERTIES
+                        INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR}")
 endif ()
 
 install_pvplugin (MantidParaViewMDEWNexusReaderSMPlugin QT_VERSION 4)
diff --git a/qt/paraview_ext/PVPlugins/Readers/MDHWNexusReader/CMakeLists.txt b/qt/paraview_ext/PVPlugins/Readers/MDHWNexusReader/CMakeLists.txt
index ab78ea7f66eee6b29dff2924078c8ad0d9ae9d2e..1766734e929e019289446f6d660851de7bdede12 100644
--- a/qt/paraview_ext/PVPlugins/Readers/MDHWNexusReader/CMakeLists.txt
+++ b/qt/paraview_ext/PVPlugins/Readers/MDHWNexusReader/CMakeLists.txt
@@ -8,7 +8,7 @@ set_pvplugin_properties (MantidParaViewMDHWNexusReaderSMPlugin QT_VERSION 4)
 
 include_directories ( SYSTEM ${QWT5_INCLUDE_DIR} )
 
-target_link_libraries( MantidParaViewMDHWNexusReaderSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} 
+target_link_libraries( MantidParaViewMDHWNexusReaderSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME}
 ${CORE_MANTIDLIBS}
 DataObjects
 VatesAPI
@@ -20,7 +20,10 @@ Qt4::QtCore
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( MantidParaViewMDHWNexusReaderSMPlugin PROPERTIES INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+  set_target_properties(MantidParaViewMDHWNexusReaderSMPlugin PROPERTIES
+                        INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(MantidParaViewMDHWNexusReaderSMPlugin PROPERTIES INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR}")
 endif ()
 
 install_pvplugin (MantidParaViewMDHWNexusReaderSMPlugin QT_VERSION 4)
diff --git a/qt/paraview_ext/PVPlugins/Readers/NexusPeaksReader/CMakeLists.txt b/qt/paraview_ext/PVPlugins/Readers/NexusPeaksReader/CMakeLists.txt
index 1f81b8f7361bf47822c08fbb5d0f6937f6bb1d9b..16747038fd737a84b763e8d330917c09cc397b8e 100644
--- a/qt/paraview_ext/PVPlugins/Readers/NexusPeaksReader/CMakeLists.txt
+++ b/qt/paraview_ext/PVPlugins/Readers/NexusPeaksReader/CMakeLists.txt
@@ -6,7 +6,7 @@ add_paraview_plugin( MantidParaViewNexusPeaksReaderSMPlugin "1.0"
 )
 set_pvplugin_properties (MantidParaViewNexusPeaksReaderSMPlugin QT_VERSION 4)
 
-target_link_libraries( MantidParaViewNexusPeaksReaderSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} 
+target_link_libraries( MantidParaViewNexusPeaksReaderSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME}
 ${CORE_MANTIDLIBS}
 DataObjects
 VatesAPI
@@ -16,9 +16,11 @@ ${NEXUS_LIBRARIES}
 ${NEXUS_C_LIBRARIES})
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( MantidParaViewNexusPeaksReaderSMPlugin PROPERTIES INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+  set_target_properties(MantidParaViewNexusPeaksReaderSMPlugin PROPERTIES
+                        INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(MantidParaViewNexusPeaksReaderSMPlugin PROPERTIES
+                        INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR}")
 endif ()
 
 install_pvplugin (MantidParaViewNexusPeaksReaderSMPlugin QT_VERSION 4)
-
-
diff --git a/qt/paraview_ext/PVPlugins/Readers/PeaksReader/CMakeLists.txt b/qt/paraview_ext/PVPlugins/Readers/PeaksReader/CMakeLists.txt
index 5aeb1d90d62e8425f243aef747021cf20a11a3a7..657645ecb22dc84136ff49b84fae81c0dc616cac 100644
--- a/qt/paraview_ext/PVPlugins/Readers/PeaksReader/CMakeLists.txt
+++ b/qt/paraview_ext/PVPlugins/Readers/PeaksReader/CMakeLists.txt
@@ -9,7 +9,7 @@ set_pvplugin_properties (MantidParaViewPeaksReaderSMPlugin QT_VERSION 4)
 # Add to the 'VatesParaViewPlugins' group in VS
 set_property( TARGET MantidParaViewPeaksReaderSMPlugin PROPERTY FOLDER "MantidVatesParaViewPlugins" )
 
-target_link_libraries( MantidParaViewPeaksReaderSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} 
+target_link_libraries( MantidParaViewPeaksReaderSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME}
 ${CORE_MANTIDLIBS}
 DataObjects
 VatesAPI
@@ -18,9 +18,11 @@ ${Boost_LIBRARIES}
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( MantidParaViewPeaksReaderSMPlugin PROPERTIES INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+  set_target_properties(MantidParaViewPeaksReaderSMPlugin PROPERTIES
+                        INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(MantidParaViewPeaksReaderSMPlugin PROPERTIES
+                        INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR}")
 endif ()
 
 install_pvplugin (MantidParaViewPeaksReaderSMPlugin QT_VERSION 4)
-
-
diff --git a/qt/paraview_ext/PVPlugins/Sources/MDEWSource/CMakeLists.txt b/qt/paraview_ext/PVPlugins/Sources/MDEWSource/CMakeLists.txt
index 0e2c165d463c65113472a66d5028143dabcb7073..8ad774a160cedca017ebaf49b1970675d9da29ad 100644
--- a/qt/paraview_ext/PVPlugins/Sources/MDEWSource/CMakeLists.txt
+++ b/qt/paraview_ext/PVPlugins/Sources/MDEWSource/CMakeLists.txt
@@ -6,7 +6,7 @@ ADD_PARAVIEW_PLUGIN(MantidParaViewMDEWSourceSMPlugin "1.0"
 set_pvplugin_properties (MantidParaViewMDEWSourceSMPlugin QT_VERSION 4)
 
 include_directories ( SYSTEM ${QWT5_INCLUDE_DIR} )
-target_link_libraries( MantidParaViewMDEWSourceSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} 
+target_link_libraries( MantidParaViewMDEWSourceSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME}
 ${CORE_MANTIDLIBS}
 DataObjects
 VatesAPI
@@ -18,7 +18,10 @@ Qt4::QtCore
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( MantidParaViewMDEWSourceSMPlugin PROPERTIES INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+  set_target_properties(MantidParaViewMDEWSourceSMPlugin PROPERTIES
+                        INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(MantidParaViewMDEWSourceSMPlugin PROPERTIES INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR}")
 endif ()
 
 install_pvplugin (MantidParaViewMDEWSourceSMPlugin QT_VERSION 4)
diff --git a/qt/paraview_ext/PVPlugins/Sources/MDHWSource/CMakeLists.txt b/qt/paraview_ext/PVPlugins/Sources/MDHWSource/CMakeLists.txt
index 394c47275404da2b8eb29b908d2eaf36f8c2c409..fc08e675b6b62dc7f887c0a612da270171cfe270 100644
--- a/qt/paraview_ext/PVPlugins/Sources/MDHWSource/CMakeLists.txt
+++ b/qt/paraview_ext/PVPlugins/Sources/MDHWSource/CMakeLists.txt
@@ -7,7 +7,7 @@ set_pvplugin_properties (MantidParaViewMDHWSourceSMPlugin QT_VERSION 4)
 
 include_directories ( SYSTEM ${QWT5_INCLUDE_DIR} )
 
-target_link_libraries( MantidParaViewMDHWSourceSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} 
+target_link_libraries( MantidParaViewMDHWSourceSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME}
 ${CORE_MANTIDLIBS}
 DataObjects
 VatesAPI
@@ -19,7 +19,11 @@ Qt4::QtCore
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( MantidParaViewMDHWSourceSMPlugin PROPERTIES INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+  set_target_properties(MantidParaViewMDHWSourceSMPlugin PROPERTIES
+                        INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(MantidParaViewMDHWSourceSMPlugin PROPERTIES
+                        INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR}")
 endif ()
 
 install_pvplugin (MantidParaViewMDHWSourceSMPlugin QT_VERSION 4)
diff --git a/qt/paraview_ext/PVPlugins/Sources/PeaksSource/CMakeLists.txt b/qt/paraview_ext/PVPlugins/Sources/PeaksSource/CMakeLists.txt
index e0ba945b3b93645fb36731b30f6c95f7b5037760..eb87a67ae27ddfb4b8f0c6bfef1f134e67f56ace 100644
--- a/qt/paraview_ext/PVPlugins/Sources/PeaksSource/CMakeLists.txt
+++ b/qt/paraview_ext/PVPlugins/Sources/PeaksSource/CMakeLists.txt
@@ -14,7 +14,11 @@ ${Boost_LIBRARIES}
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( MantidParaViewPeaksSourceSMPlugin PROPERTIES INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+  set_target_properties(MantidParaViewPeaksSourceSMPlugin
+                        PROPERTIES INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(MantidParaViewPeaksSourceSMPlugin
+                        PROPERTIES INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR}")
 endif ()
 
 install_pvplugin (MantidParaViewPeaksSourceSMPlugin QT_VERSION 4)
diff --git a/qt/paraview_ext/PVPlugins/Sources/SinglePeakMarkerSource/CMakeLists.txt b/qt/paraview_ext/PVPlugins/Sources/SinglePeakMarkerSource/CMakeLists.txt
index 3e3e60f7816f60a2d3b36ca5acdc461f2b0148f5..fbed014965eee65537f8a2313e18bd9db5666781 100644
--- a/qt/paraview_ext/PVPlugins/Sources/SinglePeakMarkerSource/CMakeLists.txt
+++ b/qt/paraview_ext/PVPlugins/Sources/SinglePeakMarkerSource/CMakeLists.txt
@@ -8,7 +8,11 @@ set_pvplugin_properties (MantidParaViewSinglePeakMarkerSourceSMPlugin QT_VERSION
 target_link_libraries( MantidParaViewSinglePeakMarkerSourceSMPlugin LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} ${CORE_MANTIDLIBS} VatesAPI )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( MantidParaViewSinglePeakMarkerSourceSMPlugin PROPERTIES INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+  set_target_properties(MantidParaViewSinglePeakMarkerSourceSMPlugin PROPERTIES
+                        INSTALL_RPATH "@loader_path/../../../Contents/Libraries;@loader_path/../../../Contents/MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(MantidParaViewSinglePeakMarkerSourceSMPlugin PROPERTIES
+                        INSTALL_RPATH "\$ORIGIN/../../../${LIB_DIR}")
 endif ()
 
 install_pvplugin (MantidParaViewSinglePeakMarkerSourceSMPlugin QT_VERSION 4)
diff --git a/qt/paraview_ext/VatesAPI/CMakeLists.txt b/qt/paraview_ext/VatesAPI/CMakeLists.txt
index 7acc8ef11f826bee09ee3d8482afd8a97824f124..567ab71d3a97c1c4566bc8bdff3165f77208f349 100644
--- a/qt/paraview_ext/VatesAPI/CMakeLists.txt
+++ b/qt/paraview_ext/VatesAPI/CMakeLists.txt
@@ -220,7 +220,9 @@ endif()
 
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( VatesAPI PROPERTIES INSTALL_RPATH "@loader_path/../MacOS;@loader_path/../Libraries")
+  set_target_properties(VatesAPI PROPERTIES INSTALL_RPATH "@loader_path/../MacOS;@loader_path/../Libraries")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(VatesAPI PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 # Create test file projects
diff --git a/qt/paraview_ext/VatesAlgorithms/CMakeLists.txt b/qt/paraview_ext/VatesAlgorithms/CMakeLists.txt
index 253e8da2af0c7b365943673b627d94a358585678..5fc54adb2eeddf14972e2714d1fd9130b6818eda 100644
--- a/qt/paraview_ext/VatesAlgorithms/CMakeLists.txt
+++ b/qt/paraview_ext/VatesAlgorithms/CMakeLists.txt
@@ -48,7 +48,9 @@ ${POCO_LIBRARIES}
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( VatesAlgorithms PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS;@loader_path/../Libraries")
+  set_target_properties(VatesAlgorithms PROPERTIES INSTALL_RPATH "@loader_path/../Contents/MacOS;@loader_path/../Libraries")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(VatesAlgorithms PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 # Create test file projects
diff --git a/qt/paraview_ext/VatesSimpleGui/QtWidgets/CMakeLists.txt b/qt/paraview_ext/VatesSimpleGui/QtWidgets/CMakeLists.txt
index 763427c635981b5e9a0dbaa7231a8f2e2e5ca886..a67121969ada40ca346a259ce484deffb4e4b62f 100644
--- a/qt/paraview_ext/VatesSimpleGui/QtWidgets/CMakeLists.txt
+++ b/qt/paraview_ext/VatesSimpleGui/QtWidgets/CMakeLists.txt
@@ -51,6 +51,8 @@ mtd_add_qt_library (TARGET_NAME VatesSimpleGuiQtWidgets
   OSX_INSTALL_RPATH
     @loader_path/../../Contents/Libraries
     @loader_path/../../Contents/MacOS
+  LINUX_INSTALL_RPATH
+    "\$ORIGIN/../../${LIB_DIR}"
 )
 
 # Set the name of the generated library
diff --git a/qt/paraview_ext/VatesSimpleGui/ViewWidgets/CMakeLists.txt b/qt/paraview_ext/VatesSimpleGui/ViewWidgets/CMakeLists.txt
index e0ad791bf97402bae7c8fe7d62687cd4e61bc693..fd315f848220acce656e2ec9544f7e9d00429a9a 100644
--- a/qt/paraview_ext/VatesSimpleGui/ViewWidgets/CMakeLists.txt
+++ b/qt/paraview_ext/VatesSimpleGui/ViewWidgets/CMakeLists.txt
@@ -135,6 +135,8 @@ mtd_add_qt_library (TARGET_NAME VatesSimpleGuiViewWidgets
   OSX_INSTALL_RPATH
     @loader_path/../../Contents/MacOS
     @loader_path/../../Contents/Libraries
+  LINUX_INSTALL_RPATH
+    "\$ORIGIN/../../${LIB_DIR}"
 )
 
 # Set the name of the generated library
diff --git a/qt/python/mantidqt/widgets/codeeditor/execution.py b/qt/python/mantidqt/widgets/codeeditor/execution.py
index 990c348c2f44ebd0b7f1c2792897473c234f597e..e36cbb8105e6d708c7479a85e23222a7c5bb9241 100644
--- a/qt/python/mantidqt/widgets/codeeditor/execution.py
+++ b/qt/python/mantidqt/widgets/codeeditor/execution.py
@@ -149,7 +149,7 @@ class PythonCodeExecution(QObject):
         self._task = task
         return task
 
-    def execute(self, code_str, filename=''):
+    def execute(self, code_str, filename=None):
         """Execute the given code on the calling thread
         within the provided context.
 
diff --git a/qt/python/mantidqt/widgets/codeeditor/inputsplitter.py b/qt/python/mantidqt/widgets/codeeditor/inputsplitter.py
index 738a33a264cf76a5d1f28ba677c25d1a8aab5ff3..a13256d2309fa7a789f954ff56f751af9209eed9 100644
--- a/qt/python/mantidqt/widgets/codeeditor/inputsplitter.py
+++ b/qt/python/mantidqt/widgets/codeeditor/inputsplitter.py
@@ -68,7 +68,10 @@ class InputSplitter(IPyInputSplitter):
         if source.endswith('\\\n'):
             return False
 
-        self._update_indent(lines)
+        try:
+            self._update_indent(lines)
+        except TypeError: # _update_indent was changed in IPython 6.0
+            self._update_indent()
         try:
             self.code = self._compile(source, symbol="exec")
         # Invalid syntax can produce any of a number of different errors from
diff --git a/qt/python/mantidqt/widgets/codeeditor/test/test_execution.py b/qt/python/mantidqt/widgets/codeeditor/test/test_execution.py
index 4c4c5f5960f278111dc6afa4f4f408b99b3d4db3..b0d6a5b7865644ca2882d7d21827ba53437ce2ab 100644
--- a/qt/python/mantidqt/widgets/codeeditor/test/test_execution.py
+++ b/qt/python/mantidqt/widgets/codeeditor/test/test_execution.py
@@ -111,7 +111,7 @@ class PythonCodeExecutionTest(unittest.TestCase):
         code = """
 def foo():
     def bar():
-        # raises a NameError
+        \"""raises a NameError\"""
         y = _local + 1
     # call inner
     bar()
@@ -220,7 +220,7 @@ squared = sum*sum
         executor = PythonCodeExecution()
         self.assertRaises(expected_exc_type, executor.execute, code)
 
-    def _run_async_code(self, code, with_progress=False, filename=''):
+    def _run_async_code(self, code, with_progress=False, filename=None):
         executor = PythonCodeExecution()
         if with_progress:
             recv = ReceiverWithProgress()
diff --git a/qt/python/mantidqtpython/mantidqtpython_def.sip b/qt/python/mantidqtpython/mantidqtpython_def.sip
index 4f345d40f0203dba51fe88aae1efdba08f52efea..431d52e15b0fb514301f537157d183d4ace5a1a8 100644
--- a/qt/python/mantidqtpython/mantidqtpython_def.sip
+++ b/qt/python/mantidqtpython/mantidqtpython_def.sip
@@ -1549,22 +1549,22 @@ class QDataProcessorWidget : QWidget
 public:
 QDataProcessorWidget(const MantidQt::MantidWidgets::DataProcessor::WhiteList &,
                      const MantidQt::MantidWidgets::DataProcessor::ProcessingAlgorithm &,
-                     QWidget *parent );
+                     QWidget *parent, int group = 0 );
 QDataProcessorWidget(const MantidQt::MantidWidgets::DataProcessor::WhiteList &,
                      QWidget *parent );
 QDataProcessorWidget(const MantidQt::MantidWidgets::DataProcessor::WhiteList &,
                      const MantidQt::MantidWidgets::DataProcessor::PreprocessMap &,
                      const MantidQt::MantidWidgets::DataProcessor::ProcessingAlgorithm &,
-                     QWidget *parent );
+                     QWidget *parent, int group = 0 );
 QDataProcessorWidget(const MantidQt::MantidWidgets::DataProcessor::WhiteList &,
                      const MantidQt::MantidWidgets::DataProcessor::ProcessingAlgorithm &,
                      const MantidQt::MantidWidgets::DataProcessor::PostprocessingAlgorithm &,
-                     QWidget *parent );
+                     QWidget *parent, int group = 0 );
 QDataProcessorWidget(const MantidQt::MantidWidgets::DataProcessor::WhiteList &,
                      const MantidQt::MantidWidgets::DataProcessor::PreprocessMap &,
                      const MantidQt::MantidWidgets::DataProcessor::ProcessingAlgorithm &,
                      const MantidQt::MantidWidgets::DataProcessor::PostprocessingAlgorithm &,
-                     QWidget *parent );
+                     QWidget *parent, int group = 0 );
 void accept(MantidQt::MantidWidgets::DataProcessor::DataProcessorMainPresenter *);
 void setInstrumentList(const QString &instruments, const QString &defaultInstrument);
 QString getCurrentInstrument() const;
@@ -2462,7 +2462,7 @@ private:
 };//end namespace MantidWidgets
 
 
-namespace SpectrumView 
+namespace SpectrumView
 {
 class SpectrumView : QMainWindow
 {
diff --git a/qt/scientific_interfaces/CMakeLists.txt b/qt/scientific_interfaces/CMakeLists.txt
index 0dbb3851bfeb01b7e6bc1a7bfd101720b3313133..e07a503eb1f6d8fd29d7432177d56ea6d83e8324 100644
--- a/qt/scientific_interfaces/CMakeLists.txt
+++ b/qt/scientific_interfaces/CMakeLists.txt
@@ -32,6 +32,7 @@ set ( TEST_FILES
   test/ALCPeakFittingPresenterTest.h
   test/EnggDiffGSASFittingModelTest.h
   test/EnggDiffGSASFittingPresenterTest.h
+  test/EnggDiffMultiRunFittingWidgetModelTest.h
   test/EnggDiffMultiRunFittingWidgetPresenterTest.h
   test/EnggDiffFittingModelTest.h
   test/EnggDiffFittingPresenterTest.h
diff --git a/qt/scientific_interfaces/DynamicPDF/CMakeLists.txt b/qt/scientific_interfaces/DynamicPDF/CMakeLists.txt
index 44b0c90ddc870dbf622dedd3d738145d57a67375..6214e1311802b822d713fa1522b8ce3339b3708f 100644
--- a/qt/scientific_interfaces/DynamicPDF/CMakeLists.txt
+++ b/qt/scientific_interfaces/DynamicPDF/CMakeLists.txt
@@ -21,7 +21,7 @@ set ( INC_FILES
 	SliceSelector.h
 )
 
-set ( MOC_FILES 
+set ( MOC_FILES
     DPDFBackgroundRemover.h
     DPDFDisplayControl.h
     DPDFFitControl.h
@@ -32,7 +32,7 @@ set ( MOC_FILES
 )
 
 
-set ( UI_FILES 
+set ( UI_FILES
     DPDFBackgroundRemover.ui
     DPDFFitControl.ui
     DPDFFourierTransform.ui
@@ -64,4 +64,6 @@ mtd_add_qt_library (TARGET_NAME MantidScientificInterfacesDynamicPDF
     ${PLUGINS_DIR}
   OSX_INSTALL_RPATH
     @loader_path/../../Contents/MacOS
+  LINUX_INSTALL_RPATH
+    "\$ORIGIN/../../${LIB_DIR}"
 )
diff --git a/qt/scientific_interfaces/EnggDiffraction/CMakeLists.txt b/qt/scientific_interfaces/EnggDiffraction/CMakeLists.txt
index 8663c9c3d4b4350a9e4276ca5f372d7c4b110ef8..af61739f60d81589913c22fa37faf1218178de04 100644
--- a/qt/scientific_interfaces/EnggDiffraction/CMakeLists.txt
+++ b/qt/scientific_interfaces/EnggDiffraction/CMakeLists.txt
@@ -5,6 +5,8 @@ set ( SRC_FILES
 	EnggDiffGSASFittingModel.cpp
 	EnggDiffGSASFittingPresenter.cpp
 	EnggDiffGSASFittingViewQtWidget.cpp
+	EnggDiffMultiRunFittingQtWidget.cpp
+	EnggDiffMultiRunFittingWidgetModel.cpp
 	EnggDiffMultiRunFittingWidgetPresenter.cpp
 	EnggDiffractionPresenter.cpp
 	EnggDiffractionViewQtGUI.cpp
@@ -23,6 +25,8 @@ set ( INC_FILES
 	EnggDiffGSASFittingPresenter.h
 	EnggDiffGSASFittingViewQtWidget.h
 	EnggDiffGSASRefinementMethod.h
+	EnggDiffMultiRunFittingQtWidget.h
+	EnggDiffMultiRunFittingWidgetModel.h
 	EnggDiffMultiRunFittingWidgetPresenter.h
 	EnggDiffractionPresWorker.h
 	EnggDiffractionPresenter.h
@@ -49,6 +53,7 @@ set ( MOC_FILES
     EnggDiffractionPresenter.h
     EnggDiffractionPresWorker.h
     EnggDiffractionViewQtGUI.h
+    EnggDiffMultiRunFittingQtWidget.h
 )
 
 set ( UI_FILES
@@ -59,6 +64,7 @@ set ( UI_FILES
    EnggDiffractionQtTabFitting.ui
    EnggDiffractionQtTabGSAS.ui
    EnggDiffractionQtTabSettings.ui
+   EnggDiffMultiRunFittingWidget.ui
 )
 
 mtd_add_qt_library (TARGET_NAME MantidScientificInterfacesEnggDiffraction
@@ -88,5 +94,7 @@ mtd_add_qt_library (TARGET_NAME MantidScientificInterfacesEnggDiffraction
   OSX_INSTALL_RPATH
     @loader_path/../../Contents/MacOS
     @loader_path/../../plugins/qt4
-)
+  LINUX_INSTALL_RPATH
+    "\$ORIGIN/../../${LIB_DIR};\$ORIGIN/../../plugins/qt4/"
 
+)
diff --git a/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingModel.cpp b/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingModel.cpp
index 2cdc507f9321efa581949b8aee0590c67cd3c114..4b36684b8ff0ceed54827646836c084674df2fba 100644
--- a/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingModel.cpp
+++ b/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingModel.cpp
@@ -18,14 +18,14 @@ std::string stripWSNameFromFilename(const std::string &fullyQualifiedFilename) {
   return filenameSegments[0];
 }
 
-size_t getBankID(API::MatrixWorkspace_const_sptr ws) {
+boost::optional<size_t> getBankID(API::MatrixWorkspace_const_sptr ws) {
   const static std::string bankIDPropertyName = "bankid";
   if (ws->run().hasProperty(bankIDPropertyName)) {
     const auto log = dynamic_cast<Kernel::PropertyWithValue<int> *>(
         ws->run().getLogData(bankIDPropertyName));
     return boost::lexical_cast<size_t>(log->value());
   }
-  throw std::runtime_error("Bank ID was not set in the sample logs.");
+  return boost::none;
 }
 
 } // anonymous namespace
@@ -81,33 +81,37 @@ std::string generateLatticeParamsName(const RunLabel &runLabel) {
 }
 }
 
-bool EnggDiffGSASFittingModel::doPawleyRefinement(
+boost::optional<std::string> EnggDiffGSASFittingModel::doPawleyRefinement(
     const RunLabel &runLabel, const std::string &instParamFile,
     const std::vector<std::string> &phaseFiles, const std::string &pathToGSASII,
     const std::string &GSASIIProjectFile, const double dMin,
     const double negativeWeight) {
   const auto inputWS = getFocusedWorkspace(runLabel);
   if (!inputWS) {
-    return false;
+    return "Could not find focused run for run number " +
+           std::to_string(runLabel.runNumber) + " and bank ID " +
+           std::to_string(runLabel.bank);
   }
   const auto outputWSName = generateFittedPeaksWSName(runLabel);
   const auto latticeParamsName = generateLatticeParamsName(runLabel);
 
-  const auto rwp = doGSASRefinementAlgorithm(
-      *inputWS, outputWSName, latticeParamsName, "Pawley refinement",
-      instParamFile, phaseFiles, pathToGSASII, GSASIIProjectFile, dMin,
-      negativeWeight);
+  double rwp = 0;
 
-  if (!rwp) {
-    return false;
+  try {
+    rwp = doGSASRefinementAlgorithm(*inputWS, outputWSName, latticeParamsName,
+                                    "Pawley refinement", instParamFile,
+                                    phaseFiles, pathToGSASII, GSASIIProjectFile,
+                                    dMin, negativeWeight);
+  } catch (const std::exception &e) {
+    return boost::make_optional<std::string>(e.what());
   }
 
-  addFitResultsToMaps(runLabel, *rwp, outputWSName, latticeParamsName);
+  addFitResultsToMaps(runLabel, rwp, outputWSName, latticeParamsName);
 
-  return true;
+  return boost::none;
 }
 
-boost::optional<double> EnggDiffGSASFittingModel::doGSASRefinementAlgorithm(
+double EnggDiffGSASFittingModel::doGSASRefinementAlgorithm(
     API::MatrixWorkspace_sptr inputWorkspace,
     const std::string &outputWorkspaceName,
     const std::string &latticeParamsName, const std::string &refinementMethod,
@@ -115,52 +119,52 @@ boost::optional<double> EnggDiffGSASFittingModel::doGSASRefinementAlgorithm(
     const std::vector<std::string> &phaseFiles, const std::string &pathToGSASII,
     const std::string &GSASIIProjectFile, const double dMin,
     const double negativeWeight) {
-  double rwp = -1;
-  try {
-    auto gsasAlg =
-        API::AlgorithmManager::Instance().create("GSASIIRefineFitPeaks");
-    gsasAlg->setProperty("RefinementMethod", refinementMethod);
-    gsasAlg->setProperty("InputWorkspace", inputWorkspace);
-    gsasAlg->setProperty("OutputWorkspace", outputWorkspaceName);
-    gsasAlg->setProperty("LatticeParameters", latticeParamsName);
-    gsasAlg->setProperty("InstrumentFile", instParamFile);
-    gsasAlg->setProperty("PhaseInfoFiles", phaseFiles);
-    gsasAlg->setProperty("PathToGSASII", pathToGSASII);
-    gsasAlg->setProperty("SaveGSASIIProjectFile", GSASIIProjectFile);
-    gsasAlg->setProperty("PawleyDMin", dMin);
-    gsasAlg->setProperty("PawleyNegativeWeight", negativeWeight);
-    gsasAlg->execute();
-
-    rwp = gsasAlg->getProperty("Rwp");
-  } catch (const std::exception) {
-    return boost::none;
-  }
+  auto gsasAlg =
+      API::AlgorithmManager::Instance().create("GSASIIRefineFitPeaks");
+  gsasAlg->setProperty("RefinementMethod", refinementMethod);
+  gsasAlg->setProperty("InputWorkspace", inputWorkspace);
+  gsasAlg->setProperty("OutputWorkspace", outputWorkspaceName);
+  gsasAlg->setProperty("LatticeParameters", latticeParamsName);
+  gsasAlg->setProperty("InstrumentFile", instParamFile);
+  gsasAlg->setProperty("PhaseInfoFiles", phaseFiles);
+  gsasAlg->setProperty("PathToGSASII", pathToGSASII);
+  gsasAlg->setProperty("SaveGSASIIProjectFile", GSASIIProjectFile);
+  gsasAlg->setProperty("PawleyDMin", dMin);
+  gsasAlg->setProperty("PawleyNegativeWeight", negativeWeight);
+  gsasAlg->execute();
+
+  const double rwp = gsasAlg->getProperty("Rwp");
   return rwp;
 }
 
-bool EnggDiffGSASFittingModel::doRietveldRefinement(
+boost::optional<std::string> EnggDiffGSASFittingModel::doRietveldRefinement(
     const RunLabel &runLabel, const std::string &instParamFile,
     const std::vector<std::string> &phaseFiles, const std::string &pathToGSASII,
     const std::string &GSASIIProjectFile) {
   const auto inputWS = getFocusedWorkspace(runLabel);
   if (!inputWS) {
-    return false;
+    return "Could not find focused run for run number " +
+           std::to_string(runLabel.runNumber) + " and bank ID " +
+           std::to_string(runLabel.bank);
   }
+
   const auto outputWSName = generateFittedPeaksWSName(runLabel);
   const auto latticeParamsName = generateLatticeParamsName(runLabel);
 
-  const auto rwp = doGSASRefinementAlgorithm(
-      *inputWS, outputWSName, latticeParamsName, "Rietveld refinement",
-      instParamFile, phaseFiles, pathToGSASII, GSASIIProjectFile,
-      DEFAULT_PAWLEY_DMIN, DEFAULT_PAWLEY_NEGATIVE_WEIGHT);
-
-  if (!rwp) {
-    return false;
+  double rwp = 0;
+  try {
+    rwp = doGSASRefinementAlgorithm(
+        *inputWS, outputWSName, latticeParamsName, "Rietveld refinement",
+        instParamFile, phaseFiles, pathToGSASII, GSASIIProjectFile,
+        DEFAULT_PAWLEY_DMIN, DEFAULT_PAWLEY_NEGATIVE_WEIGHT);
   }
 
-  addFitResultsToMaps(runLabel, *rwp, outputWSName, latticeParamsName);
+  catch (const std::exception &e) {
+    return boost::make_optional<std::string>(e.what());
+  }
+  addFitResultsToMaps(runLabel, rwp, outputWSName, latticeParamsName);
 
-  return true;
+  return boost::none;
 }
 
 boost::optional<API::MatrixWorkspace_sptr>
@@ -196,7 +200,8 @@ bool EnggDiffGSASFittingModel::hasFocusedRun(const RunLabel &runLabel) const {
   return m_focusedWorkspaceMap.contains(runLabel);
 }
 
-bool EnggDiffGSASFittingModel::loadFocusedRun(const std::string &filename) {
+boost::optional<std::string>
+EnggDiffGSASFittingModel::loadFocusedRun(const std::string &filename) {
   const auto wsName = stripWSNameFromFilename(filename);
 
   try {
@@ -204,17 +209,24 @@ bool EnggDiffGSASFittingModel::loadFocusedRun(const std::string &filename) {
     loadAlg->setProperty("Filename", filename);
     loadAlg->setProperty("OutputWorkspace", wsName);
     loadAlg->execute();
-  } catch (const std::exception) {
-    return false;
+  } catch (const std::exception &e) {
+    return boost::make_optional<std::string>(e.what());
   }
 
   API::AnalysisDataServiceImpl &ADS = API::AnalysisDataService::Instance();
   const auto ws = ADS.retrieveWS<API::MatrixWorkspace>(wsName);
 
   const auto runNumber = ws->getRunNumber();
-  const auto bank = getBankID(ws);
-  m_focusedWorkspaceMap.add(RunLabel(runNumber, bank), ws);
-  return true;
+  const auto bankID = getBankID(ws);
+
+  if (!bankID) {
+    return "Bank ID was not set in the sample logs for run " +
+           std::to_string(runNumber) +
+           ". Make sure it has been focused correctly";
+  }
+
+  m_focusedWorkspaceMap.add(RunLabel(runNumber, *bankID), ws);
+  return boost::none;
 }
 
 } // CustomInterfaces
diff --git a/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingModel.h b/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingModel.h
index b2956e7a8c5933d7e19f4f6d62cb8e88b46dfde3..18b749bb0afe83db3206b4cd1c820df20982a7b5 100644
--- a/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingModel.h
+++ b/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingModel.h
@@ -12,19 +12,19 @@ class MANTIDQT_ENGGDIFFRACTION_DLL EnggDiffGSASFittingModel
     : public IEnggDiffGSASFittingModel {
 
 public:
-  bool doPawleyRefinement(const RunLabel &runLabel,
-                          const std::string &instParamFile,
-                          const std::vector<std::string> &phaseFiles,
-                          const std::string &pathToGSASII,
-                          const std::string &GSASIIProjectFile,
-                          const double dMin,
-                          const double negativeWeight) override;
-
-  bool doRietveldRefinement(const RunLabel &runLabel,
-                            const std::string &instParamFile,
-                            const std::vector<std::string> &phaseFiles,
-                            const std::string &pathToGSASII,
-                            const std::string &GSASIIProjectFile) override;
+  boost::optional<std::string>
+  doPawleyRefinement(const RunLabel &runLabel, const std::string &instParamFile,
+                     const std::vector<std::string> &phaseFiles,
+                     const std::string &pathToGSASII,
+                     const std::string &GSASIIProjectFile, const double dMin,
+                     const double negativeWeight) override;
+
+  boost::optional<std::string>
+  doRietveldRefinement(const RunLabel &runLabel,
+                       const std::string &instParamFile,
+                       const std::vector<std::string> &phaseFiles,
+                       const std::string &pathToGSASII,
+                       const std::string &GSASIIProjectFile) override;
 
   boost::optional<Mantid::API::MatrixWorkspace_sptr>
   getFittedPeaks(const RunLabel &runLabel) const override;
@@ -41,7 +41,8 @@ public:
 
   bool hasFittedPeaksForRun(const RunLabel &runLabel) const override;
 
-  bool loadFocusedRun(const std::string &filename) override;
+  boost::optional<std::string>
+  loadFocusedRun(const std::string &filename) override;
 
 protected:
   /// The following methods are marked as protected so that they can be exposed
@@ -94,8 +95,8 @@ private:
   /// Run GSASIIRefineFitPeaks
   /// Note this must be virtual so that it can be mocked out by the helper class
   /// in EnggDiffGSASFittingModelTest
-  /// Returns Rwp of the fit (empty optional if fit was unsuccessful)
-  virtual boost::optional<double> doGSASRefinementAlgorithm(
+  /// Returns Rwp of the fit
+  virtual double doGSASRefinementAlgorithm(
       Mantid::API::MatrixWorkspace_sptr inputWorkspace,
       const std::string &outputWorkspaceName,
       const std::string &latticeParamsName, const std::string &refinementMethod,
diff --git a/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingPresenter.cpp b/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingPresenter.cpp
index 3349ec9841460f33edc355301a092bfe2241875f..812ce3bbb19fa3d30e5c2ca8a33a41060b978fa1 100644
--- a/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingPresenter.cpp
+++ b/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingPresenter.cpp
@@ -61,11 +61,12 @@ void EnggDiffGSASFittingPresenter::displayFitResults(const RunLabel &runLabel) {
   const auto rwp = m_model->getRwp(runLabel);
 
   if (!fittedPeaks || !latticeParams || !rwp) {
-    m_view->userWarning("Unexpectedly tried to plot fit results for invalid "
-                        "run, run number = " +
-                        std::to_string(runLabel.runNumber) + ", bank ID = " +
-                        std::to_string(runLabel.bank) +
-                        ". Please contact the development team");
+    m_view->userError("Invalid run identifier",
+                      "Unexpectedly tried to plot fit results for invalid "
+                      "run, run number = " +
+                          std::to_string(runLabel.runNumber) + ", bank ID = " +
+                          std::to_string(runLabel.bank) +
+                          ". Please contact the development team");
     return;
   }
 
@@ -76,7 +77,7 @@ void EnggDiffGSASFittingPresenter::displayFitResults(const RunLabel &runLabel) {
   m_view->displayRwp(*rwp);
 }
 
-bool EnggDiffGSASFittingPresenter::doPawleyRefinement(
+boost::optional<std::string> EnggDiffGSASFittingPresenter::doPawleyRefinement(
     const RunLabel &runLabel, const std::string &instParamFile,
     const std::vector<std::string> &phaseFiles, const std::string &pathToGSASII,
     const std::string &GSASIIProjectFile) {
@@ -88,7 +89,7 @@ bool EnggDiffGSASFittingPresenter::doPawleyRefinement(
                                      negativeWeight);
 }
 
-bool EnggDiffGSASFittingPresenter::doRietveldRefinement(
+boost::optional<std::string> EnggDiffGSASFittingPresenter::doRietveldRefinement(
     const RunLabel &runLabel, const std::string &instParamFile,
     const std::vector<std::string> &phaseFiles, const std::string &pathToGSASII,
     const std::string &GSASIIProjectFile) {
@@ -106,41 +107,40 @@ void EnggDiffGSASFittingPresenter::processDoRefinement() {
   const auto pathToGSASII = m_view->getPathToGSASII();
   const auto GSASIIProjectFile = m_view->getGSASIIProjectPath();
 
-  bool refinementSuccessful = false;
+  boost::optional<std::string> refinementFailure(boost::none);
 
   switch (refinementMethod) {
 
   case GSASRefinementMethod::PAWLEY:
-    refinementSuccessful = doPawleyRefinement(
-        runLabel, instParamFile, phaseFiles, pathToGSASII, GSASIIProjectFile);
+    refinementFailure = doPawleyRefinement(runLabel, instParamFile, phaseFiles,
+                                           pathToGSASII, GSASIIProjectFile);
     break;
 
   case GSASRefinementMethod::RIETVELD:
-    refinementSuccessful = doRietveldRefinement(
+    refinementFailure = doRietveldRefinement(
         runLabel, instParamFile, phaseFiles, pathToGSASII, GSASIIProjectFile);
     break;
   }
 
-  if (refinementSuccessful) {
-    updatePlot(runLabel);
+  if (refinementFailure) {
+    m_view->userWarning("Refinement failed", *refinementFailure);
   } else {
-    m_view->userWarning("Refinement failed, see the log for more details");
+    updatePlot(runLabel);
   }
 }
 
 void EnggDiffGSASFittingPresenter::processLoadRun() {
   const auto focusedFileNames = m_view->getFocusedFileNames();
-  bool loadSuccessful = true;
 
-  for (const auto &fileName : focusedFileNames) {
-    loadSuccessful &= m_model->loadFocusedRun(fileName);
-  }
+  for (const auto fileName : focusedFileNames) {
+    const auto loadFailure = m_model->loadFocusedRun(fileName);
 
-  if (loadSuccessful) {
-    const auto runLabels = m_model->getRunLabels();
-    m_view->updateRunList(runLabels);
-  } else {
-    m_view->userWarning("Load failed, see the log for more details");
+    if (loadFailure) {
+      m_view->userWarning("Load failed", *loadFailure);
+    } else {
+      const auto runLabels = m_model->getRunLabels();
+      m_view->updateRunList(runLabels);
+    }
   }
 }
 
@@ -156,9 +156,11 @@ void EnggDiffGSASFittingPresenter::processShutDown() { m_viewHasClosed = true; }
 void EnggDiffGSASFittingPresenter::updatePlot(const RunLabel &runLabel) {
   const auto focusedWSOptional = m_model->getFocusedWorkspace(runLabel);
   if (!focusedWSOptional) {
-    m_view->userWarning("Tried to access invalid run, runNumber " +
-                        std::to_string(runLabel.runNumber) + " and bank ID " +
-                        std::to_string(runLabel.bank));
+    m_view->userError("Invalid run identifier",
+                      "Tried to access invalid run, runNumber " +
+                          std::to_string(runLabel.runNumber) + " and bank ID " +
+                          std::to_string(runLabel.bank) +
+                          ". Please contact the development team");
     return;
   }
   const auto focusedWS = *focusedWSOptional;
diff --git a/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingPresenter.h b/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingPresenter.h
index adc3c069a5b93d1e944e566efc090ecc6027959f..4697765096d72190592f94048230b5a638863d3a 100644
--- a/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingPresenter.h
+++ b/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingPresenter.h
@@ -45,13 +45,14 @@ private:
    @param pathToGSASII Location of the directory containing GSASIIscriptable.py
    (and GSAS-II executables)
    @param GSASIIProjectFile Location to save the .gpx project to
-   @return Whether the refinement was successful
+   @return String describing any failures, empty optional if refinement was
+   successful
    */
-  bool doPawleyRefinement(const RunLabel &runLabel,
-                          const std::string &instParamFile,
-                          const std::vector<std::string> &phaseFiles,
-                          const std::string &pathToGSASII,
-                          const std::string &GSASIIProjectFile);
+  boost::optional<std::string>
+  doPawleyRefinement(const RunLabel &runLabel, const std::string &instParamFile,
+                     const std::vector<std::string> &phaseFiles,
+                     const std::string &pathToGSASII,
+                     const std::string &GSASIIProjectFile);
 
   /**
    Perform a Rietveld refinement on a run
@@ -62,13 +63,13 @@ private:
    @param pathToGSASII Location of the directory containing GSASIIscriptable.py
    (and GSAS-II executables)
    @param GSASIIProjectFile Location to save the .gpx project to
-   @return Whether the refinement was successful
+   @return String describing any failures, empty optional if refinement was
+   successful
    */
-  bool doRietveldRefinement(const RunLabel &runLabel,
-                            const std::string &instParamFile,
-                            const std::vector<std::string> &phaseFiles,
-                            const std::string &pathToGSASII,
-                            const std::string &GSASIIProjectFile);
+  boost::optional<std::string> doRietveldRefinement(
+      const RunLabel &runLabel, const std::string &instParamFile,
+      const std::vector<std::string> &phaseFiles,
+      const std::string &pathToGSASII, const std::string &GSASIIProjectFile);
 
   /**
    Overplot fitted peaks for a run, and display lattice parameters and Rwp in
diff --git a/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingViewQtWidget.cpp b/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingViewQtWidget.cpp
index c48734efc077ad8466215c16b549698b0c23e514..d267540a78354c51c1b23e9ebe5c9157e1785973 100644
--- a/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingViewQtWidget.cpp
+++ b/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingViewQtWidget.cpp
@@ -7,7 +7,9 @@
 namespace MantidQt {
 namespace CustomInterfaces {
 
-EnggDiffGSASFittingViewQtWidget::EnggDiffGSASFittingViewQtWidget() {
+EnggDiffGSASFittingViewQtWidget::EnggDiffGSASFittingViewQtWidget(
+    boost::shared_ptr<IEnggDiffractionUserMsg> userMessageProvider)
+    : m_userMessageProvider(userMessageProvider) {
   setupUI();
 
   m_zoomTool = Mantid::Kernel::make_unique<QwtPlotZoomer>(
@@ -200,10 +202,15 @@ void EnggDiffGSASFittingViewQtWidget::updateRunList(
   }
 }
 
+void EnggDiffGSASFittingViewQtWidget::userError(
+    const std::string &errorTitle, const std::string &errorDescription) const {
+  m_userMessageProvider->userError(errorTitle, errorDescription);
+}
+
 void EnggDiffGSASFittingViewQtWidget::userWarning(
+    const std::string &warningTitle,
     const std::string &warningDescription) const {
-  UNUSED_ARG(warningDescription);
-  throw std::runtime_error("userWarning not yet implemented");
+  m_userMessageProvider->userWarning(warningTitle, warningDescription);
 }
 
 } // CustomInterfaces
diff --git a/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingViewQtWidget.h b/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingViewQtWidget.h
index 3cb4278865cc5bc1d819d2471e42d0dc6d336934..d0989d65b6dc942d1fb42ad6648e0ce42211505c 100644
--- a/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingViewQtWidget.h
+++ b/qt/scientific_interfaces/EnggDiffraction/EnggDiffGSASFittingViewQtWidget.h
@@ -4,6 +4,7 @@
 #include "DllConfig.h"
 #include "IEnggDiffGSASFittingPresenter.h"
 #include "IEnggDiffGSASFittingView.h"
+#include "IEnggDiffractionUserMsg.h"
 
 #include <qwt_plot_curve.h>
 #include <qwt_plot_zoomer.h>
@@ -19,7 +20,8 @@ class MANTIDQT_ENGGDIFFRACTION_DLL EnggDiffGSASFittingViewQtWidget
   Q_OBJECT
 
 public:
-  EnggDiffGSASFittingViewQtWidget();
+  EnggDiffGSASFittingViewQtWidget(
+      boost::shared_ptr<IEnggDiffractionUserMsg> userMessageProvider);
 
   ~EnggDiffGSASFittingViewQtWidget() override;
 
@@ -60,7 +62,11 @@ public:
 
   void updateRunList(const std::vector<RunLabel> &runLabels) override;
 
-  void userWarning(const std::string &warningDescription) const override;
+  void userError(const std::string &errorTitle,
+                 const std::string &errorDescription) const override;
+
+  void userWarning(const std::string &warningTitle,
+                   const std::string &warningDescription) const override;
 
 private slots:
   void browseFocusedRun();
@@ -70,6 +76,8 @@ private slots:
 private:
   std::vector<std::unique_ptr<QwtPlotCurve>> m_focusedRunCurves;
 
+  boost::shared_ptr<IEnggDiffractionUserMsg> m_userMessageProvider;
+
   std::unique_ptr<IEnggDiffGSASFittingPresenter> m_presenter;
 
   Ui::EnggDiffractionQtTabGSAS m_ui;
diff --git a/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingQtWidget.cpp b/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingQtWidget.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f3d2ee184faaa093222e5922fd41ebf4d32101e4
--- /dev/null
+++ b/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingQtWidget.cpp
@@ -0,0 +1,126 @@
+#include "EnggDiffMultiRunFittingQtWidget.h"
+
+#include "MantidKernel/make_unique.h"
+
+namespace MantidQt {
+namespace CustomInterfaces {
+
+EnggDiffMultiRunFittingQtWidget::EnggDiffMultiRunFittingQtWidget(
+    boost::shared_ptr<IEnggDiffMultiRunFittingWidgetPresenter> presenter,
+    boost::shared_ptr<IEnggDiffractionUserMsg> messageProvider)
+    : m_presenter(presenter), m_userMessageProvider(messageProvider) {
+  setupUI();
+
+  m_zoomTool = Mantid::Kernel::make_unique<QwtPlotZoomer>(
+      QwtPlot::xBottom, QwtPlot::yLeft,
+      QwtPicker::DragSelection | QwtPicker::CornerToCorner,
+      QwtPicker::AlwaysOff, m_ui.plotArea->canvas());
+  m_zoomTool->setRubberBandPen(QPen(Qt::black));
+  m_zoomTool->setEnabled(false);
+}
+
+EnggDiffMultiRunFittingQtWidget::~EnggDiffMultiRunFittingQtWidget() {
+  cleanUpPlot();
+}
+
+void EnggDiffMultiRunFittingQtWidget::cleanUpPlot() {
+  for (auto &curve : m_focusedRunCurves) {
+    curve->detach();
+  }
+  m_focusedRunCurves.clear();
+}
+
+RunLabel EnggDiffMultiRunFittingQtWidget::getSelectedRunLabel() const {
+  const auto currentLabel = m_ui.listWidget_runLabels->currentItem()->text();
+  const auto pieces = currentLabel.split("_");
+  if (pieces.size() != 2) {
+    throw std::runtime_error(
+        "Unexpected run label: \"" + currentLabel.toStdString() +
+        "\". Please contact the development team with this message");
+  }
+  return RunLabel(pieces[0].toInt(), pieces[1].toUInt());
+}
+
+void EnggDiffMultiRunFittingQtWidget::reportPlotInvalidFittedPeaks(
+    const RunLabel &runLabel) {
+  userError("Invalid fitted peaks identifier",
+            "Tried to plot invalid fitted peaks, run number " +
+                std::to_string(runLabel.runNumber) + " and bank ID " +
+                std::to_string(runLabel.bank) +
+                ". Please contact the development team with this message");
+}
+
+void EnggDiffMultiRunFittingQtWidget::reportPlotInvalidFocusedRun(
+    const RunLabel &runLabel) {
+  userError("Invalid focused run identifier",
+            "Tried to plot invalid focused run, run number " +
+                std::to_string(runLabel.runNumber) + " and bank ID " +
+                std::to_string(runLabel.bank) +
+                ". Please contact the development team with this message");
+}
+
+void EnggDiffMultiRunFittingQtWidget::plotFittedPeaks(
+    const std::vector<boost::shared_ptr<QwtData>> &curve) {
+  UNUSED_ARG(curve);
+  throw std::runtime_error("plotFittedPeaks not yet implemented");
+}
+
+void EnggDiffMultiRunFittingQtWidget::plotFocusedRun(
+    const std::vector<boost::shared_ptr<QwtData>> &curves) {
+  for (const auto &curve : curves) {
+    auto plotCurve = Mantid::Kernel::make_unique<QwtPlotCurve>();
+
+    plotCurve->setData(*curve);
+    plotCurve->attach(m_ui.plotArea);
+    m_focusedRunCurves.emplace_back(std::move(plotCurve));
+  }
+  m_ui.plotArea->replot();
+  m_zoomTool->setZoomBase();
+  m_zoomTool->setEnabled(true);
+}
+
+void EnggDiffMultiRunFittingQtWidget::processSelectRun() {
+  m_presenter->notify(
+      IEnggDiffMultiRunFittingWidgetPresenter::Notification::SelectRun);
+}
+
+void EnggDiffMultiRunFittingQtWidget::resetCanvas() {
+  cleanUpPlot();
+  m_ui.plotArea->replot();
+  resetPlotZoomLevel();
+}
+
+void EnggDiffMultiRunFittingQtWidget::resetPlotZoomLevel() {
+  m_ui.plotArea->setAxisAutoScale(QwtPlot::xBottom);
+  m_ui.plotArea->setAxisAutoScale(QwtPlot::yLeft);
+  m_zoomTool->setZoomBase(true);
+}
+
+void EnggDiffMultiRunFittingQtWidget::setupUI() {
+  m_ui.setupUi(this);
+
+  connect(m_ui.listWidget_runLabels, SIGNAL(itemSelectionChanged()), this,
+          SLOT(processSelectRun()));
+}
+
+bool EnggDiffMultiRunFittingQtWidget::showFitResultsSelected() const {
+  return m_ui.checkBox_plotFittedPeaks->isChecked();
+}
+
+void EnggDiffMultiRunFittingQtWidget::updateRunList(
+    const std::vector<RunLabel> &runLabels) {
+  m_ui.listWidget_runLabels->clear();
+  for (const auto &runLabel : runLabels) {
+    const auto labelStr = QString::number(runLabel.runNumber) + tr("_") +
+                          QString::number(runLabel.bank);
+    m_ui.listWidget_runLabels->addItem(labelStr);
+  }
+}
+
+void EnggDiffMultiRunFittingQtWidget::userError(
+    const std::string &errorTitle, const std::string &errorDescription) {
+  m_userMessageProvider->userError(errorTitle, errorDescription);
+}
+
+} // CustomInterfaces
+} // MantidQt
diff --git a/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingQtWidget.h b/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingQtWidget.h
new file mode 100644
index 0000000000000000000000000000000000000000..3bb7ea3dc4dac310fa49f44d0e8d7bdfed7eb14a
--- /dev/null
+++ b/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingQtWidget.h
@@ -0,0 +1,74 @@
+#ifndef MANTIDQT_CUSTOMINTERFACES_ENGGDIFFMULTIRUNFITTINGQTWIDGET_H_
+#define MANTIDQT_CUSTOMINTERFACES_ENGGDIFFMULTIRUNFITTINGQTWIDGET_H_
+
+#include "DllConfig.h"
+#include "IEnggDiffMultiRunFittingWidgetPresenter.h"
+#include "IEnggDiffMultiRunFittingWidgetView.h"
+#include "IEnggDiffractionUserMsg.h"
+#include "ui_EnggDiffMultiRunFittingWidget.h"
+
+#include <boost/shared_ptr.hpp>
+#include <qwt_plot_curve.h>
+#include <qwt_plot_zoomer.h>
+
+namespace MantidQt {
+namespace CustomInterfaces {
+
+class MANTIDQT_ENGGDIFFRACTION_DLL EnggDiffMultiRunFittingQtWidget
+    : public QWidget,
+      public IEnggDiffMultiRunFittingWidgetView {
+  Q_OBJECT
+
+public:
+  EnggDiffMultiRunFittingQtWidget(
+      boost::shared_ptr<IEnggDiffMultiRunFittingWidgetPresenter> presenter,
+      boost::shared_ptr<IEnggDiffractionUserMsg> messageProvider);
+
+  ~EnggDiffMultiRunFittingQtWidget() override;
+
+  RunLabel getSelectedRunLabel() const override;
+
+  void plotFittedPeaks(
+      const std::vector<boost::shared_ptr<QwtData>> &curve) override;
+
+  void
+  plotFocusedRun(const std::vector<boost::shared_ptr<QwtData>> &curve) override;
+
+  void reportPlotInvalidFittedPeaks(const RunLabel &runLabel) override;
+
+  void reportPlotInvalidFocusedRun(const RunLabel &runLabel) override;
+
+  void resetCanvas() override;
+
+  bool showFitResultsSelected() const override;
+
+  void updateRunList(const std::vector<RunLabel> &runLabels) override;
+
+  void userError(const std::string &errorTitle,
+                 const std::string &errorDescription) override;
+
+private slots:
+  void processSelectRun();
+
+private:
+  void cleanUpPlot();
+
+  void resetPlotZoomLevel();
+
+  void setupUI();
+
+  std::vector<std::unique_ptr<QwtPlotCurve>> m_focusedRunCurves;
+
+  std::unique_ptr<QwtPlotZoomer> m_zoomTool;
+
+  boost::shared_ptr<IEnggDiffMultiRunFittingWidgetPresenter> m_presenter;
+
+  Ui::EnggDiffMultiRunFittingWidget m_ui;
+
+  boost::shared_ptr<IEnggDiffractionUserMsg> m_userMessageProvider;
+};
+
+} // CustomInterfaces
+} // MantidQt
+
+#endif // MANTIDQT_CUSTOMINTERFACES_ENGGDIFFMULTIRUNFITTINGQTWIDGET_H_
diff --git a/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidget.ui b/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidget.ui
new file mode 100644
index 0000000000000000000000000000000000000000..cf9d7ee6f8d2c418be9ba036b2cf85e18d3e58a0
--- /dev/null
+++ b/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidget.ui
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EnggDiffMultiRunFittingWidget</class>
+ <widget class="QWidget" name="EnggDiffMultiRunFittingWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>499</width>
+    <height>453</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="1">
+    <widget class="QLabel" name="label_runLabels">
+     <property name="text">
+      <string>Run Number</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QwtPlot" name="plotArea">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+       <horstretch>4</horstretch>
+       <verstretch>2</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>0</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="sizeIncrement">
+      <size>
+       <width>2</width>
+       <height>2</height>
+      </size>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1">
+    <widget class="QListWidget" name="listWidget_runLabels">
+     <property name="minimumSize">
+      <size>
+       <width>75</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="maximumSize">
+      <size>
+       <width>75</width>
+       <height>16777215</height>
+      </size>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1">
+    <widget class="QPushButton" name="pushButton_removeRun">
+     <property name="text">
+      <string>Remove Run</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <layout class="QHBoxLayout" name="horizontalLayout_plotControls">
+     <item>
+      <widget class="QPushButton" name="pushButton_plotToSeparateWindow">
+       <property name="text">
+        <string>Plot To Separate Window</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QCheckBox" name="checkBox_plotFittedPeaks">
+       <property name="text">
+        <string>Plot Fitted Peaks</string>
+       </property>
+      </widget>
+     </item>
+     <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>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>QwtPlot</class>
+   <extends>QFrame</extends>
+   <header>qwt_plot.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidgetModel.cpp b/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidgetModel.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b72be7f54d7813b54de6fbd38248bb9d783874cb
--- /dev/null
+++ b/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidgetModel.cpp
@@ -0,0 +1,45 @@
+#include "EnggDiffMultiRunFittingWidgetModel.h"
+
+namespace MantidQt {
+namespace CustomInterfaces {
+
+void EnggDiffMultiRunFittingWidgetModel::addFittedPeaks(
+    const RunLabel &runLabel, const Mantid::API::MatrixWorkspace_sptr ws) {
+  m_fittedPeaksMap.add(runLabel, ws);
+}
+
+void EnggDiffMultiRunFittingWidgetModel::addFocusedRun(
+    const RunLabel &runLabel, const Mantid::API::MatrixWorkspace_sptr ws) {
+  m_focusedRunMap.add(runLabel, ws);
+}
+
+std::vector<RunLabel>
+EnggDiffMultiRunFittingWidgetModel::getAllWorkspaceLabels() const {
+  return m_focusedRunMap.getRunLabels();
+}
+
+boost::optional<Mantid::API::MatrixWorkspace_sptr>
+EnggDiffMultiRunFittingWidgetModel::getFittedPeaks(
+    const RunLabel &runLabel) const {
+  if (m_fittedPeaksMap.contains(runLabel)) {
+    return m_fittedPeaksMap.get(runLabel);
+  }
+  return boost::none;
+}
+
+boost::optional<Mantid::API::MatrixWorkspace_sptr>
+EnggDiffMultiRunFittingWidgetModel::getFocusedRun(
+    const RunLabel &runLabel) const {
+  if (m_focusedRunMap.contains(runLabel)) {
+    return m_focusedRunMap.get(runLabel);
+  }
+  return boost::none;
+}
+
+bool EnggDiffMultiRunFittingWidgetModel::hasFittedPeaksForRun(
+    const RunLabel &runLabel) const {
+  return m_fittedPeaksMap.contains(runLabel);
+}
+
+} // namespace MantidQt
+} // namespace CustomInterfaces
diff --git a/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidgetModel.h b/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidgetModel.h
new file mode 100644
index 0000000000000000000000000000000000000000..8560e252c00333aaa73c75eaf851b97b0d51c907
--- /dev/null
+++ b/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidgetModel.h
@@ -0,0 +1,41 @@
+#ifndef MANTIDQT_CUSTOMINTERFACES_ENGGDIFFMULTIRUNFITTINGWIDGETMODEL_H_
+#define MANTIDQT_CUSTOMINTERFACES_ENGGDIFFMULTIRUNFITTINGWIDGETMODEL_H_
+
+#include "DllConfig.h"
+#include "IEnggDiffMultiRunFittingWidgetModel.h"
+#include "RunMap.h"
+
+namespace MantidQt {
+namespace CustomInterfaces {
+
+class MANTIDQT_ENGGDIFFRACTION_DLL EnggDiffMultiRunFittingWidgetModel
+    : public IEnggDiffMultiRunFittingWidgetModel {
+
+public:
+  void addFittedPeaks(const RunLabel &runLabel,
+                      const Mantid::API::MatrixWorkspace_sptr ws) override;
+
+  void addFocusedRun(const RunLabel &runLabel,
+                     const Mantid::API::MatrixWorkspace_sptr ws) override;
+
+  std::vector<RunLabel> getAllWorkspaceLabels() const override;
+
+  boost::optional<Mantid::API::MatrixWorkspace_sptr>
+  getFittedPeaks(const RunLabel &runLabel) const override;
+
+  boost::optional<Mantid::API::MatrixWorkspace_sptr>
+  getFocusedRun(const RunLabel &runLabel) const override;
+
+  bool hasFittedPeaksForRun(const RunLabel &runLabel) const override;
+
+private:
+  static constexpr size_t MAX_BANKS = 2;
+
+  RunMap<MAX_BANKS, Mantid::API::MatrixWorkspace_sptr> m_fittedPeaksMap;
+  RunMap<MAX_BANKS, Mantid::API::MatrixWorkspace_sptr> m_focusedRunMap;
+};
+
+} // namespace MantidQt
+} // namespace CustomInterfaces
+
+#endif // MANTIDQT_CUSTOMINTERFACES_ENGGDIFFMULTIRUNFITTINGWIDGETMODEL_H_
diff --git a/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidgetPresenter.cpp b/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidgetPresenter.cpp
index 18689ef1e7d837827ceef6d456bedc4605e97cf6..9dc6f6f959d3f3b6eb493a93953a5dbe70a72646 100644
--- a/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidgetPresenter.cpp
+++ b/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidgetPresenter.cpp
@@ -1,5 +1,7 @@
 #include "EnggDiffMultiRunFittingWidgetPresenter.h"
 
+#include "MantidQtWidgets/LegacyQwt/QwtHelper.h"
+
 namespace MantidQt {
 namespace CustomInterfaces {
 
@@ -18,6 +20,17 @@ void EnggDiffMultiRunFittingWidgetPresenter::addFocusedRun(
   m_view->updateRunList(m_model->getAllWorkspaceLabels());
 }
 
+void EnggDiffMultiRunFittingWidgetPresenter::displayFitResults(
+    const RunLabel &runLabel) {
+  const auto fittedPeaks = m_model->getFittedPeaks(runLabel);
+  if (!fittedPeaks) {
+    m_view->reportPlotInvalidFittedPeaks(runLabel);
+  } else {
+    const auto plottablePeaks = API::QwtHelper::curveDataFromWs(*fittedPeaks);
+    m_view->plotFittedPeaks(plottablePeaks);
+  }
+}
+
 boost::optional<Mantid::API::MatrixWorkspace_sptr>
 EnggDiffMultiRunFittingWidgetPresenter::getFittedPeaks(
     const RunLabel &runLabel) const {
@@ -30,5 +43,38 @@ EnggDiffMultiRunFittingWidgetPresenter::getFocusedRun(
   return m_model->getFocusedRun(runLabel);
 }
 
+void EnggDiffMultiRunFittingWidgetPresenter::notify(
+    IEnggDiffMultiRunFittingWidgetPresenter::Notification notif) {
+  switch (notif) {
+  case Notification::SelectRun:
+    processSelectRun();
+    break;
+  }
+}
+
+void EnggDiffMultiRunFittingWidgetPresenter::processSelectRun() {
+  const auto selectedRunLabel = m_view->getSelectedRunLabel();
+  updatePlot(selectedRunLabel);
+}
+
+void EnggDiffMultiRunFittingWidgetPresenter::updatePlot(
+    const RunLabel &runLabel) {
+  const auto focusedRun = m_model->getFocusedRun(runLabel);
+
+  if (!focusedRun) {
+    m_view->reportPlotInvalidFocusedRun(runLabel);
+  } else {
+    const auto plottableCurve = API::QwtHelper::curveDataFromWs(*focusedRun);
+
+    m_view->resetCanvas();
+    m_view->plotFocusedRun(plottableCurve);
+
+    if (m_model->hasFittedPeaksForRun(runLabel) &&
+        m_view->showFitResultsSelected()) {
+      displayFitResults(runLabel);
+    }
+  }
+}
+
 } // namespace CustomInterfaces
 } // namespace MantidQt
diff --git a/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidgetPresenter.h b/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidgetPresenter.h
index 1b493e022784fb563a819de7de2ee2105cf92a3b..e01e4c98fa04925a4691dcc7fcdfbd59720d40c4 100644
--- a/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidgetPresenter.h
+++ b/qt/scientific_interfaces/EnggDiffraction/EnggDiffMultiRunFittingWidgetPresenter.h
@@ -31,7 +31,19 @@ public:
   boost::optional<Mantid::API::MatrixWorkspace_sptr>
   getFocusedRun(const RunLabel &runLabel) const override;
 
+  void
+  notify(IEnggDiffMultiRunFittingWidgetPresenter::Notification notif) override;
+
 private:
+  void processSelectRun();
+
+  /// Display fitted peaks and any other fit information for a certain run
+  void displayFitResults(const RunLabel &runLabel);
+
+  /// Update the plot area with a focused run, and its fitted peaks if available
+  /// and requested
+  void updatePlot(const RunLabel &runLabel);
+
   std::unique_ptr<IEnggDiffMultiRunFittingWidgetModel> m_model;
 
   std::unique_ptr<IEnggDiffMultiRunFittingWidgetView> m_view;
diff --git a/qt/scientific_interfaces/EnggDiffraction/EnggDiffractionViewQtGUI.cpp b/qt/scientific_interfaces/EnggDiffraction/EnggDiffractionViewQtGUI.cpp
index dcd72906ea2098059192bd915898d68ddf78c03c..e4d8dc7f8d11dbad3d87e35d12ab2f74815cf830 100644
--- a/qt/scientific_interfaces/EnggDiffraction/EnggDiffractionViewQtGUI.cpp
+++ b/qt/scientific_interfaces/EnggDiffraction/EnggDiffractionViewQtGUI.cpp
@@ -92,7 +92,7 @@ void EnggDiffractionViewQtGUI::initLayout() {
       m_ui.tabMain, sharedView, sharedView, fullPres, fullPres, sharedView);
   m_ui.tabMain->addTab(m_fittingWidget, QString("Fitting"));
 
-  m_gsasWidget = new EnggDiffGSASFittingViewQtWidget();
+  m_gsasWidget = new EnggDiffGSASFittingViewQtWidget(sharedView);
   m_ui.tabMain->addTab(m_gsasWidget, QString("GSAS-II Refinement"));
 
   QWidget *wSettings = new QWidget(m_ui.tabMain);
diff --git a/qt/scientific_interfaces/EnggDiffraction/IEnggDiffGSASFittingModel.h b/qt/scientific_interfaces/EnggDiffraction/IEnggDiffGSASFittingModel.h
index b2d4d082828dfc8c90de4ad48cd09b42b1a0fd07..e7ec2182ba7460316a89ae14d6cbbd940cde5acd 100644
--- a/qt/scientific_interfaces/EnggDiffraction/IEnggDiffGSASFittingModel.h
+++ b/qt/scientific_interfaces/EnggDiffraction/IEnggDiffGSASFittingModel.h
@@ -31,15 +31,16 @@ public:
    @param GSASIIProjectFile Location to save the .gpx project to
    @param dMin The minimum d-spacing to use for refinement
    @param negativeWeight The weight of the penalty function
-   @return Whether the refinement was successful
+   @return String decription any failures that occurred (empty optional if
+   success)
    */
-  virtual bool doPawleyRefinement(const RunLabel &runLabel,
-                                  const std::string &instParamFile,
-                                  const std::vector<std::string> &phaseFiles,
-                                  const std::string &pathToGSASII,
-                                  const std::string &GSASIIProjectFile,
-                                  const double dMin,
-                                  const double negativeWeight) = 0;
+  virtual boost::optional<std::string>
+  doPawleyRefinement(const RunLabel &runLabel, const std::string &instParamFile,
+                     const std::vector<std::string> &phaseFiles,
+                     const std::string &pathToGSASII,
+                     const std::string &GSASIIProjectFile, const double dMin,
+                     const double negativeWeight) = 0;
+
   /**
    Perform a Rietveld refinement on a run
    @param runLabel Run number and bank ID of the workspace to refine
@@ -49,13 +50,15 @@ public:
    @param pathToGSASII Location of the directory containing GSASIIscriptable.py
    (and GSAS-II executables)
    @param GSASIIProjectFile Location to save the .gpx project to
-   @return Whether the refinement was successful
+   @return String decription any failures that occurred (empty optional if
+   success)
    */
-  virtual bool doRietveldRefinement(const RunLabel &runLabel,
-                                    const std::string &instParamFile,
-                                    const std::vector<std::string> &phaseFiles,
-                                    const std::string &pathToGSASII,
-                                    const std::string &GSASIIProjectFile) = 0;
+  virtual boost::optional<std::string>
+  doRietveldRefinement(const RunLabel &runLabel,
+                       const std::string &instParamFile,
+                       const std::vector<std::string> &phaseFiles,
+                       const std::string &pathToGSASII,
+                       const std::string &GSASIIProjectFile) = 0;
 
   /**
    Get the fit results for a given run
@@ -109,9 +112,11 @@ public:
   /**
    Load a focused run from a file to the model
    @param filename The name of the file to load
-   @return Whether the load was a success
+   @return Empty optional if load was a success, string describing failure if
+   not
    */
-  virtual bool loadFocusedRun(const std::string &filename) = 0;
+  virtual boost::optional<std::string>
+  loadFocusedRun(const std::string &filename) = 0;
 };
 
 } // namespace MantidQt
diff --git a/qt/scientific_interfaces/EnggDiffraction/IEnggDiffGSASFittingView.h b/qt/scientific_interfaces/EnggDiffraction/IEnggDiffGSASFittingView.h
index de910fb233e4809240c4aecd6b2ea2a811160c99..5d64033a3efa28674901dd3a9f824a2575a9d5df 100644
--- a/qt/scientific_interfaces/EnggDiffraction/IEnggDiffGSASFittingView.h
+++ b/qt/scientific_interfaces/EnggDiffraction/IEnggDiffGSASFittingView.h
@@ -106,11 +106,21 @@ public:
    */
   virtual void updateRunList(const std::vector<RunLabel> &runLabels) = 0;
 
+  /**
+   Display an error to the user
+   @param errorTitle Title of the error
+   @param errorDescription Longer description of the error
+  */
+  virtual void userError(const std::string &errorTitle,
+                         const std::string &errorDescription) const = 0;
+
   /**
    Display a warning to the user
-   @param warningDescription The warning to show
+   @param warningTitle Title of the warning
+   @param warningDescription Longer description of the warning
    */
-  virtual void userWarning(const std::string &warningDescription) const = 0;
+  virtual void userWarning(const std::string &warningTitle,
+                           const std::string &warningDescription) const = 0;
 };
 
 } // namespace CustomInterfaces
diff --git a/qt/scientific_interfaces/EnggDiffraction/IEnggDiffMultiRunFittingWidgetModel.h b/qt/scientific_interfaces/EnggDiffraction/IEnggDiffMultiRunFittingWidgetModel.h
index 0220f60a0e616fd07ee43da6b6caaed91f11034d..2c3c0a19a1739e56a10d4c9bbddd724211dde14f 100644
--- a/qt/scientific_interfaces/EnggDiffraction/IEnggDiffMultiRunFittingWidgetModel.h
+++ b/qt/scientific_interfaces/EnggDiffraction/IEnggDiffMultiRunFittingWidgetModel.h
@@ -52,6 +52,9 @@ public:
   */
   virtual boost::optional<Mantid::API::MatrixWorkspace_sptr>
   getFocusedRun(const RunLabel &runLabel) const = 0;
+
+  /// Get whether the model contains fitted peaks for a given run
+  virtual bool hasFittedPeaksForRun(const RunLabel &runLabel) const = 0;
 };
 
 } // namespace MantidQt
diff --git a/qt/scientific_interfaces/EnggDiffraction/IEnggDiffMultiRunFittingWidgetPresenter.h b/qt/scientific_interfaces/EnggDiffraction/IEnggDiffMultiRunFittingWidgetPresenter.h
index 35c0fe606df3c67da7ea84fbc607cf8d52a86fc4..2b89cab94f3a8e3b16881dfde983de684dd04c41 100644
--- a/qt/scientific_interfaces/EnggDiffraction/IEnggDiffMultiRunFittingWidgetPresenter.h
+++ b/qt/scientific_interfaces/EnggDiffraction/IEnggDiffMultiRunFittingWidgetPresenter.h
@@ -14,6 +14,12 @@ class IEnggDiffMultiRunFittingWidgetPresenter {
 public:
   virtual ~IEnggDiffMultiRunFittingWidgetPresenter() = default;
 
+  // User actions, triggered by the (passive) view,
+  // which need handling in implementation
+  enum class Notification {
+    SelectRun, ///< The user has selected a new run from the list
+  };
+
   /**
    Add a fitted peaks workspace to the widget, so it can be overplotted on its
    focused run
@@ -49,6 +55,16 @@ public:
   */
   virtual boost::optional<Mantid::API::MatrixWorkspace_sptr>
   getFocusedRun(const RunLabel &runLabel) const = 0;
+
+  /**
+   * Notifications sent through the presenter when something changes
+   * in the view. This plays the role of signals emitted by the view
+   * to this presenter.
+   *
+   * @param notif Type of notification to process.
+   */
+  virtual void
+  notify(IEnggDiffMultiRunFittingWidgetPresenter::Notification notif) = 0;
 };
 
 } // namespace CustomInterfaces
diff --git a/qt/scientific_interfaces/EnggDiffraction/IEnggDiffMultiRunFittingWidgetView.h b/qt/scientific_interfaces/EnggDiffraction/IEnggDiffMultiRunFittingWidgetView.h
index fed03b67328607d0f20d2fac2fbe64324a47cc93..d9eaaacacfc3e3865f7df02be1afe6146882d2b7 100644
--- a/qt/scientific_interfaces/EnggDiffraction/IEnggDiffMultiRunFittingWidgetView.h
+++ b/qt/scientific_interfaces/EnggDiffraction/IEnggDiffMultiRunFittingWidgetView.h
@@ -6,6 +6,7 @@
 #include "MantidAPI/MatrixWorkspace.h"
 
 #include <boost/optional.hpp>
+#include <qwt_data.h>
 #include <vector>
 
 namespace MantidQt {
@@ -15,8 +16,42 @@ class IEnggDiffMultiRunFittingWidgetView {
 public:
   virtual ~IEnggDiffMultiRunFittingWidgetView() = default;
 
+  /// Get run number and bank ID of the run currently selected in the list
+  virtual RunLabel getSelectedRunLabel() const = 0;
+
+  /// Plot a Qwt curve representing a fitted peaks workspace to the canvas
+  virtual void
+  plotFittedPeaks(const std::vector<boost::shared_ptr<QwtData>> &curve) = 0;
+
+  /// Plot a Qwt curve representing a focused run to the canvas
+  virtual void
+  plotFocusedRun(const std::vector<boost::shared_ptr<QwtData>> &curve) = 0;
+
+  /**
+   Show an error that the user has tried to plot an invalid fitted peaks
+   workspace
+   @param runLabel Label of that workspace
+   */
+  virtual void reportPlotInvalidFittedPeaks(const RunLabel &runLabel) = 0;
+
+  /**
+   Show an error that the user has tried to plot an invalid focused run
+   @param runLabel Label of that run
+   */
+  virtual void reportPlotInvalidFocusedRun(const RunLabel &runLabel) = 0;
+
+  /// Clear the plot area to avoid overplotting
+  virtual void resetCanvas() = 0;
+
+  /// Get whether the user has selected to overplot fit results
+  virtual bool showFitResultsSelected() const = 0;
+
   /// Update the list of loaded run numbers and bank IDs
   virtual void updateRunList(const std::vector<RunLabel> &runLabels) = 0;
+
+  /// Report an error to the user
+  virtual void userError(const std::string &errorTitle,
+                         const std::string &errorDescription) = 0;
 };
 
 } // CustomInterfaces
diff --git a/qt/scientific_interfaces/General/CMakeLists.txt b/qt/scientific_interfaces/General/CMakeLists.txt
index 527a2876ce5f98bce4aa6e55f04ebd0a5d916ddc..6668881e838e6e44d0c864def01f75faad5839fe 100644
--- a/qt/scientific_interfaces/General/CMakeLists.txt
+++ b/qt/scientific_interfaces/General/CMakeLists.txt
@@ -62,4 +62,6 @@ mtd_add_qt_library (TARGET_NAME MantidScientificInterfacesGeneral
       ${PLUGINS_DIR}
   OSX_INSTALL_RPATH
     @loader_path/../../Contents/MacOS
+  LINUX_INSTALL_RPATH
+    "\$ORIGIN/../../${LIB_DIR}"
 )
diff --git a/qt/scientific_interfaces/General/ReflMainWidget.ui b/qt/scientific_interfaces/General/ReflMainWidget.ui
index bb1e5d55fab2383b4becc9b34e6798d9933ab21c..5f70b4792c904969b903061fdefa06406c940b7b 100644
--- a/qt/scientific_interfaces/General/ReflMainWidget.ui
+++ b/qt/scientific_interfaces/General/ReflMainWidget.ui
@@ -15,7 +15,16 @@
   </property>
   <widget class="QWidget" name="centralwidget">
    <layout class="QVBoxLayout" name="layoutMain">
-    <property name="margin">
+    <property name="leftMargin">
+     <number>1</number>
+    </property>
+    <property name="topMargin">
+     <number>1</number>
+    </property>
+    <property name="rightMargin">
+     <number>1</number>
+    </property>
+    <property name="bottomMargin">
      <number>1</number>
     </property>
     <item>
@@ -33,6 +42,9 @@
        <bool>true</bool>
       </property>
       <widget class="QGroupBox" name="groupSearchPane">
+       <property name="toolTip">
+        <string/>
+       </property>
        <property name="title">
         <string>Search Runs</string>
        </property>
@@ -40,7 +52,16 @@
         <property name="spacing">
          <number>1</number>
         </property>
-        <property name="margin">
+        <property name="leftMargin">
+         <number>1</number>
+        </property>
+        <property name="topMargin">
+         <number>1</number>
+        </property>
+        <property name="rightMargin">
+         <number>1</number>
+        </property>
+        <property name="bottomMargin">
          <number>1</number>
         </property>
         <item>
@@ -73,7 +94,7 @@
              </size>
             </property>
             <property name="toolTip">
-             <string>Select the instrument to search with</string>
+             <string>The instrument used for this investigation</string>
             </property>
             <property name="whatsThis">
              <string>Specifies which instrument you're searching for data from.</string>
@@ -111,7 +132,7 @@
              </size>
             </property>
             <property name="toolTip">
-             <string>Investigation to search for</string>
+             <string>ID of the investigation runs to search for</string>
             </property>
             <property name="whatsThis">
              <string>Specifies the investigation id that you are searching for runs from.</string>
@@ -133,7 +154,7 @@
              </sizepolicy>
             </property>
             <property name="toolTip">
-             <string>Search</string>
+             <string>Search for runs using ICAT</string>
             </property>
             <property name="whatsThis">
              <string>Searches ICAT for runs from the given instrument with the given investigation id.</string>
@@ -145,6 +166,9 @@
           </item>
           <item row="2" column="0">
            <widget class="QComboBox" name="comboTransferMethod">
+            <property name="toolTip">
+             <string>The method used to transfer runs into the processing table</string>
+            </property>
             <property name="whatsThis">
              <string>The strategy for searching and transfering files. See help.</string>
             </property>
@@ -197,6 +221,9 @@
           </item>
           <item>
            <widget class="QToolButton" name="buttonTransfer">
+            <property name="toolTip">
+             <string>Transfer selected results into the processing table</string>
+            </property>
             <property name="text">
              <string>Transfer</string>
             </property>
@@ -217,7 +244,16 @@
         <property name="spacing">
          <number>1</number>
         </property>
-        <property name="margin">
+        <property name="leftMargin">
+         <number>1</number>
+        </property>
+        <property name="topMargin">
+         <number>1</number>
+        </property>
+        <property name="rightMargin">
+         <number>1</number>
+        </property>
+        <property name="bottomMargin">
          <number>1</number>
         </property>
         <item>
@@ -286,11 +322,23 @@
           </item>
           <item>
            <layout class="QHBoxLayout" name="layoutRowButtons">
-            <property name="margin">
+            <property name="leftMargin">
+             <number>1</number>
+            </property>
+            <property name="topMargin">
+             <number>1</number>
+            </property>
+            <property name="rightMargin">
+             <number>1</number>
+            </property>
+            <property name="bottomMargin">
              <number>1</number>
             </property>
             <item>
              <widget class="QProgressBar" name="progressBar">
+              <property name="toolTip">
+               <string>Progress of the active reduction</string>
+              </property>
               <property name="whatsThis">
                <string>Shows the current progress when processing.</string>
               </property>
@@ -324,6 +372,9 @@
             </item>
             <item>
              <widget class="QCheckBox" name="checkEnableNotebook">
+              <property name="toolTip">
+               <string>Tick to generate a python notebook when performing a reduction.</string>
+              </property>
               <property name="text">
                <string>Output Notebook</string>
               </property>
@@ -331,6 +382,9 @@
             </item>
             <item>
              <widget class="QToolButton" name="buttonProcess">
+              <property name="toolTip">
+               <string>Begin processing runs from the table</string>
+              </property>
               <property name="text">
                <string>Process</string>
               </property>
@@ -358,7 +412,7 @@
      <x>0</x>
      <y>0</y>
      <width>959</width>
-     <height>18</height>
+     <height>28</height>
     </rect>
    </property>
    <property name="nativeMenuBar">
@@ -373,7 +427,7 @@
       <string>Open Table</string>
      </property>
      <property name="icon">
-      <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+      <iconset>
        <normaloff>:/multiload.png</normaloff>:/multiload.png</iconset>
      </property>
     </widget>
@@ -415,7 +469,7 @@
   </widget>
   <action name="actionNewTable">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/new.png</normaloff>:/new.png</iconset>
    </property>
    <property name="text">
@@ -427,7 +481,7 @@
   </action>
   <action name="actionSaveTable">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/filesave.png</normaloff>:/filesave.png</iconset>
    </property>
    <property name="text">
@@ -439,7 +493,7 @@
   </action>
   <action name="actionSaveTableAs">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/filesaveas.png</normaloff>:/filesaveas.png</iconset>
    </property>
    <property name="text">
@@ -451,7 +505,7 @@
   </action>
   <action name="actionAppendRow">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/insert_row.png</normaloff>:/insert_row.png</iconset>
    </property>
    <property name="text">
@@ -466,7 +520,7 @@
   </action>
   <action name="actionPrependRow">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/insert_row.png</normaloff>:/insert_row.png</iconset>
    </property>
    <property name="text">
@@ -481,7 +535,7 @@
   </action>
   <action name="actionDeleteRow">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/delete_row.png</normaloff>:/delete_row.png</iconset>
    </property>
    <property name="text">
@@ -493,7 +547,7 @@
   </action>
   <action name="actionGroupRows">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/drag_curves.png</normaloff>:/drag_curves.png</iconset>
    </property>
    <property name="text">
@@ -505,7 +559,7 @@
   </action>
   <action name="actionProcess">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/stat_rows.png</normaloff>:/stat_rows.png</iconset>
    </property>
    <property name="text">
@@ -517,7 +571,7 @@
   </action>
   <action name="actionExpandSelection">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/fit_frame.png</normaloff>:/fit_frame.png</iconset>
    </property>
    <property name="text">
@@ -532,7 +586,7 @@
   </action>
   <action name="actionOptionsDialog">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/configure.png</normaloff>:/configure.png</iconset>
    </property>
    <property name="text">
@@ -547,7 +601,7 @@
   </action>
   <action name="actionClearSelected">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/erase.png</normaloff>:/erase.png</iconset>
    </property>
    <property name="text">
@@ -559,7 +613,7 @@
   </action>
   <action name="actionCopySelected">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/copy.png</normaloff>:/copy.png</iconset>
    </property>
    <property name="text">
@@ -574,7 +628,7 @@
   </action>
   <action name="actionPasteSelected">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/paste.png</normaloff>:/paste.png</iconset>
    </property>
    <property name="text">
@@ -589,7 +643,7 @@
   </action>
   <action name="actionCutSelected">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/cut.png</normaloff>:/cut.png</iconset>
    </property>
    <property name="text">
@@ -604,7 +658,7 @@
   </action>
   <action name="actionSearch">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/folder.png</normaloff>:/folder.png</iconset>
    </property>
    <property name="text">
@@ -613,7 +667,7 @@
   </action>
   <action name="actionTransfer">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/append_drag_curves.png</normaloff>:/append_drag_curves.png</iconset>
    </property>
    <property name="text">
@@ -628,7 +682,7 @@
   </action>
   <action name="actionImportTable">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/open_template.png</normaloff>:/open_template.png</iconset>
    </property>
    <property name="text">
@@ -643,7 +697,7 @@
   </action>
   <action name="actionExportTable">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/save_template.png</normaloff>:/save_template.png</iconset>
    </property>
    <property name="text">
@@ -658,7 +712,7 @@
   </action>
   <action name="actionHelp">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/help.png</normaloff>:/help.png</iconset>
    </property>
    <property name="text">
@@ -676,7 +730,7 @@
   </action>
   <action name="actionPlotRow">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/graph.png</normaloff>:/graph.png</iconset>
    </property>
    <property name="text">
@@ -691,7 +745,7 @@
   </action>
   <action name="actionPlotGroup">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/trajectory.png</normaloff>:/trajectory.png</iconset>
    </property>
    <property name="text">
@@ -706,7 +760,7 @@
   </action>
   <action name="actionSlitCalculator">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/param_range_btn.png</normaloff>:/param_range_btn.png</iconset>
    </property>
    <property name="text">
diff --git a/qt/scientific_interfaces/ISISReflectometry/CMakeLists.txt b/qt/scientific_interfaces/ISISReflectometry/CMakeLists.txt
index 5b6740bbcbe3050c9734234b8d8b55ff7c930071..c19ec5c7672d3fa9f5623d32a1612c43a7ceff4c 100644
--- a/qt/scientific_interfaces/ISISReflectometry/CMakeLists.txt
+++ b/qt/scientific_interfaces/ISISReflectometry/CMakeLists.txt
@@ -91,6 +91,7 @@ set ( MOC_FILES
     QtReflSaveTabView.h
     QtReflSettingsTabView.h
     QtReflSettingsView.h
+    QtReflMainWindowView.h
 )
 
 set ( UI_FILES
@@ -128,4 +129,6 @@ mtd_add_qt_library (TARGET_NAME MantidScientificInterfacesISISReflectometry
       ${PLUGINS_DIR}
   OSX_INSTALL_RPATH
     @loader_path/../../Contents/MacOS
+  LINUX_INSTALL_RPATH
+    "\$ORIGIN/../../${LIB_DIR}"
 )
diff --git a/qt/scientific_interfaces/ISISReflectometry/IReflEventPresenter.h b/qt/scientific_interfaces/ISISReflectometry/IReflEventPresenter.h
index e39d2ece76f8adbe64cce9cb808d973ed55d7a6d..a2b5ed7d775ca9476e6eb8aa413136539e992b83 100644
--- a/qt/scientific_interfaces/ISISReflectometry/IReflEventPresenter.h
+++ b/qt/scientific_interfaces/ISISReflectometry/IReflEventPresenter.h
@@ -42,6 +42,9 @@ public:
   virtual std::string getTimeSlicingValues() const = 0;
   /// Time-slicing type
   virtual std::string getTimeSlicingType() const = 0;
+
+  virtual void onReductionPaused() = 0;
+  virtual void onReductionResumed() = 0;
 };
 }
 }
diff --git a/qt/scientific_interfaces/ISISReflectometry/IReflEventTabPresenter.h b/qt/scientific_interfaces/ISISReflectometry/IReflEventTabPresenter.h
index ecef7d9f04e12ba881c6b06c13be7af5303f5d6f..379f3674fbc62a850ece8c10682117123bf52ae6 100644
--- a/qt/scientific_interfaces/ISISReflectometry/IReflEventTabPresenter.h
+++ b/qt/scientific_interfaces/ISISReflectometry/IReflEventTabPresenter.h
@@ -41,6 +41,9 @@ public:
   virtual std::string getTimeSlicingValues(int group) const = 0;
   /// Time-slicing type
   virtual std::string getTimeSlicingType(int group) const = 0;
+
+  virtual void onReductionPaused(int group) = 0;
+  virtual void onReductionResumed(int group) = 0;
 };
 }
 }
diff --git a/qt/scientific_interfaces/ISISReflectometry/IReflEventView.h b/qt/scientific_interfaces/ISISReflectometry/IReflEventView.h
index 8ce9939fcae0ae44c6c970d5ac7ff13e9cdde153..ce247af9d75a74e8acb1d68ad0705c5bd4d3c13e 100644
--- a/qt/scientific_interfaces/ISISReflectometry/IReflEventView.h
+++ b/qt/scientific_interfaces/ISISReflectometry/IReflEventView.h
@@ -51,6 +51,9 @@ public:
 
   virtual std::string getTimeSlicingValues() const = 0;
   virtual std::string getTimeSlicingType() const = 0;
+
+  virtual void enableAll() = 0;
+  virtual void disableAll() = 0;
 };
 }
 }
diff --git a/qt/scientific_interfaces/ISISReflectometry/IReflMainWindowPresenter.h b/qt/scientific_interfaces/ISISReflectometry/IReflMainWindowPresenter.h
index 2ced88b289ab503cef1becb809cc972b008f9d9d..877c5ae68c751adb47fab26048e9628084dd36db 100644
--- a/qt/scientific_interfaces/ISISReflectometry/IReflMainWindowPresenter.h
+++ b/qt/scientific_interfaces/ISISReflectometry/IReflMainWindowPresenter.h
@@ -39,9 +39,15 @@ class IReflMainWindowPresenter {
 public:
   /// Destructor
   virtual ~IReflMainWindowPresenter(){};
+  enum class Flag {
+    ConfirmReductionPausedFlag,
+    ConfirmReductionResumedFlag,
+    HelpPressed
+  };
 
-  enum class Flag { ConfirmReductionPausedFlag, ConfirmReductionResumedFlag };
   virtual void notify(Flag flag) = 0;
+  virtual void notifyReductionPaused(int group) = 0;
+  virtual void notifyReductionResumed(int group) = 0;
 
   /// Pre-processing
   virtual std::string getTransmissionRuns(int group) const = 0;
diff --git a/qt/scientific_interfaces/ISISReflectometry/IReflSaveTabPresenter.h b/qt/scientific_interfaces/ISISReflectometry/IReflSaveTabPresenter.h
index 81c387769151f0bf3d58a4e57539e07714e5c9ae..88b851028eac9d0ce02371282c364805c9e6476e 100644
--- a/qt/scientific_interfaces/ISISReflectometry/IReflSaveTabPresenter.h
+++ b/qt/scientific_interfaces/ISISReflectometry/IReflSaveTabPresenter.h
@@ -48,6 +48,8 @@ public:
 
   /// Tell the presenter something happened
   virtual void notify(IReflSaveTabPresenter::Flag flag) = 0;
+  virtual void onAnyReductionPaused() = 0;
+  virtual void onAnyReductionResumed() = 0;
 };
 }
 }
diff --git a/qt/scientific_interfaces/ISISReflectometry/IReflSettingsPresenter.h b/qt/scientific_interfaces/ISISReflectometry/IReflSettingsPresenter.h
index c809b910200be82f6c0e5c620e9e921a65692db5..b7728edbf8e77d5a417f060f176f43f85f12b2b0 100644
--- a/qt/scientific_interfaces/ISISReflectometry/IReflSettingsPresenter.h
+++ b/qt/scientific_interfaces/ISISReflectometry/IReflSettingsPresenter.h
@@ -3,6 +3,7 @@
 
 #include "IReflSettingsTabPresenter.h"
 #include "MantidQtWidgets/Common/DataProcessorUI/OptionsQMap.h"
+#include "MantidAPI/Algorithm.h"
 
 #include <string>
 
@@ -44,6 +45,7 @@ public:
   virtual MantidWidgets::DataProcessor::OptionsQMap
   getTransmissionOptions() const = 0;
   /// Processing
+  virtual Mantid::API::IAlgorithm_sptr createReductionAlg() = 0;
   virtual MantidWidgets::DataProcessor::OptionsQMap
   getReductionOptions() const = 0;
   /// Post-processing
@@ -62,6 +64,9 @@ public:
   virtual void notify(IReflSettingsPresenter::Flag flag) = 0;
   /// Set current instrument name
   virtual void setInstrumentName(const std::string &instName) = 0;
+
+  virtual void onReductionPaused() = 0;
+  virtual void onReductionResumed() = 0;
 };
 }
 }
diff --git a/qt/scientific_interfaces/ISISReflectometry/IReflSettingsTabPresenter.h b/qt/scientific_interfaces/ISISReflectometry/IReflSettingsTabPresenter.h
index a0ba40b2364e6344602fbaa540bed165779efdd8..b4669987547f7282ca42459342f4ab411aabe5c9 100644
--- a/qt/scientific_interfaces/ISISReflectometry/IReflSettingsTabPresenter.h
+++ b/qt/scientific_interfaces/ISISReflectometry/IReflSettingsTabPresenter.h
@@ -52,6 +52,8 @@ public:
   virtual void setInstrumentName(const std::string &instName) = 0;
   virtual void acceptMainPresenter(IReflMainWindowPresenter *mainPresenter) = 0;
   virtual void settingsChanged(int group) = 0;
+  virtual void onReductionPaused(int group) = 0;
+  virtual void onReductionResumed(int group) = 0;
 };
 }
 }
diff --git a/qt/scientific_interfaces/ISISReflectometry/IReflSettingsView.h b/qt/scientific_interfaces/ISISReflectometry/IReflSettingsView.h
index 8cd533212452738daa7f1c65574b7d221ce2fb24..079033be3fa04d0f41ee88be310f4761e4d01de0 100644
--- a/qt/scientific_interfaces/ISISReflectometry/IReflSettingsView.h
+++ b/qt/scientific_interfaces/ISISReflectometry/IReflSettingsView.h
@@ -4,6 +4,7 @@
 #include "DllConfig.h"
 #include <map>
 #include <vector>
+#include "MantidAPI/Algorithm.h"
 #include "ExperimentOptionDefaults.h"
 #include "InstrumentOptionDefaults.h"
 #include "GetInstrumentParameter.h"
@@ -99,6 +100,8 @@ public:
   /// Set polarisation corrections and parameters enabled/disabled
   virtual void setPolarisationOptionsEnabled(bool enable) = 0;
   virtual void setDetectorCorrectionEnabled(bool enable) = 0;
+  virtual void disableAll() = 0;
+  virtual void enableAll() = 0;
 };
 }
 }
diff --git a/qt/scientific_interfaces/ISISReflectometry/QtReflEventView.cpp b/qt/scientific_interfaces/ISISReflectometry/QtReflEventView.cpp
index 3eeee9f49e691f440fe00735485a6b995fbc9f29..d18997387347c9da77bbb8f4424c1f807aaaf949 100644
--- a/qt/scientific_interfaces/ISISReflectometry/QtReflEventView.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/QtReflEventView.cpp
@@ -20,10 +20,10 @@ QtReflEventView::QtReflEventView(QWidget *parent) {
   m_sliceTypeMap[SliceType::LogValue] = "LogValue";
 
   // Add slicing option buttons to list
-  m_buttonList.push_back(m_ui.uniformEvenButton);
-  m_buttonList.push_back(m_ui.uniformButton);
-  m_buttonList.push_back(m_ui.customButton);
-  m_buttonList.push_back(m_ui.logValueButton);
+  m_buttonList.emplace_back(m_ui.uniformEvenButton);
+  m_buttonList.emplace_back(m_ui.uniformButton);
+  m_buttonList.emplace_back(m_ui.customButton);
+  m_buttonList.emplace_back(m_ui.logValueButton);
 
   // Whenever one of the slicing option buttons is selected, their corresponding
   // entry is enabled, otherwise they remain disabled.
@@ -54,6 +54,24 @@ IReflEventPresenter *QtReflEventView::getPresenter() const {
   return m_presenter.get();
 }
 
+void QtReflEventView::enableAll() {
+  m_ui.uniformEvenEdit->setEnabled(true);
+  m_ui.uniformEdit->setEnabled(true);
+  m_ui.logValueEdit->setEnabled(true);
+  m_ui.logValueTypeEdit->setEnabled(true);
+  for (auto *button : m_buttonList)
+    button->setEnabled(true);
+}
+
+void QtReflEventView::disableAll() {
+  m_ui.uniformEvenEdit->setEnabled(false);
+  m_ui.uniformEdit->setEnabled(false);
+  m_ui.logValueEdit->setEnabled(false);
+  m_ui.logValueTypeEdit->setEnabled(false);
+  for (auto *button : m_buttonList)
+    button->setEnabled(false);
+}
+
 /** Returns the time slicing value(s) obtained from the selected widget
 * @return :: Time slicing values
 */
diff --git a/qt/scientific_interfaces/ISISReflectometry/QtReflEventView.h b/qt/scientific_interfaces/ISISReflectometry/QtReflEventView.h
index 9ed94a5fbf7fe340b53eece01406c04ac0991097..c2a4f749da9734b97045f61ffa6444fa15564a9f 100644
--- a/qt/scientific_interfaces/ISISReflectometry/QtReflEventView.h
+++ b/qt/scientific_interfaces/ISISReflectometry/QtReflEventView.h
@@ -50,6 +50,9 @@ public:
   /// Returns time-slicing type
   std::string getTimeSlicingType() const override;
 
+  void enableAll() override;
+  void disableAll() override;
+
 public slots:
   /// Enable / disable slicing option entry fields
   void toggleSlicingOptions() const;
diff --git a/qt/scientific_interfaces/ISISReflectometry/QtReflMainWindowView.cpp b/qt/scientific_interfaces/ISISReflectometry/QtReflMainWindowView.cpp
index 5261dde7123760a42f3f1251e7ff8f5c3c2c2fa4..ee29e44a1c13a1c28e5bc4892342ac0be6b868ba 100644
--- a/qt/scientific_interfaces/ISISReflectometry/QtReflMainWindowView.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/QtReflMainWindowView.cpp
@@ -35,11 +35,17 @@ void QtReflMainWindowView::initLayout() {
   auto settingsPresenter = createSettingsTab();
   auto savePresenter = createSaveTab();
 
+  connect(m_ui.helpButton, SIGNAL(clicked()), this, SLOT(helpPressed()));
+
   // Create the presenter
   m_presenter.reset(new ReflMainWindowPresenter(
       this, runsPresenter, eventPresenter, settingsPresenter, savePresenter));
 }
 
+void QtReflMainWindowView::helpPressed() {
+  m_presenter->notify(IReflMainWindowPresenter::Flag::HelpPressed);
+}
+
 /** Creates the 'Runs' tab and returns a pointer to its presenter
 * @return :: A pointer to the presenter managing the 'Runs' tab
 */
diff --git a/qt/scientific_interfaces/ISISReflectometry/QtReflMainWindowView.h b/qt/scientific_interfaces/ISISReflectometry/QtReflMainWindowView.h
index 7916589ac740c2a535d7ebf025cfdeeb0f777f83..6cb5f70be8faa9205ce42622e159ed3632299e3f 100644
--- a/qt/scientific_interfaces/ISISReflectometry/QtReflMainWindowView.h
+++ b/qt/scientific_interfaces/ISISReflectometry/QtReflMainWindowView.h
@@ -44,6 +44,7 @@ Code Documentation is available at: <http://doxygen.mantidproject.org>
 */
 class QtReflMainWindowView : public MantidQt::API::UserSubWindow,
                              public IReflMainWindowView {
+  Q_OBJECT
 public:
   /// Constructor
   explicit QtReflMainWindowView(QWidget *parent = nullptr);
@@ -66,6 +67,9 @@ public:
   /// Close window handler
   void closeEvent(QCloseEvent *event) override;
 
+public slots:
+  void helpPressed();
+
 private:
   /// Initializes the interface
   void initLayout() override;
diff --git a/qt/scientific_interfaces/ISISReflectometry/QtReflRunsTabView.cpp b/qt/scientific_interfaces/ISISReflectometry/QtReflRunsTabView.cpp
index 2a84a03d60b455b810243539ebe8dfbf74b175d2..9620ab668cec194da162298c8f99fef54f23959c 100644
--- a/qt/scientific_interfaces/ISISReflectometry/QtReflRunsTabView.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/QtReflRunsTabView.cpp
@@ -52,7 +52,7 @@ void QtReflRunsTabView::initLayout() {
 
   QDataProcessorWidget *qDataProcessorWidget_1 = new QDataProcessorWidget(
       std::unique_ptr<DataProcessor::DataProcessorPresenter>(
-          presenterFactory.create()),
+          presenterFactory.create(0)),
       this);
   ui.toolbox->addItem(qDataProcessorWidget_1, "Group 1");
   connect(qDataProcessorWidget_1,
@@ -61,7 +61,7 @@ void QtReflRunsTabView::initLayout() {
 
   QDataProcessorWidget *qDataProcessorWidget_2 = new QDataProcessorWidget(
       std::unique_ptr<DataProcessor::DataProcessorPresenter>(
-          presenterFactory.create()),
+          presenterFactory.create(1)),
       this);
   ui.toolbox->addItem(qDataProcessorWidget_2, "Group 2");
   connect(qDataProcessorWidget_2,
diff --git a/qt/scientific_interfaces/ISISReflectometry/QtReflSettingsView.cpp b/qt/scientific_interfaces/ISISReflectometry/QtReflSettingsView.cpp
index 80f4cc04f8a32dece3eee2e18d000f27038f50af..2d2179b459c7f0b44e3ab4085cc6af3ee02c4e93 100644
--- a/qt/scientific_interfaces/ISISReflectometry/QtReflSettingsView.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/QtReflSettingsView.cpp
@@ -20,8 +20,9 @@ QtReflSettingsView::QtReflSettingsView(int group, QWidget *parent) {
 
   UNUSED_ARG(parent);
   initLayout();
-
-  m_presenter.reset(new ReflSettingsPresenter(this, group));
+  m_presenter = Mantid::Kernel::make_unique<ReflSettingsPresenter>(this, group);
+  auto alg = m_presenter->createReductionAlg();
+  registerSettingsWidgets(alg);
 }
 
 //----------------------------------------------------------------------------------------------
@@ -45,63 +46,85 @@ void QtReflSettingsView::initLayout() {
           SLOT(summationTypeChanged(int)));
   connect(m_ui.correctDetectorsCheckBox, SIGNAL(clicked(bool)), this,
           SLOT(setDetectorCorrectionEnabled(bool)));
-
-  connectChangeListeners();
 }
 
-void QtReflSettingsView::connectSettingsChange(QLineEdit *edit) {
-  connect(edit, SIGNAL(textChanged(QString const &)), this,
+void QtReflSettingsView::connectSettingsChange(QLineEdit &edit) {
+  connect(&edit, SIGNAL(textChanged(QString const &)), this,
           SLOT(notifySettingsChanged()));
 }
 
-void QtReflSettingsView::connectSettingsChange(QComboBox *edit) {
-  connect(edit, SIGNAL(currentIndexChanged(int)), this,
+void QtReflSettingsView::connectSettingsChange(QComboBox &edit) {
+  connect(&edit, SIGNAL(currentIndexChanged(int)), this,
           SLOT(notifySettingsChanged()));
 }
 
-void QtReflSettingsView::connectSettingsChange(QCheckBox *edit) {
-  connect(edit, SIGNAL(stateChanged(int)), this, SLOT(notifySettingsChanged()));
-}
-
-void QtReflSettingsView::connectSettingsChange(QGroupBox *edit) {
-  connect(edit, SIGNAL(toggled(bool)), this, SLOT(notifySettingsChanged()));
-}
-
-void QtReflSettingsView::connectChangeListeners() {
-  connectExperimentSettingsChangeListeners();
-  connectInstrumentSettingsChangeListeners();
-}
-
-void QtReflSettingsView::connectInstrumentSettingsChangeListeners() {
-  connectSettingsChange(m_ui.instSettingsGroup);
-  connectSettingsChange(m_ui.intMonCheckBox);
-  connectSettingsChange(m_ui.monIntMinEdit);
-  connectSettingsChange(m_ui.monIntMaxEdit);
-  connectSettingsChange(m_ui.monBgMinEdit);
-  connectSettingsChange(m_ui.monBgMaxEdit);
-  connectSettingsChange(m_ui.lamMinEdit);
-  connectSettingsChange(m_ui.lamMaxEdit);
-  connectSettingsChange(m_ui.I0MonIndexEdit);
-  connectSettingsChange(m_ui.procInstEdit);
-  connectSettingsChange(m_ui.detectorCorrectionTypeComboBox);
-  connectSettingsChange(m_ui.correctDetectorsCheckBox);
-  connectSettingsChange(m_ui.reductionTypeComboBox);
-  connectSettingsChange(m_ui.summationTypeComboBox);
+void QtReflSettingsView::connectSettingsChange(QCheckBox &edit) {
+  connect(&edit, SIGNAL(stateChanged(int)), this,
+          SLOT(notifySettingsChanged()));
 }
 
-void QtReflSettingsView::connectExperimentSettingsChangeListeners() {
-  connectSettingsChange(m_ui.expSettingsGroup);
-  connectSettingsChange(m_ui.analysisModeComboBox);
-  connectSettingsChange(m_ui.transmissionRunsEdit);
-  connectSettingsChange(m_ui.startOverlapEdit);
-  connectSettingsChange(m_ui.endOverlapEdit);
-  connectSettingsChange(m_ui.polCorrComboBox);
-  connectSettingsChange(m_ui.CRhoEdit);
-  connectSettingsChange(m_ui.CAlphaEdit);
-  connectSettingsChange(m_ui.CApEdit);
-  connectSettingsChange(m_ui.CPpEdit);
-  connectSettingsChange(m_ui.momentumTransferStepEdit);
-  connectSettingsChange(m_ui.scaleFactorEdit);
+void QtReflSettingsView::connectSettingsChange(QGroupBox &edit) {
+  connect(&edit, SIGNAL(toggled(bool)), this, SLOT(notifySettingsChanged()));
+}
+
+void QtReflSettingsView::disableAll() {
+  m_ui.instSettingsGroup->setEnabled(false);
+  m_ui.expSettingsGroup->setEnabled(false);
+}
+
+void QtReflSettingsView::enableAll() {
+  m_ui.instSettingsGroup->setEnabled(true);
+  m_ui.expSettingsGroup->setEnabled(true);
+}
+
+void QtReflSettingsView::registerSettingsWidgets(
+    Mantid::API::IAlgorithm_sptr alg) {
+  registerExperimentSettingsWidgets(alg);
+  registerInstrumentSettingsWidgets(alg);
+}
+
+void QtReflSettingsView::registerInstrumentSettingsWidgets(
+    Mantid::API::IAlgorithm_sptr alg) {
+  connectSettingsChange(*m_ui.instSettingsGroup);
+  registerSettingWidget(*m_ui.intMonCheckBox, "NormalizeByIntegratedMonitors",
+                        alg);
+  registerSettingWidget(*m_ui.monIntMinEdit, "MonitorIntegrationWavelengthMin",
+                        alg);
+  registerSettingWidget(*m_ui.monIntMaxEdit, "MonitorIntegrationWavelengthMax",
+                        alg);
+  registerSettingWidget(*m_ui.monBgMinEdit, "MonitorBackgroundWavelengthMin",
+                        alg);
+  registerSettingWidget(*m_ui.monBgMaxEdit, "MonitorBackgroundWavelengthMax",
+                        alg);
+  registerSettingWidget(*m_ui.lamMinEdit, "WavelengthMin", alg);
+  registerSettingWidget(*m_ui.lamMaxEdit, "WavelengthMax", alg);
+  registerSettingWidget(*m_ui.I0MonIndexEdit, "I0MonitorIndex", alg);
+  registerSettingWidget(*m_ui.procInstEdit, "ProcessingInstructions", alg);
+  registerSettingWidget(*m_ui.detectorCorrectionTypeComboBox,
+                        "DetectorCorrectionType", alg);
+  registerSettingWidget(*m_ui.correctDetectorsCheckBox, "CorrectDetectors",
+                        alg);
+  registerSettingWidget(*m_ui.reductionTypeComboBox, "ReductionType", alg);
+  registerSettingWidget(*m_ui.summationTypeComboBox, "SummationType", alg);
+}
+
+void QtReflSettingsView::registerExperimentSettingsWidgets(
+    Mantid::API::IAlgorithm_sptr alg) {
+  connectSettingsChange(*m_ui.expSettingsGroup);
+  registerSettingWidget(*m_ui.analysisModeComboBox, "AnalysisMode", alg);
+  registerSettingWidget(*m_ui.transmissionRunsEdit, "FirstTransmissionRun",
+                        alg);
+  registerSettingWidget(*m_ui.startOverlapEdit, "StartOverlap", alg);
+  registerSettingWidget(*m_ui.endOverlapEdit, "EndOverlap", alg);
+  registerSettingWidget(*m_ui.polCorrComboBox, "PolarizationAnalysis", alg);
+  registerSettingWidget(*m_ui.CRhoEdit, "CRho", alg);
+  registerSettingWidget(*m_ui.CAlphaEdit, "CAlpha", alg);
+  registerSettingWidget(*m_ui.CApEdit, "CAp", alg);
+  registerSettingWidget(*m_ui.CPpEdit, "CPp", alg);
+  registerSettingWidget(*m_ui.momentumTransferStepEdit, "MomentumTransferStep",
+                        alg);
+  registerSettingWidget(*m_ui.scaleFactorEdit, "ScaleFactor", alg);
+  registerSettingWidget(stitchOptionsLineEdit(), "Params", alg);
 }
 
 void QtReflSettingsView::notifySettingsChanged() {
@@ -117,6 +140,21 @@ void QtReflSettingsView::setReductionTypeEnabled(bool enable) {
   m_ui.reductionTypeComboBox->setEnabled(enable);
 }
 
+template <typename Widget>
+void QtReflSettingsView::registerSettingWidget(
+    Widget &widget, std::string const &propertyName,
+    Mantid::API::IAlgorithm_sptr alg) {
+  connectSettingsChange(widget);
+  setToolTipAsPropertyDocumentation(widget, propertyName, alg);
+}
+
+void QtReflSettingsView::setToolTipAsPropertyDocumentation(
+    QWidget &widget, std::string const &propertyName,
+    Mantid::API::IAlgorithm_sptr alg) {
+  widget.setToolTip(QString::fromStdString(
+      alg->getPointerToProperty(propertyName)->documentation()));
+}
+
 /** Returns the presenter managing this view
 * @return :: A pointer to the presenter
 */
@@ -216,7 +254,8 @@ void QtReflSettingsView::setChecked(QCheckBox &checkBox, bool checked) {
 
 class SetI0MonIndex : public boost::static_visitor<> {
 public:
-  SetI0MonIndex(QLineEdit &I0MonIndexEdit) : m_I0monIndexEdit(I0MonIndexEdit) {}
+  explicit SetI0MonIndex(QLineEdit &I0MonIndexEdit)
+      : m_I0monIndexEdit(I0MonIndexEdit) {}
 
   void operator()(int index) const {
     m_I0monIndexEdit.setText(QString::number(index));
diff --git a/qt/scientific_interfaces/ISISReflectometry/QtReflSettingsView.h b/qt/scientific_interfaces/ISISReflectometry/QtReflSettingsView.h
index a75c1b76dbdec96b4c00cfdac8f602fa7deab867..d90c9db7517c395cb7c09b2aa69faab4a0e1b119 100644
--- a/qt/scientific_interfaces/ISISReflectometry/QtReflSettingsView.h
+++ b/qt/scientific_interfaces/ISISReflectometry/QtReflSettingsView.h
@@ -108,6 +108,8 @@ public:
   /// Creates hints for 'Stitch1DMany'
   void
   createStitchHints(const std::map<std::string, std::string> &hints) override;
+  void disableAll() override;
+  void enableAll() override;
 
   void showOptionLoadErrors(
       std::vector<InstrumentParameterTypeMissmatch> const &errors,
@@ -131,6 +133,20 @@ public slots:
 private:
   /// Initialise the interface
   void initLayout();
+  void registerSettingsWidgets(Mantid::API::IAlgorithm_sptr alg);
+  void registerInstrumentSettingsWidgets(Mantid::API::IAlgorithm_sptr alg);
+  void registerExperimentSettingsWidgets(Mantid::API::IAlgorithm_sptr alg);
+  void setToolTipAsPropertyDocumentation(QWidget &widget,
+                                         std::string const &propertyName,
+                                         Mantid::API::IAlgorithm_sptr alg);
+
+  template <typename Widget>
+  void registerSettingWidget(Widget &widget, std::string const &propertyName,
+                             Mantid::API::IAlgorithm_sptr alg);
+  void connectSettingsChange(QLineEdit &edit);
+  void connectSettingsChange(QComboBox &edit);
+  void connectSettingsChange(QCheckBox &edit);
+  void connectSettingsChange(QGroupBox &edit);
   QLineEdit &stitchOptionsLineEdit() const;
   void setSelected(QComboBox &box, std::string const &str);
   void setText(QLineEdit &lineEdit, int value);
@@ -142,13 +158,6 @@ private:
   void setChecked(QCheckBox &checkBox, bool checked);
   std::string getText(QLineEdit const &lineEdit) const;
   std::string getText(QComboBox const &box) const;
-  void connectChangeListeners();
-  void connectExperimentSettingsChangeListeners();
-  void connectInstrumentSettingsChangeListeners();
-  void connectSettingsChange(QLineEdit *edit);
-  void connectSettingsChange(QComboBox *edit);
-  void connectSettingsChange(QCheckBox *edit);
-  void connectSettingsChange(QGroupBox *edit);
 
   /// The widget
   Ui::ReflSettingsWidget m_ui;
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflDataProcessorPresenter.cpp b/qt/scientific_interfaces/ISISReflectometry/ReflDataProcessorPresenter.cpp
index 4987864f3b1ba5db4c80040522a39d1a3a3b6690..57c18d3ecb7881f32bff63b38dfe0ff8b72dafc6 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflDataProcessorPresenter.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflDataProcessorPresenter.cpp
@@ -25,6 +25,7 @@ namespace CustomInterfaces {
 * @param processor : A ProcessingAlgorithm
 * @param postprocessor : A PostprocessingAlgorithm
 * workspaces
+* @param group : The zero-based index of this presenter within the tab.
 * @param postprocessMap : A map containing instructions for post-processing.
 * This map links column name to properties of the post-processing algorithm
 * @param loader : The algorithm responsible for loading data
@@ -33,10 +34,11 @@ ReflDataProcessorPresenter::ReflDataProcessorPresenter(
     const WhiteList &whitelist,
     const std::map<QString, PreprocessingAlgorithm> &preprocessMap,
     const ProcessingAlgorithm &processor,
-    const PostprocessingAlgorithm &postprocessor,
+    const PostprocessingAlgorithm &postprocessor, int group,
     const std::map<QString, QString> &postprocessMap, const QString &loader)
     : GenericDataProcessorPresenter(whitelist, preprocessMap, processor,
-                                    postprocessor, postprocessMap, loader) {}
+                                    postprocessor, group, postprocessMap,
+                                    loader) {}
 
 /**
 * Destructor
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflDataProcessorPresenter.h b/qt/scientific_interfaces/ISISReflectometry/ReflDataProcessorPresenter.h
index 739a6ad34f2aab12d6dc6457d95bb2cae7c71421..79574ee32f3cc09748ca43c0690e555cfe08d9d2 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflDataProcessorPresenter.h
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflDataProcessorPresenter.h
@@ -47,7 +47,7 @@ public:
       const WhiteList &whitelist,
       const std::map<QString, PreprocessingAlgorithm> &preprocessMap,
       const ProcessingAlgorithm &processor,
-      const PostprocessingAlgorithm &postprocessor,
+      const PostprocessingAlgorithm &postprocessor, int group,
       const std::map<QString, QString> &postprocessMap =
           std::map<QString, QString>(),
       const QString &loader = "Load");
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflEventPresenter.cpp b/qt/scientific_interfaces/ISISReflectometry/ReflEventPresenter.cpp
index 388f7f43b1ebd055e0ff76b4500ed0d6e9cfe028..6bc30c3d1e985742ad7f9a3dbc611b7ed866585b 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflEventPresenter.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflEventPresenter.cpp
@@ -31,5 +31,9 @@ std::string ReflEventPresenter::getTimeSlicingType() const {
 
   return m_view->getTimeSlicingType();
 }
+
+void ReflEventPresenter::onReductionPaused() { m_view->enableAll(); }
+
+void ReflEventPresenter::onReductionResumed() { m_view->disableAll(); }
 }
 }
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflEventPresenter.h b/qt/scientific_interfaces/ISISReflectometry/ReflEventPresenter.h
index 43490196ca9a581078692e0bea487fc4d2a6acee..0e4dbe124c6dcc35271b9192e443203f79b344c6 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflEventPresenter.h
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflEventPresenter.h
@@ -49,6 +49,9 @@ public:
   /// Returns time-slicing type
   std::string getTimeSlicingType() const override;
 
+  void onReductionPaused() override;
+  void onReductionResumed() override;
+
 private:
   /// The view we are managing
   IReflEventView *m_view;
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflEventTabPresenter.cpp b/qt/scientific_interfaces/ISISReflectometry/ReflEventTabPresenter.cpp
index 1b549ad2090745277a88800d5b1d84be6ba20241..24eed3410450036b3b2469bba5de92749ea4effc 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflEventTabPresenter.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflEventTabPresenter.cpp
@@ -24,7 +24,6 @@ ReflEventTabPresenter::~ReflEventTabPresenter() {}
 * @return :: Time-slicing values for 'ReflectometryReductionOneAuto'
 */
 std::string ReflEventTabPresenter::getTimeSlicingValues(int group) const {
-
   return m_eventPresenters.at(group)->getTimeSlicingValues();
 }
 
@@ -34,8 +33,15 @@ std::string ReflEventTabPresenter::getTimeSlicingValues(int group) const {
 * @return :: Time-slicing type for 'ReflectometryReductionOneAuto'
 */
 std::string ReflEventTabPresenter::getTimeSlicingType(int group) const {
-
   return m_eventPresenters.at(group)->getTimeSlicingType();
 }
+
+void ReflEventTabPresenter::onReductionPaused(int group) {
+  m_eventPresenters[group]->onReductionPaused();
+}
+
+void ReflEventTabPresenter::onReductionResumed(int group) {
+  m_eventPresenters[group]->onReductionResumed();
+}
 }
 }
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflEventTabPresenter.h b/qt/scientific_interfaces/ISISReflectometry/ReflEventTabPresenter.h
index 5b5f50298027ff41476e405ce10264a8da924503..94fd609820cb6ad9a563da97115a633975600d2a 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflEventTabPresenter.h
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflEventTabPresenter.h
@@ -51,6 +51,9 @@ public:
   /// Return time-slicing type
   std::string getTimeSlicingType(int group) const override;
 
+  void onReductionResumed(int group) override;
+  void onReductionPaused(int group) override;
+
 private:
   /// The presenters for each group as a vector
   std::vector<IReflEventPresenter *> m_eventPresenters;
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflEventWidget.ui b/qt/scientific_interfaces/ISISReflectometry/ReflEventWidget.ui
index 08615ca1d2a6520497bdcd3d07cbe31ee5e1fad2..6f83ec0442cb63310d68423a6b0ad3c6d3977fd2 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflEventWidget.ui
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflEventWidget.ui
@@ -1,212 +1,240 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
-  <class>ReflEventWidget</class>
-  <widget class="QWidget" name="ReflEventWidget">
-    <property name="geometry">
-      <rect>
-        <x>0</x>
-        <y>0</y>
-        <width>959</width>
-        <height>346</height>
-      </rect>
-    </property>
-    <property name="font">
-      <font>
-        <pointsize>8</pointsize>
-        <bold>false</bold>
-      </font>
-    </property>
-    <property name="windowTitle">
-      <string>Event Widget</string>
-    </property>
-    <layout class="QVBoxLayout" name="eventModeMainLayout">
-      <property name="margin">
-        <number>5</number>
-      </property>
+ <class>ReflEventWidget</class>
+ <widget class="QWidget" name="ReflEventWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>959</width>
+    <height>346</height>
+   </rect>
+  </property>
+  <property name="font">
+   <font>
+    <pointsize>8</pointsize>
+    <weight>50</weight>
+    <bold>false</bold>
+   </font>
+  </property>
+  <property name="windowTitle">
+   <string>Event Widget</string>
+  </property>
+  <layout class="QVBoxLayout" name="eventModeMainLayout">
+   <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>
+    <widget class="QGroupBox" name="uniformGroup">
+     <property name="title">
+      <string>Uniform slicing</string>
+     </property>
+     <layout class="QVBoxLayout" name="uniformLayoutContainer">
       <item>
-        <widget class="QGroupBox" name="uniformGroup">
-          <property name="title">
-            <string>Uniform slicing</string>
+       <layout class="QGridLayout" name="uniformLayout0">
+        <item row="0" column="0">
+         <widget class="QRadioButton" name="uniformEvenButton">
+          <property name="checked">
+           <bool>true</bool>
           </property>
-          <layout class="QVBoxLayout" name="uniformLayoutContainer">
-            <item>
-              <layout class="QGridLayout" name="uniformLayout0">
-                <item row="0" column="0">
-                  <widget class="QRadioButton" name="uniformEvenButton">
-                    <property name="checked">
-                      <bool>true</bool>
-                    </property>
-                    <attribute name="buttonGroup">
-                      <string notr="true">slicingOptionsButtonGroup</string>
-                    </attribute>
-                  </widget>
-                </item>
-                <item row="0" column="1">
-                  <widget class="QLineEdit" name="uniformEvenEdit"/>
-                </item>
-                <item row="0" column="2">
-                  <widget class="QLabel" name="uniformEvenLabel">
-                    <property name="text">
-                      <string>even time slices</string>
-                    </property>
-                  </widget>
-                </item>
-                <item row="0" column="3">
-                  <spacer name="uniformEvenSpacer">
-                    <property name="orientation">
-                      <enum>Qt::Horizontal</enum>
-                    </property>
-                    <property name="sizeHint" stdset="0">
-                      <size>
-                        <width>20</width>
-                        <height>20</height>
-                      </size>
-                    </property>
-                  </spacer>
-                </item>
-                <item row="1" column="0">
-                  <widget class="QRadioButton" name="uniformButton">
-                    <attribute name="buttonGroup">
-                      <string notr="true">slicingOptionsButtonGroup</string>
-                    </attribute>
-                  </widget>
-                </item>
-                <item row="1" column="1">
-                  <widget class="QLineEdit" name="uniformEdit"/>
-                </item>
-                <item row="1" column="2">
-                  <widget class="QLabel" name="uniformLabel">
-                    <property name="text">
-                      <string>(sec) slices</string>
-                    </property>
-                  </widget>
-                </item>
-                <item row="1" column="3">
-                  <spacer name="uniformSpacer">
-                    <property name="orientation">
-                      <enum>Qt::Horizontal</enum>
-                    </property>
-                    <property name="sizeHint" stdset="0">
-                      <size>
-                        <width>20</width>
-                        <height>20</height>
-                      </size>
-                    </property>
-                  </spacer>
-                </item>
-              </layout>
-            </item>
-          </layout>
-        </widget>
-      </item>
-      <item>
-        <widget class="QGroupBox" name="customGroup">
-          <property name="title">
-            <string>Custom slicing</string>
+          <attribute name="buttonGroup">
+           <string notr="true">slicingOptionsButtonGroup</string>
+          </attribute>
+         </widget>
+        </item>
+        <item row="0" column="1">
+         <widget class="QLineEdit" name="uniformEvenEdit">
+          <property name="toolTip">
+           <string>The number of evenly sized slices to split the event data into</string>
           </property>
-          <layout class="QHBoxLayout" name="customLayout">
-            <item>
-              <widget class="QRadioButton" name="customButton">
-                <attribute name="buttonGroup">
-                  <string notr="true">slicingOptionsButtonGroup</string>
-                </attribute>
-              </widget>
-            </item>
-            <item>
-              <widget class="QLabel" name="customLabel">
-                <property name="text">
-                  <string>Python list (sec)</string>
-                </property>
-              </widget>
-            </item>
-            <item>
-              <widget class="QLineEdit" name="customEdit"/>
-            </item>
-            <item>
-              <spacer name="customSpacer">
-                <property name="orientation">
-                  <enum>Qt::Horizontal</enum>
-                </property>
-                <property name="sizeHint" stdset="0">
-                  <size>
-                    <width>20</width>
-                    <height>20</height>
-                  </size>
-                </property>
-              </spacer>
-            </item>
-          </layout>
-        </widget>
-      </item>
-      <item>
-        <widget class="QGroupBox" name="logValueGroup">
-          <property name="title">
-            <string>Slicing by log value</string>
+         </widget>
+        </item>
+        <item row="0" column="2">
+         <widget class="QLabel" name="uniformEvenLabel">
+          <property name="text">
+           <string>even time slices</string>
           </property>
-          <layout class="QHBoxLayout" name="logValueLayout">
-            <item>
-              <widget class="QRadioButton" name="logValueButton">
-                <attribute name="buttonGroup">
-                  <string notr="true">slicingOptionsButtonGroup</string>
-                </attribute>
-              </widget>
-            </item>
-            <item>
-              <widget class="QLabel" name="logValueLabel">
-                <property name="text">
-                  <string>Python list (sec)</string>
-                </property>
-              </widget>
-            </item>
-            <item>
-              <widget class="QLineEdit" name="logValueEdit"/>
-            </item>
-            <item>
-              <widget class="QLabel" name="logValueTypeLabel">
-                <property name="text">
-                  <string>Log name</string>
-                </property>
-              </widget>
-            </item>
-            <item>
-              <widget class="QLineEdit" name="logValueTypeEdit"/>
-            </item>
-            <item>
-              <spacer name="logValueSpacer1">
-                <property name="orientation">
-                  <enum>Qt::Horizontal</enum>
-                </property>
-                <property name="sizeHint" stdset="0">
-                  <size>
-                    <width>20</width>
-                    <height>20</height>
-                  </size>
-                </property>
-              </spacer>
-            </item>
-          </layout>
-        </widget>
-      </item>
-      <item>
-        <spacer name="verticalSpacer">
+         </widget>
+        </item>
+        <item row="0" column="3">
+         <spacer name="uniformEvenSpacer">
+          <property name="orientation">
+           <enum>Qt::Horizontal</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>20</width>
+            <height>20</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+        <item row="1" column="0">
+         <widget class="QRadioButton" name="uniformButton">
+          <attribute name="buttonGroup">
+           <string notr="true">slicingOptionsButtonGroup</string>
+          </attribute>
+         </widget>
+        </item>
+        <item row="1" column="1">
+         <widget class="QLineEdit" name="uniformEdit">
+          <property name="toolTip">
+           <string>The length of time in seconds each slice should cover</string>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="2">
+         <widget class="QLabel" name="uniformLabel">
+          <property name="text">
+           <string>(sec) slices</string>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="3">
+         <spacer name="uniformSpacer">
           <property name="orientation">
-            <enum>Qt::Vertical</enum>
+           <enum>Qt::Horizontal</enum>
           </property>
           <property name="sizeHint" stdset="0">
-            <size>
-              <width>20</width>
-              <height>50</height>
-            </size>
+           <size>
+            <width>20</width>
+            <height>20</height>
+           </size>
           </property>
-        </spacer>
+         </spacer>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="customGroup">
+     <property name="title">
+      <string>Custom slicing</string>
+     </property>
+     <layout class="QHBoxLayout" name="customLayout">
+      <item>
+       <widget class="QRadioButton" name="customButton">
+        <attribute name="buttonGroup">
+         <string notr="true">slicingOptionsButtonGroup</string>
+        </attribute>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="customLabel">
+        <property name="text">
+         <string>Python list (sec)</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="customEdit">
+        <property name="toolTip">
+         <string>A comma separated list of values indicating the times (in seconds) at which to slice the data</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <spacer name="customSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>20</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="logValueGroup">
+     <property name="title">
+      <string>Slicing by log value</string>
+     </property>
+     <layout class="QHBoxLayout" name="logValueLayout">
+      <item>
+       <widget class="QRadioButton" name="logValueButton">
+        <attribute name="buttonGroup">
+         <string notr="true">slicingOptionsButtonGroup</string>
+        </attribute>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="logValueLabel">
+        <property name="text">
+         <string>Python list</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="logValueEdit">
+        <property name="toolTip">
+         <string>A comma separated list of values indicating the log values at which to slice the data</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="logValueTypeLabel">
+        <property name="text">
+         <string>Log name</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="logValueTypeEdit">
+        <property name="toolTip">
+         <string>The name of the log value to slice on</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <spacer name="logValueSpacer1">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>20</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
       </item>
-    </layout>
-  </widget>
-  <tabstops/>
-  <customwidgets/>
-  <resources/>
-  <connections/>
-  <buttongroups>
-    <buttongroup name="slicingOptionsButtonGroup"/>
-  </buttongroups>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>50</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+ <buttongroups>
+  <buttongroup name="slicingOptionsButtonGroup"/>
+ </buttongroups>
 </ui>
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflGenericDataProcessorPresenterFactory.cpp b/qt/scientific_interfaces/ISISReflectometry/ReflGenericDataProcessorPresenterFactory.cpp
index 483890a61f880427ccf33b300efb47920ce40ae3..3cb871a8e63dda568f27cc559359d3fe49b8060f 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflGenericDataProcessorPresenterFactory.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflGenericDataProcessorPresenterFactory.cpp
@@ -10,7 +10,7 @@ using namespace MantidQt::MantidWidgets;
 * Creates a Reflectometry Data Processor Presenter
 */
 std::unique_ptr<ReflDataProcessorPresenter>
-ReflGenericDataProcessorPresenterFactory::create() {
+ReflGenericDataProcessorPresenterFactory::create(int group) {
 
   // The whitelist, elements will appear in order in the table
   // 'Run(s)' column will be linked to 'InputWorkspace' property
@@ -111,7 +111,7 @@ ReflGenericDataProcessorPresenterFactory::create() {
   std::map<QString, QString> postprocessMap = {{"dQ/Q", "Params"}};
 
   return Mantid::Kernel::make_unique<ReflDataProcessorPresenter>(
-      whitelist, preprocessMap, processor, postprocessor, postprocessMap,
+      whitelist, preprocessMap, processor, postprocessor, group, postprocessMap,
       "LoadISISNexus");
 }
 }
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflGenericDataProcessorPresenterFactory.h b/qt/scientific_interfaces/ISISReflectometry/ReflGenericDataProcessorPresenterFactory.h
index e0e39c050b3e935ed684050bcc463e39398b4253..5df82135560dc708466cc5fd470a031157cc34a2 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflGenericDataProcessorPresenterFactory.h
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflGenericDataProcessorPresenterFactory.h
@@ -40,7 +40,7 @@ public:
   /**
   * Creates a Reflectometry Data Processor Presenter
   */
-  std::unique_ptr<ReflDataProcessorPresenter> create();
+  std::unique_ptr<ReflDataProcessorPresenter> create(int group);
 };
 }
 }
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflMainWindowPresenter.cpp b/qt/scientific_interfaces/ISISReflectometry/ReflMainWindowPresenter.cpp
index 95742d9cc1535dda55ed35b6e862356d20572054..7d8b827bf014c113db481d06050ddea2d945480c 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflMainWindowPresenter.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflMainWindowPresenter.cpp
@@ -4,7 +4,7 @@
 #include "IReflEventTabPresenter.h"
 #include "IReflSettingsTabPresenter.h"
 #include "IReflSaveTabPresenter.h"
-#include <iostream>
+#include "MantidQtWidgets/Common/HelpWindow.h"
 
 using namespace MantidQt::MantidWidgets::DataProcessor;
 
@@ -41,6 +41,20 @@ ReflMainWindowPresenter::ReflMainWindowPresenter(
 */
 ReflMainWindowPresenter::~ReflMainWindowPresenter() {}
 
+void ReflMainWindowPresenter::notifyReductionPaused(int group) {
+  m_isProcessing = false;
+  m_savePresenter->onAnyReductionPaused();
+  m_settingsPresenter->onReductionPaused(group);
+  m_eventPresenter->onReductionPaused(group);
+}
+
+void ReflMainWindowPresenter::notifyReductionResumed(int group) {
+  m_isProcessing = true;
+  m_savePresenter->onAnyReductionResumed();
+  m_settingsPresenter->onReductionResumed(group);
+  m_eventPresenter->onReductionResumed(group);
+}
+
 /**
 Used by the view to tell the presenter something has changed
 */
@@ -53,11 +67,19 @@ void ReflMainWindowPresenter::notify(IReflMainWindowPresenter::Flag flag) {
   case Flag::ConfirmReductionResumedFlag:
     m_isProcessing = true;
     break;
+  case Flag::HelpPressed:
+    showHelp();
+    break;
   }
   // Not having a 'default' case is deliberate. gcc issues a warning if there's
   // a flag we aren't handling.
 }
 
+void ReflMainWindowPresenter::showHelp() {
+  MantidQt::API::HelpWindow::showCustomInterface(nullptr,
+                                                 QString("ISIS Reflectometry"));
+}
+
 void ReflMainWindowPresenter::settingsChanged(int group) {
   m_runsPresenter->settingsChanged(group);
 }
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflMainWindowPresenter.h b/qt/scientific_interfaces/ISISReflectometry/ReflMainWindowPresenter.h
index b6c75d20c6c7013fbd5db05b7042171fef06a2b1..b33cd2b829c395cdc087f5223e6eb7e05ac4dae3 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflMainWindowPresenter.h
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflMainWindowPresenter.h
@@ -51,9 +51,6 @@ public:
   /// Destructor
   ~ReflMainWindowPresenter() override;
 
-  // Tell the presenter something has happened
-  void notify(IReflMainWindowPresenter::Flag flag) override;
-
   /// Returns values passed for 'Transmission run(s)'
   std::string getTransmissionRuns(int group) const override;
   /// Returns global options for 'CreateTransmissionWorkspaceAuto'
@@ -82,8 +79,10 @@ public:
 
   /// Returns whether the Runs Tab is currently processing any runs
   bool checkIfProcessing() const override;
-
   void settingsChanged(int group) override;
+  void notify(IReflMainWindowPresenter::Flag flag) override;
+  void notifyReductionPaused(int group) override;
+  void notifyReductionResumed(int group) override;
 
 private:
   /// Check for Settings Tab null pointer
@@ -94,6 +93,7 @@ private:
   void pauseReduction() const;
   /// Resumes reduction in the Runs Tab
   void resumeReduction() const;
+  void showHelp();
   /// The view we are handling
   IReflMainWindowView *m_view;
   /// The presenter of tab 'Runs'
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflMainWindowWidget.ui b/qt/scientific_interfaces/ISISReflectometry/ReflMainWindowWidget.ui
index ea85fadb3f166cf6af3e2db7c40ba7039fc5b549..27ccd181ebddbeb6d706b0865976ff7cda4e3337 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflMainWindowWidget.ui
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflMainWindowWidget.ui
@@ -4,39 +4,48 @@
  <widget class="QMainWindow" name="RelMainWindowWidget">
   <property name="geometry">
    <rect>
-     <x>0</x>
-     <y>0</y>
-     <width>959</width>
-     <height>346</height>
+    <x>0</x>
+    <y>0</y>
+    <width>975</width>
+    <height>515</height>
    </rect>
   </property>
   <property name="windowTitle">
    <string>ISIS Reflectometry</string>
   </property>
   <widget class="QWidget" name="centralwidget">
-   <layout class="QGridLayout" name="gridLayout">
-    <property name="leftMargin">
-     <number>3</number>
-    </property>
-    <property name="topMargin">
-     <number>3</number>
-    </property>
-    <property name="rightMargin">
-     <number>3</number>
-    </property>
-    <property name="bottomMargin">
-     <number>1</number>
-    </property>
-    <property name="spacing">
-     <number>1</number>
-    </property>
-    <item row="0" column="0">
-      <widget class="QTabWidget" name="mainTab">
-      </widget>/>
+   <layout class="QVBoxLayout" name="verticalLayout">
+    <item>
+     <widget class="QTabWidget" name="mainTab"/>
+    </item>
+    <item>
+     <widget class="QPushButton" name="helpButton">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="minimumSize">
+       <size>
+        <width>30</width>
+        <height>30</height>
+       </size>
+      </property>
+      <property name="maximumSize">
+       <size>
+        <width>30</width>
+        <height>30</height>
+       </size>
+      </property>
+      <property name="text">
+       <string>?</string>
+      </property>
+     </widget>
     </item>
    </layout>
   </widget>
  </widget>
  <resources/>
  <connections/>
-</ui>
\ No newline at end of file
+</ui>
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabPresenter.cpp b/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabPresenter.cpp
index d5f620949205c4426760b548a0088fbb04861bf0..39c2ab7cb71dd9b1d0f1980841a7e30f5afeda50 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabPresenter.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabPresenter.cpp
@@ -511,18 +511,14 @@ bool ReflRunsTabPresenter::startNewAutoreduction() const {
 
 /** Notifies main presenter that data reduction is confirmed to be paused
 */
-void ReflRunsTabPresenter::confirmReductionPaused() const {
-
-  m_mainPresenter->notify(
-      IReflMainWindowPresenter::Flag::ConfirmReductionPausedFlag);
+void ReflRunsTabPresenter::confirmReductionPaused(int group) {
+  m_mainPresenter->notifyReductionPaused(group);
 }
 
 /** Notifies main presenter that data reduction is confirmed to be resumed
 */
-void ReflRunsTabPresenter::confirmReductionResumed() const {
-
-  m_mainPresenter->notify(
-      IReflMainWindowPresenter::Flag::ConfirmReductionResumedFlag);
+void ReflRunsTabPresenter::confirmReductionResumed(int group) {
+  m_mainPresenter->notifyReductionResumed(group);
 }
 
 /** Changes the current instrument in the data processor widget. Also clears the
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabPresenter.h b/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabPresenter.h
index 15c095cebcb5210994cb8d1ce7e18215a4827ddd..76bbce7090ffdb24d722c0fd878efb305ea7a02e 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabPresenter.h
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabPresenter.h
@@ -83,8 +83,8 @@ public:
   /// Determine whether to start a new autoreduction
   bool startNewAutoreduction() const override;
   /// Reduction paused/resumed confirmation handler
-  void confirmReductionPaused() const override;
-  void confirmReductionResumed() const override;
+  void confirmReductionPaused(int group) override;
+  void confirmReductionResumed(int group) override;
   void settingsChanged(int group) override;
 
 private:
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabWidget.ui b/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabWidget.ui
index fd60f73a44ad4b9c3554f0ebbd6d39b5cbc3ae77..141f3440c150a65ec7ff95eb2bed7cebb973ff80 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabWidget.ui
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflRunsTabWidget.ui
@@ -13,280 +13,309 @@
   <property name="windowTitle">
    <string>RunsTab</string>
   </property>
-   <layout class="QVBoxLayout" name="layoutMain">
-    <property name="margin">
-     <number>1</number>
-    </property>
-     <item>
-       <widget class="QMenuBar" name="menuBar">
-         <property name="geometry">
-           <rect>
-             <x>0</x>
-             <y>0</y>
-             <width>959</width>
-             <height>18</height>
-           </rect>
+  <property name="toolTip">
+   <string/>
+  </property>
+  <layout class="QVBoxLayout" name="layoutMain">
+   <property name="leftMargin">
+    <number>1</number>
+   </property>
+   <property name="topMargin">
+    <number>1</number>
+   </property>
+   <property name="rightMargin">
+    <number>1</number>
+   </property>
+   <property name="bottomMargin">
+    <number>1</number>
+   </property>
+   <item>
+    <widget class="QMenuBar" name="menuBar">
+     <property name="nativeMenuBar">
+      <bool>false</bool>
+     </property>
+     <widget class="QMenu" name="menuTable">
+      <property name="title">
+       <string>&amp;Reflectometry</string>
+      </property>
+     </widget>
+     <widget class="QMenu" name="menuRows">
+      <property name="title">
+       <string>&amp;Edit</string>
+      </property>
+     </widget>
+     <addaction name="menuTable"/>
+     <addaction name="menuRows"/>
+    </widget>
+   </item>
+   <item>
+    <widget class="QSplitter" name="splitterTables">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="styleSheet">
+      <string>QSplitter::handle { background-color:white }</string>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="childrenCollapsible">
+      <bool>true</bool>
+     </property>
+     <widget class="QGroupBox" name="groupSearchPane">
+      <property name="title">
+       <string>Search Runs</string>
+      </property>
+      <layout class="QVBoxLayout" name="layoutSearchPane">
+       <property name="spacing">
+        <number>1</number>
+       </property>
+       <property name="leftMargin">
+        <number>1</number>
+       </property>
+       <property name="topMargin">
+        <number>1</number>
+       </property>
+       <property name="rightMargin">
+        <number>1</number>
+       </property>
+       <property name="bottomMargin">
+        <number>1</number>
+       </property>
+       <item>
+        <layout class="QFormLayout" name="layoutSearchForm">
+         <property name="fieldGrowthPolicy">
+          <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
          </property>
-         <property name="nativeMenuBar">
-           <bool>false</bool>
+         <item row="0" column="0">
+          <widget class="QLabel" name="labelInstrument">
+           <property name="text">
+            <string>Instrument:</string>
+           </property>
+           <property name="buddy">
+            <cstring>comboSearchInstrument</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="1">
+          <widget class="QComboBox" name="comboSearchInstrument">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="minimumSize">
+            <size>
+             <width>150</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="toolTip">
+            <string>The instrument used for the investigation to search for</string>
+           </property>
+           <property name="whatsThis">
+            <string>Specifies which instrument you're searching for data from.</string>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="0">
+          <widget class="QLabel" name="labelInvestigationId">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string>Investigation Id:</string>
+           </property>
+           <property name="buddy">
+            <cstring>textSearch</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="1">
+          <widget class="QLineEdit" name="textSearch">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="minimumSize">
+            <size>
+             <width>40</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="toolTip">
+            <string>The Id of the investigation runs to search for</string>
+           </property>
+           <property name="whatsThis">
+            <string>Specifies the investigation id that you are searching for runs from.</string>
+           </property>
+           <property name="inputMethodHints">
+            <set>Qt::ImhDigitsOnly</set>
+           </property>
+           <property name="cursorPosition">
+            <number>0</number>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="1">
+          <widget class="QPushButton" name="buttonSearch">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="toolTip">
+            <string>Search for runs using ICAT</string>
+           </property>
+           <property name="whatsThis">
+            <string>Searches ICAT for runs from the given instrument with the given investigation id.</string>
+           </property>
+           <property name="text">
+            <string>Search</string>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="0">
+          <widget class="QComboBox" name="comboTransferMethod">
+           <property name="toolTip">
+            <string>The method used to transfer runs into the processing table</string>
+           </property>
+           <property name="whatsThis">
+            <string>The strategy for searching and transfering files. See help.</string>
+           </property>
+          </widget>
+         </item>
+         <item row="3" column="0" colspan="2">
+          <widget class="QPushButton" name="buttonAutoreduce">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="toolTip">
+            <string>Transfer the selected run(s) into the processing table and reduce them</string>
+           </property>
+           <property name="whatsThis">
+            <string>Automatically searches ICAT for runs with given instrument and investigation id, transfers runs to table and processes them.</string>
+           </property>
+           <property name="text">
+            <string>Autoreduce</string>
+           </property>
+           <property name="icon">
+            <iconset>
+             <normaloff>:/play2.png</normaloff>:/play2.png</iconset>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <widget class="QTableView" name="tableSearchResults">
+         <property name="contextMenuPolicy">
+          <enum>Qt::CustomContextMenu</enum>
+         </property>
+         <property name="selectionMode">
+          <enum>QAbstractItemView::ExtendedSelection</enum>
          </property>
-         <widget class="QMenu" name="menuTable">
-           <property name="title">
-             <string>&amp;Reflectometry</string>
+         <property name="selectionBehavior">
+          <enum>QAbstractItemView::SelectRows</enum>
+         </property>
+         <attribute name="horizontalHeaderDefaultSectionSize">
+          <number>60</number>
+         </attribute>
+         <attribute name="horizontalHeaderMinimumSectionSize">
+          <number>20</number>
+         </attribute>
+         <attribute name="horizontalHeaderStretchLastSection">
+          <bool>true</bool>
+         </attribute>
+         <attribute name="verticalHeaderVisible">
+          <bool>false</bool>
+         </attribute>
+         <attribute name="verticalHeaderDefaultSectionSize">
+          <number>20</number>
+         </attribute>
+        </widget>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="layoutSearchBottomRow">
+         <item>
+          <widget class="QProgressBar" name="progressBar">
+           <property name="whatsThis">
+            <string>Shows the current progress when transferring runs.</string>
            </property>
-         </widget>
-         <widget class="QMenu" name="menuRows">
-           <property name="title">
-             <string>&amp;Edit</string>
+           <property name="value">
+            <number>0</number>
            </property>
-         </widget>
-         <addaction name="menuTable"/>
-         <addaction name="menuRows"/>
-       </widget>
-     </item>
-    <item>
-     <widget class="QSplitter" name="splitterTables">
-      <property name="sizePolicy">
-       <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
-        <horstretch>0</horstretch>
-        <verstretch>0</verstretch>
-       </sizepolicy>
-      </property>
-      <property name="orientation">
-       <enum>Qt::Horizontal</enum>
+          </widget>
+         </item>
+         <item>
+          <widget class="QToolButton" name="buttonTransfer">
+           <property name="toolTip">
+            <string>Transfer the selected run(s) into the processing table</string>
+           </property>
+           <property name="text">
+            <string>Transfer</string>
+           </property>
+           <property name="toolButtonStyle">
+            <enum>Qt::ToolButtonTextBesideIcon</enum>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QGroupBox" name="groupProcessPane">
+      <property name="title">
+       <string>Process Runs</string>
       </property>
-       <property name="styleSheet">
-        <string>QSplitter::handle { background-color:white }</string>
+      <layout class="QVBoxLayout" name="layoutProcessPane">
+       <property name="spacing">
+        <number>1</number>
        </property>
-       <property name="childrenCollapsible">
-       <bool>true</bool>
-      </property>
-      <widget class="QGroupBox" name="groupSearchPane">
-       <property name="title">
-        <string>Search Runs</string>
+       <property name="leftMargin">
+        <number>1</number>
+       </property>
+       <property name="topMargin">
+        <number>1</number>
+       </property>
+       <property name="rightMargin">
+        <number>1</number>
        </property>
-       <layout class="QVBoxLayout" name="layoutSearchPane">
-        <property name="spacing">
-         <number>1</number>
-        </property>
-        <property name="margin">
-         <number>1</number>
-        </property>
-        <item>
-         <layout class="QFormLayout" name="layoutSearchForm">
-          <property name="fieldGrowthPolicy">
-           <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
-          </property>
-          <item row="0" column="0">
-           <widget class="QLabel" name="labelInstrument">
-            <property name="text">
-             <string>Instrument:</string>
-            </property>
-            <property name="buddy">
-             <cstring>comboSearchInstrument</cstring>
-            </property>
-           </widget>
-          </item>
-          <item row="0" column="1">
-           <widget class="QComboBox" name="comboSearchInstrument">
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-            <property name="minimumSize">
-             <size>
-              <width>150</width>
-              <height>0</height>
-             </size>
-            </property>
-            <property name="toolTip">
-             <string>Select the instrument to search with</string>
-            </property>
-            <property name="whatsThis">
-             <string>Specifies which instrument you're searching for data from.</string>
-            </property>
-           </widget>
-          </item>
-          <item row="1" column="0">
-           <widget class="QLabel" name="labelInvestigationId">
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-            <property name="text">
-             <string>Investigation Id:</string>
-            </property>
-            <property name="buddy">
-             <cstring>textSearch</cstring>
-            </property>
-           </widget>
-          </item>
-          <item row="1" column="1">
-           <widget class="QLineEdit" name="textSearch">
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-            <property name="minimumSize">
-             <size>
-              <width>40</width>
-              <height>0</height>
-             </size>
-            </property>
-            <property name="toolTip">
-             <string>Investigation to search for</string>
-            </property>
-            <property name="whatsThis">
-             <string>Specifies the investigation id that you are searching for runs from.</string>
-            </property>
-            <property name="inputMethodHints">
-             <set>Qt::ImhDigitsOnly</set>
-            </property>
-            <property name="cursorPosition">
-             <number>0</number>
-            </property>
-           </widget>
-          </item>
-          <item row="2" column="1">
-           <widget class="QPushButton" name="buttonSearch">
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-            <property name="toolTip">
-             <string>Search</string>
-            </property>
-            <property name="whatsThis">
-             <string>Searches ICAT for runs from the given instrument with the given investigation id.</string>
-            </property>
-            <property name="text">
-             <string>Search</string>
-            </property>
-           </widget>
-          </item>
-          <item row="2" column="0">
-           <widget class="QComboBox" name="comboTransferMethod">
-            <property name="whatsThis">
-             <string>The strategy for searching and transfering files. See help.</string>
-            </property>
-           </widget>
-          </item>
-          <item row="3" column="0" colspan="2">
-           <widget class="QPushButton" name="buttonAutoreduce">
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-            <property name="text">
-             <string>Autoreduce</string>
-            </property>
-            <property name="icon">
-             <iconset resource="../../../../MantidPlot/icons/icons.qrc">
-              <normaloff>:/play2.png</normaloff>:/play2.png</iconset>
-            </property>
-            <property name="toolTip">
-             <string>Autoreduce</string>
-            </property>
-            <property name="whatsThis">
-             <string>Automatically searches ICAT for runs with given instrument and investigation id, transfers runs to table and processes them.</string>
-            </property>
-           </widget>
-          </item>
-         </layout>
-        </item>
-        <item>
-         <widget class="QTableView" name="tableSearchResults">
-          <property name="contextMenuPolicy">
-           <enum>Qt::CustomContextMenu</enum>
-          </property>
-          <property name="selectionMode">
-           <enum>QAbstractItemView::ExtendedSelection</enum>
-          </property>
-          <property name="selectionBehavior">
-           <enum>QAbstractItemView::SelectRows</enum>
-          </property>
-          <attribute name="horizontalHeaderDefaultSectionSize">
-           <number>60</number>
-          </attribute>
-          <attribute name="horizontalHeaderMinimumSectionSize">
-           <number>20</number>
-          </attribute>
-          <attribute name="horizontalHeaderStretchLastSection">
-           <bool>true</bool>
-          </attribute>
-          <attribute name="verticalHeaderVisible">
-           <bool>false</bool>
-          </attribute>
-          <attribute name="verticalHeaderDefaultSectionSize">
-           <number>20</number>
-          </attribute>
-         </widget>
-        </item>
-        <item>
-         <layout class="QHBoxLayout" name="layoutSearchBottomRow">
-          <item>
-            <widget class="QProgressBar" name="progressBar">
-              <property name="whatsThis">
-                <string>Shows the current progress when transferring runs.</string>
-              </property>
-              <property name="value">
-                <number>0</number>
-              </property>
-            </widget>
-          </item>
-          <item>
-           <widget class="QToolButton" name="buttonTransfer">
-            <property name="text">
-             <string>Transfer</string>
-            </property>
-            <property name="toolButtonStyle">
-             <enum>Qt::ToolButtonTextBesideIcon</enum>
-            </property>
-           </widget>
-          </item>
-         </layout>
-        </item>
-       </layout>
-      </widget>
-      <widget class="QGroupBox" name="groupProcessPane">
-         <property name="title">
-           <string>Process Runs</string>
+       <property name="bottomMargin">
+        <number>1</number>
+       </property>
+       <item>
+        <widget class="QToolBox" name="toolbox">
+         <property name="font">
+          <font>
+           <pointsize>11</pointsize>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
          </property>
-         <layout class="QVBoxLayout" name="layoutProcessPane">
-           <property name="spacing">
-             <number>1</number>
-           </property>
-           <property name="margin">
-             <number>1</number>
-           </property>
-           <item>
-             <widget class="QToolBox" name="toolbox">
-               <property name="font">
-                 <font>
-                   <pointsize>11</pointsize>
-                   <bold>true</bold>
-                 </font>
-               </property>
-             </widget>
-           </item>
-         </layout>
-       </widget>
-       </widget>
-    </item>
-   </layout>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+  </layout>
   <action name="actionSearch">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/folder.png</normaloff>:/folder.png</iconset>
    </property>
    <property name="text">
@@ -300,7 +329,7 @@
   </action>
   <action name="actionTransfer">
    <property name="icon">
-    <iconset resource="../../../../MantidPlot/icons/icons.qrc">
+    <iconset>
      <normaloff>:/append_drag_curves.png</normaloff>:/append_drag_curves.png</iconset>
    </property>
    <property name="text">
@@ -318,8 +347,7 @@
   <tabstop>comboSearchInstrument</tabstop>
   <tabstop>tableSearchResults</tabstop>
  </tabstops>
-  <customwidgets/>
-  <resources>
+ <resources>
   <include location="../../../../MantidPlot/icons/icons.qrc"/>
  </resources>
  <connections>
@@ -355,11 +383,21 @@
     </hint>
    </hints>
   </connection>
-   <connection>
-     <sender>buttonAutoreduce</sender>
-     <signal>clicked()</signal>
-     <receiver>actionAutoreduce</receiver>
-     <slot>trigger()</slot>
-   </connection>
+  <connection>
+   <sender>buttonAutoreduce</sender>
+   <signal>clicked()</signal>
+   <receiver>actionAutoreduce</receiver>
+   <slot>trigger()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>20</x>
+     <y>20</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>-1</x>
+     <y>-1</y>
+    </hint>
+   </hints>
+  </connection>
  </connections>
 </ui>
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflSaveTabPresenter.cpp b/qt/scientific_interfaces/ISISReflectometry/ReflSaveTabPresenter.cpp
index 316fa0dd725496b88c6156ebb94becf12b1546ef..31ee91b63b00fa440f78d60be62f82f8cd7ac162 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflSaveTabPresenter.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflSaveTabPresenter.cpp
@@ -41,6 +41,10 @@ void ReflSaveTabPresenter::acceptMainPresenter(
   m_mainPresenter = mainPresenter;
 }
 
+void ReflSaveTabPresenter::onAnyReductionPaused() { populateWorkspaceList(); }
+
+void ReflSaveTabPresenter::onAnyReductionResumed() {}
+
 void ReflSaveTabPresenter::notify(IReflSaveTabPresenter::Flag flag) {
   switch (flag) {
   case populateWorkspaceListFlag:
@@ -212,4 +216,4 @@ std::vector<std::string> ReflSaveTabPresenter::getAvailableWorkspaceNames() {
   return validNames;
 }
 }
-}
\ No newline at end of file
+}
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflSaveTabPresenter.h b/qt/scientific_interfaces/ISISReflectometry/ReflSaveTabPresenter.h
index 640f616a13ad4baa3a50c731e44ee58f21e2fd3a..9c99f770d30f6304c213347e6e265256f63d4f37 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflSaveTabPresenter.h
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflSaveTabPresenter.h
@@ -49,6 +49,8 @@ public:
   /// Accept a main presenter
   void acceptMainPresenter(IReflMainWindowPresenter *mainPresenter) override;
   void notify(IReflSaveTabPresenter::Flag flag) override;
+  void onAnyReductionPaused() override;
+  void onAnyReductionResumed() override;
 
 private:
   /// Adds all workspace names to the list of workspaces
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflSaveTabWidget.ui b/qt/scientific_interfaces/ISISReflectometry/ReflSaveTabWidget.ui
index 682e164994229219e68b5d9ad16d79c631e5d099..f90507e4f7646bd62dcdf69741ca4a13b84607c3 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflSaveTabWidget.ui
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflSaveTabWidget.ui
@@ -266,7 +266,7 @@
                   </item>
                 </layout>
               </widget>
-            </item>     
+            </item>
             <item row ="5" column="0">
               <widget class="QLabel" name="fileFormatLabel">
                 <property name="text">
@@ -335,7 +335,7 @@
                   </size>
                 </property>
               </spacer>
-            </item>    
+            </item>
           </layout>
         </widget>
       </item>
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflSettingsPresenter.cpp b/qt/scientific_interfaces/ISISReflectometry/ReflSettingsPresenter.cpp
index 1b61f927670caa2f1d442a3b10238a1b515c416e..d0b53586a38bc4e635602cb1d843e6c30ee48dfa 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflSettingsPresenter.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflSettingsPresenter.cpp
@@ -184,6 +184,10 @@ QString ReflSettingsPresenter::asAlgorithmPropertyBool(bool value) {
   return value ? "1" : "0";
 }
 
+void ReflSettingsPresenter::onReductionResumed() { m_view->disableAll(); }
+
+void ReflSettingsPresenter::onReductionPaused() { m_view->enableAll(); }
+
 /** Returns global options for 'ReflectometryReductionOneAuto'
  * @return :: Global options for 'ReflectometryReductionOneAuto'
  */
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflSettingsPresenter.h b/qt/scientific_interfaces/ISISReflectometry/ReflSettingsPresenter.h
index f222877be6a0ae1ccd54899309d792aae5eb1926..4ff04e424996e1b6f325bb201ce9c1f7929dc470 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflSettingsPresenter.h
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflSettingsPresenter.h
@@ -63,6 +63,9 @@ public:
   std::string getTransmissionRuns() const override;
 
   void acceptTabPresenter(IReflSettingsTabPresenter *tabPresenter) override;
+  void onReductionPaused() override;
+  void onReductionResumed() override;
+  Mantid::API::IAlgorithm_sptr createReductionAlg() override;
 
 private:
   void createStitchHints();
@@ -72,7 +75,6 @@ private:
   bool hasReductionTypes(const std::string &reductionType) const;
   void handleSummationTypeChange();
   static QString asAlgorithmPropertyBool(bool value);
-  Mantid::API::IAlgorithm_sptr createReductionAlg();
   Mantid::Geometry::Instrument_const_sptr
   createEmptyInstrument(const std::string &instName);
   MantidWidgets::DataProcessor::OptionsQMap transmissionOptionsMap() const;
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflSettingsTabPresenter.cpp b/qt/scientific_interfaces/ISISReflectometry/ReflSettingsTabPresenter.cpp
index e98bce91d02cda612971866158fb6179d9927391..65051294225f6635f07ce47fc771ccc8c275425c 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflSettingsTabPresenter.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflSettingsTabPresenter.cpp
@@ -51,6 +51,14 @@ void ReflSettingsTabPresenter::setInstrumentName(const std::string &instName) {
     presenter->setInstrumentName(instName);
 }
 
+void ReflSettingsTabPresenter::onReductionResumed(int group) {
+  m_settingsPresenters[group]->onReductionResumed();
+}
+
+void ReflSettingsTabPresenter::onReductionPaused(int group) {
+  m_settingsPresenters[group]->onReductionPaused();
+}
+
 /** Returns values passed for 'Transmission run(s)'
 *
 * @param group :: The group from which to get the values
diff --git a/qt/scientific_interfaces/ISISReflectometry/ReflSettingsTabPresenter.h b/qt/scientific_interfaces/ISISReflectometry/ReflSettingsTabPresenter.h
index 978aeb548b4fe4d1df5ce22a41bd514018d70a55..292cabea7ac162f125c57632d517c1a9bf8e615b 100644
--- a/qt/scientific_interfaces/ISISReflectometry/ReflSettingsTabPresenter.h
+++ b/qt/scientific_interfaces/ISISReflectometry/ReflSettingsTabPresenter.h
@@ -49,6 +49,8 @@ public:
   void setInstrumentName(const std::string &instName) override;
   void acceptMainPresenter(IReflMainWindowPresenter *mainPresenter) override;
   void settingsChanged(int group) override;
+  void onReductionPaused(int group) override;
+  void onReductionResumed(int group) override;
   void
   passSelfToChildren(std::vector<IReflSettingsPresenter *> const &children);
 
diff --git a/qt/scientific_interfaces/ISISSANS/CMakeLists.txt b/qt/scientific_interfaces/ISISSANS/CMakeLists.txt
index 1a1ef18afcddebbfe3cf7e06da196adb6750f509..28a21f52ff145d5cd50228b0bd4a0e86a94f520c 100644
--- a/qt/scientific_interfaces/ISISSANS/CMakeLists.txt
+++ b/qt/scientific_interfaces/ISISSANS/CMakeLists.txt
@@ -66,4 +66,6 @@ mtd_add_qt_library (TARGET_NAME MantidScientificInterfacesISISSANS
   OSX_INSTALL_RPATH
     @loader_path/../../Contents/MacOS
     @loader_path/../../plugins/qt4
+  LINUX_INSTALL_RPATH
+    "\$ORIGIN/../../${LIB_DIR};\$ORIGIN/../../plugins/qt4/"
 )
diff --git a/qt/scientific_interfaces/Indirect/CMakeLists.txt b/qt/scientific_interfaces/Indirect/CMakeLists.txt
index 31048973c77f011131e0b45fe7bb93be0aab72d3..46087632e35ffc2eb8406676a5519a5655a0f867 100644
--- a/qt/scientific_interfaces/Indirect/CMakeLists.txt
+++ b/qt/scientific_interfaces/Indirect/CMakeLists.txt
@@ -196,4 +196,7 @@ mtd_add_qt_library (TARGET_NAME MantidScientificInterfacesIndirect
   OSX_INSTALL_RPATH
     @loader_path/../../Contents/MacOS
     @loader_path/../../plugins/qt4
+  LINUX_INSTALL_RPATH
+    "\$ORIGIN/../../${LIB_DIR};\$ORIGIN/../../plugins/qt4/"
+
 )
diff --git a/qt/scientific_interfaces/MultiDatasetFit/CMakeLists.txt b/qt/scientific_interfaces/MultiDatasetFit/CMakeLists.txt
index 9c403e3c37ba47be2ab5fa11c3dae6c84141bd3c..f921ef1f547de321a890914d06a4f959f8e97e0a 100644
--- a/qt/scientific_interfaces/MultiDatasetFit/CMakeLists.txt
+++ b/qt/scientific_interfaces/MultiDatasetFit/CMakeLists.txt
@@ -69,4 +69,6 @@ mtd_add_qt_library (TARGET_NAME MantidScientificInterfacesMultiDatasetFit
     ${PLUGINS_DIR}
   OSX_INSTALL_RPATH
     @loader_path/../../Contents/MacOS
+  LINUX_INSTALL_RPATH
+    "\$ORIGIN/../../${LIB_DIR}"
 )
diff --git a/qt/scientific_interfaces/Muon/CMakeLists.txt b/qt/scientific_interfaces/Muon/CMakeLists.txt
index f8c390d5e06514b6a07f916b1d043979315f19de..a926cd64dbd50f6cd3baa0f5a6ea8f526398079d 100644
--- a/qt/scientific_interfaces/Muon/CMakeLists.txt
+++ b/qt/scientific_interfaces/Muon/CMakeLists.txt
@@ -109,4 +109,6 @@ mtd_add_qt_library (TARGET_NAME MantidScientificInterfacesMuon
   OSX_INSTALL_RPATH
     @loader_path/../../Contents/MacOS
     @loader_path/../../plugins/qt4
+  LINUX_INSTALL_RPATH
+    "\$ORIGIN/../../${LIB_DIR};\$ORIGIN/../../plugins/qt4/"
 )
diff --git a/qt/scientific_interfaces/Muon/MuonAnalysis.cpp b/qt/scientific_interfaces/Muon/MuonAnalysis.cpp
index 67e6e60c0a0641d4d45492ec5e5775c9948e0b39..bd3a6f2f9fbc68caa93af1e165312994164c7cdc 100644
--- a/qt/scientific_interfaces/Muon/MuonAnalysis.cpp
+++ b/qt/scientific_interfaces/Muon/MuonAnalysis.cpp
@@ -441,6 +441,33 @@ void MuonAnalysis::plotSelectedGroupPair() {
 
   plotItem(itemType, tableRow, plotType);
 }
+/**
+* Creates workspace for specified group/pair and adds it to the ADS;
+* @param itemType :: Whether it's a group or pair
+* @param tableRow :: Row in the group/pair table which contains the item
+* @param plotType :: What kind of plot we want to analyse
+*/
+std::string MuonAnalysis::addItem(ItemType itemType, int tableRow,
+                                  PlotType plotType) {
+  AnalysisDataServiceImpl &ads = AnalysisDataService::Instance();
+
+  // Create workspace and a raw (unbinned) version of it
+  auto ws = createAnalysisWorkspace(itemType, tableRow, plotType);
+  auto wsRaw = createAnalysisWorkspace(itemType, tableRow, plotType, true);
+
+  // Find names for new workspaces
+  const std::string wsName = getNewAnalysisWSName(itemType, tableRow, plotType);
+  const std::string wsRawName = wsName + "_Raw";
+
+  // Make sure they end up in the ADS
+  ads.addOrReplace(wsName, ws);
+  ads.addOrReplace(wsRawName, wsRaw);
+
+  // Make sure they are grouped
+  std::vector<std::string> wsNames = {wsName, wsRawName};
+  MuonAnalysisHelper::groupWorkspaces(m_currentLabel, wsNames);
+  return wsName;
+}
 
 /**
  * Creates workspace for specified group/pair and plots it;
@@ -453,26 +480,8 @@ void MuonAnalysis::plotItem(ItemType itemType, int tableRow,
   m_updating = true;
   m_uiForm.fitBrowser->clearChosenGroups();
   m_uiForm.fitBrowser->clearChosenPeriods();
-
-  AnalysisDataServiceImpl &ads = AnalysisDataService::Instance();
-
   try {
-    // Create workspace and a raw (unbinned) version of it
-    auto ws = createAnalysisWorkspace(itemType, tableRow, plotType);
-    auto wsRaw = createAnalysisWorkspace(itemType, tableRow, plotType, true);
-
-    // Find names for new workspaces
-    const std::string wsName =
-        getNewAnalysisWSName(itemType, tableRow, plotType);
-    const std::string wsRawName = wsName + "_Raw";
-
-    // Make sure they end up in the ADS
-    ads.addOrReplace(wsName, ws);
-    ads.addOrReplace(wsRawName, wsRaw);
-
-    // Make sure they are grouped
-    std::vector<std::string> wsNames = {wsName, wsRawName};
-    MuonAnalysisHelper::groupWorkspaces(m_currentLabel, wsNames);
+    auto wsName = addItem(itemType, tableRow, plotType);
 
     QString wsNameQ = QString::fromStdString(wsName);
 
@@ -484,7 +493,8 @@ void MuonAnalysis::plotItem(ItemType itemType, int tableRow,
     QMessageBox::critical(this, "MuonAnalysis - Error",
                           "Unable to plot the item. Check log for details.");
   }
-
+  loadAllGroups();
+  loadAllPairs();
   m_updating = false;
 }
 
@@ -1557,6 +1567,8 @@ void MuonAnalysis::updateFrontAndCombo(bool updateIndexAndPlot) {
     currentI = 0;
   }
   setGroupsAndPairs();
+  loadAllGroups();
+  loadAllPairs();
   if (updateIndexAndPlot) {
     setGroupOrPairIndexToPlot(currentI);
     plotCurrentGroupAndPairs();
@@ -2171,6 +2183,7 @@ double MuonAnalysis::finishTime() const {
  * Load auto saved values
  */
 void MuonAnalysis::loadAutoSavedValues(const QString &group) {
+
   QSettings prevInstrumentValues;
   prevInstrumentValues.beginGroup(group + "instrument");
   QString instrumentName =
@@ -2673,6 +2686,10 @@ void MuonAnalysis::connectAutoUpdate() {
           SLOT(updateCurrentPlotStyle()));
   connect(m_optionTab, SIGNAL(multiFitStateChanged(int)), this,
           SLOT(multiFitCheckboxChanged(int)));
+  connect(m_optionTab, SIGNAL(loadAllGroupChanged(int)), this,
+          SLOT(loadAllGroups(int)));
+  connect(m_optionTab, SIGNAL(loadAllPairsChanged(int)), this,
+          SLOT(loadAllPairs(int)));
 }
 
 /**
@@ -3229,6 +3246,57 @@ void MuonAnalysis::multiFitCheckboxChanged(int state) {
                                                 : Muon::MultiFitState::Disabled;
   m_fitFunctionPresenter->setMultiFitState(multiFitState);
 }
+/**
+* Checks if the run is set and if the plot name is valid.
+* If they are not valid then the loadAllGroups and loadAllPairs
+* methods should do nothing.
+*/
+
+bool MuonAnalysis::safeToLoadAllGroupsOrPairs() {
+  std::string plotTypeName =
+      m_uiForm.frontPlotFuncs->currentText().toStdString();
+  if (m_currentLabel == "NoLabelSet") {
+    return false;
+  } else if (plotTypeName != "Asymmetry" && plotTypeName != "Counts" &&
+             plotTypeName != "Logarithm") {
+    return false;
+  }
+  return true;
+}
+
+/**
+* Load all of the pairs if the all pairs tickbox is ticked
+* @param state :: [input] (not used) Setting of combo box
+*/
+void MuonAnalysis::loadAllGroups(int state) {
+  Q_UNUSED(state);
+
+  if (m_uiForm.loadAllGroupsCheckBox->isChecked() &&
+      safeToLoadAllGroupsOrPairs()) {
+    ItemType itemType = Group;
+    PlotType plotType = parsePlotType(m_uiForm.frontPlotFuncs);
+    for (int j = 0; j < numGroups(); j++) {
+      addItem(itemType, j, plotType);
+    }
+  }
+}
+/**
+* Load all of the pairs if the all pairs tickbox is ticked
+* @param state :: [input] (not used) Setting of combo box
+*/
+void MuonAnalysis::loadAllPairs(int state) {
+
+  Q_UNUSED(state);
+  if (m_uiForm.loadAllPairsCheckBox->isChecked() &&
+      safeToLoadAllGroupsOrPairs()) {
+    ItemType itemType = Pair;
+    PlotType plotType = parsePlotType(m_uiForm.frontPlotFuncs);
+    for (int j = 0; j < numPairs(); j++) {
+
+      addItem(itemType, j, plotType);
+    }
+  }
+}
 /**
  * Update the fit data presenter with current overwrite setting
  * @param state :: [input] (not used) Setting of combo box
diff --git a/qt/scientific_interfaces/Muon/MuonAnalysis.h b/qt/scientific_interfaces/Muon/MuonAnalysis.h
index 4086722ac5b1606999dd49d5281c9c9e31cb3f8d..58a8384af8b9a6eba39af15f1c2268c44ddca426 100644
--- a/qt/scientific_interfaces/Muon/MuonAnalysis.h
+++ b/qt/scientific_interfaces/Muon/MuonAnalysis.h
@@ -251,6 +251,9 @@ private slots:
 
   /// Called when "enable multi fit" checkbox is turned on/off
   void multiFitCheckboxChanged(int state);
+  bool safeToLoadAllGroupsOrPairs();
+  void loadAllGroups(int state = 0);
+  void loadAllPairs(int state = 0);
 
   /// Called when "overwrite" is changed
   void updateDataPresenterOverwrite(int state);
@@ -292,7 +295,8 @@ private:
 
   /// Creates workspace for specified group/pair and plots it
   void plotItem(Muon::ItemType itemType, int tableRow, Muon::PlotType plotType);
-
+  std::string addItem(Muon::ItemType itemType, int tableRow,
+                      Muon::PlotType plotType);
   /// Creates workspace ready for analysis and plotting
   Mantid::API::Workspace_sptr createAnalysisWorkspace(Muon::ItemType itemType,
                                                       int tableRow,
diff --git a/qt/scientific_interfaces/Muon/MuonAnalysis.ui b/qt/scientific_interfaces/Muon/MuonAnalysis.ui
index 6ef4b3e9c033c72fd2f1863f9c86557514cfb9d5..3b593c28acc735b19e9d3702c6b0836a838726d2 100644
--- a/qt/scientific_interfaces/Muon/MuonAnalysis.ui
+++ b/qt/scientific_interfaces/Muon/MuonAnalysis.ui
@@ -2490,6 +2490,23 @@ p, li { white-space: pre-wrap; }
             <layout class="QGridLayout" name="gridLayout_20">
              <item row="3" column="0">
               <layout class="QGridLayout" name="gridLayout_21">
+               <item row="4" column="0">
+                <widget class="QLabel" name="label_12">
+                 <property name="text">
+                  <string>Load all groups</string>
+                 </property>
+                </widget>
+               </item>
+               <item row="3" column="0">
+                <widget class="QLabel" name="lblEnableMultiFit">
+                 <property name="toolTip">
+                  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enables the new UI for the &amp;quot;Data Analysis&amp;quot; tab introduced in Mantid 3.8.&lt;/p&gt;&lt;p&gt;In this mode, simultaneous fits of multiple datasets are possible.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                 </property>
+                 <property name="text">
+                  <string>Enable multiple fitting:</string>
+                 </property>
+                </widget>
+               </item>
                <item row="0" column="2">
                 <widget class="QComboBox" name="plotCreation">
                  <property name="sizePolicy">
@@ -2520,20 +2537,6 @@ p, li { white-space: pre-wrap; }
                  </item>
                 </widget>
                </item>
-               <item row="1" column="2">
-                <widget class="QComboBox" name="newPlotPolicy">
-                 <item>
-                  <property name="text">
-                   <string>Create new window</string>
-                  </property>
-                 </item>
-                 <item>
-                  <property name="text">
-                   <string>Use previous window</string>
-                  </property>
-                 </item>
-                </widget>
-               </item>
                <item row="1" column="0">
                 <widget class="QLabel" name="label_14">
                  <property name="toolTip">
@@ -2560,14 +2563,18 @@ p, li { white-space: pre-wrap; }
                  </property>
                 </spacer>
                </item>
-               <item row="3" column="0">
-                <widget class="QLabel" name="lblEnableMultiFit">
-                 <property name="toolTip">
-                  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enables the new UI for the &amp;quot;Data Analysis&amp;quot; tab introduced in Mantid 3.8.&lt;/p&gt;&lt;p&gt;In this mode, simultaneous fits of multiple datasets are possible.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-                 </property>
-                 <property name="text">
-                  <string>Enable multiple fitting:</string>
-                 </property>
+               <item row="1" column="2">
+                <widget class="QComboBox" name="newPlotPolicy">
+                 <item>
+                  <property name="text">
+                   <string>Create new window</string>
+                  </property>
+                 </item>
+                 <item>
+                  <property name="text">
+                   <string>Use previous window</string>
+                  </property>
+                 </item>
                 </widget>
                </item>
                <item row="0" column="0">
@@ -2584,6 +2591,16 @@ p, li { white-space: pre-wrap; }
                  </property>
                 </widget>
                </item>
+               <item row="2" column="0">
+                <widget class="QLabel" name="label_8">
+                 <property name="toolTip">
+                  <string>Hides the MantidPlot toolbars. Useful on small screens.</string>
+                 </property>
+                 <property name="text">
+                  <string>Hide Toolbars:</string>
+                 </property>
+                </widget>
+               </item>
                <item row="1" column="3">
                 <widget class="QStackedWidget" name="newPlotPolicyOptions">
                  <property name="currentIndex">
@@ -2655,13 +2672,13 @@ p, li { white-space: pre-wrap; }
                  </widget>
                 </widget>
                </item>
-               <item row="2" column="0">
-                <widget class="QLabel" name="label_8">
+               <item row="3" column="2">
+                <widget class="QCheckBox" name="chkEnableMultiFit">
                  <property name="toolTip">
-                  <string>Hides the MantidPlot toolbars. Useful on small screens.</string>
+                  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enables the new UI for the &amp;quot;Data Analysis&amp;quot; tab introduced in Mantid 3.8.&lt;/p&gt;&lt;p&gt;In this mode, simultaneous fits of multiple datasets are possible.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
                  </property>
                  <property name="text">
-                  <string>Hide Toolbars:</string>
+                  <string/>
                  </property>
                 </widget>
                </item>
@@ -2672,11 +2689,22 @@ p, li { white-space: pre-wrap; }
                  </property>
                 </widget>
                </item>
-               <item row="3" column="2">
-                <widget class="QCheckBox" name="chkEnableMultiFit">
-                 <property name="toolTip">
-                  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enables the new UI for the &amp;quot;Data Analysis&amp;quot; tab introduced in Mantid 3.8.&lt;/p&gt;&lt;p&gt;In this mode, simultaneous fits of multiple datasets are possible.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               <item row="4" column="2">
+                <widget class="QCheckBox" name="loadAllGroupsCheckBox">
+                 <property name="text">
+                  <string/>
                  </property>
+                </widget>
+               </item>
+               <item row="5" column="0">
+                <widget class="QLabel" name="label_13">
+                 <property name="text">
+                  <string>Load all pairs</string>
+                 </property>
+                </widget>
+               </item>
+               <item row="5" column="2">
+                <widget class="QCheckBox" name="loadAllPairsCheckBox">
                  <property name="text">
                   <string/>
                  </property>
diff --git a/qt/scientific_interfaces/Muon/MuonAnalysisOptionTab.cpp b/qt/scientific_interfaces/Muon/MuonAnalysisOptionTab.cpp
index b0b4d2f076a13062d48d12f0930c6dc1ac863c2a..b2ace7203d500713ea927f6f2b30687df3d292dd 100644
--- a/qt/scientific_interfaces/Muon/MuonAnalysisOptionTab.cpp
+++ b/qt/scientific_interfaces/Muon/MuonAnalysisOptionTab.cpp
@@ -134,6 +134,10 @@ void MuonAnalysisOptionTab::initLayout() {
           SIGNAL(settingsTabUpdatePlot()));
   connect(m_uiForm.chkEnableMultiFit, SIGNAL(stateChanged(int)), this,
           SIGNAL(multiFitStateChanged(int)));
+  connect(m_uiForm.loadAllGroupsCheckBox, SIGNAL(stateChanged(int)), this,
+          SIGNAL(loadAllGroupChanged(int)));
+  connect(m_uiForm.loadAllPairsCheckBox, SIGNAL(stateChanged(int)), this,
+          SIGNAL(loadAllPairsChanged(int)));
 }
 
 /**
diff --git a/qt/scientific_interfaces/Muon/MuonAnalysisOptionTab.h b/qt/scientific_interfaces/Muon/MuonAnalysisOptionTab.h
index 83d471821dc42c72f693d2f50a3ad3f9293d78db..81f67eceee2234b5dba56e52a6745b7e5a3131ac 100644
--- a/qt/scientific_interfaces/Muon/MuonAnalysisOptionTab.h
+++ b/qt/scientific_interfaces/Muon/MuonAnalysisOptionTab.h
@@ -95,6 +95,8 @@ signals:
 
   /// Emitted when multi fitting mode is turned on/off
   void multiFitStateChanged(int state);
+  void loadAllGroupChanged(int state);
+  void loadAllPairsChanged(int state);
 
 private:
   /// Default widget values
diff --git a/qt/scientific_interfaces/test/EnggDiffGSASFittingModelMock.h b/qt/scientific_interfaces/test/EnggDiffGSASFittingModelMock.h
index 0be14d4acd71897451e2c2471a7110ebb05f1b13..dd4094835bc3972be5c80f2f7cc88b475460ab1e 100644
--- a/qt/scientific_interfaces/test/EnggDiffGSASFittingModelMock.h
+++ b/qt/scientific_interfaces/test/EnggDiffGSASFittingModelMock.h
@@ -14,17 +14,19 @@ class MockEnggDiffGSASFittingModel : public IEnggDiffGSASFittingModel {
 
 public:
   MOCK_METHOD7(doPawleyRefinement,
-               bool(const RunLabel &runLabel, const std::string &instParamFile,
-                    const std::vector<std::string> &phaseFiles,
-                    const std::string &pathToGSASII,
-                    const std::string &GSASIIProjectFile, const double dMin,
-                    const double negativeWeight));
+               boost::optional<std::string>(
+                   const RunLabel &runLabel, const std::string &instParamFile,
+                   const std::vector<std::string> &phaseFiles,
+                   const std::string &pathToGSASII,
+                   const std::string &GSASIIProjectFile, const double dMin,
+                   const double negativeWeight));
 
   MOCK_METHOD5(doRietveldRefinement,
-               bool(const RunLabel &runLabel, const std::string &instParamFile,
-                    const std::vector<std::string> &phaseFiles,
-                    const std::string &pathToGSASII,
-                    const std::string &GSASIIProjectFile));
+               boost::optional<std::string>(
+                   const RunLabel &runLabel, const std::string &instParamFile,
+                   const std::vector<std::string> &phaseFiles,
+                   const std::string &pathToGSASII,
+                   const std::string &GSASIIProjectFile));
 
   MOCK_CONST_METHOD1(getFittedPeaks,
                      boost::optional<Mantid::API::MatrixWorkspace_sptr>(
@@ -44,7 +46,8 @@ public:
 
   MOCK_CONST_METHOD1(hasFittedPeaksForRun, bool(const RunLabel &runLabel));
 
-  MOCK_METHOD1(loadFocusedRun, bool(const std::string &filename));
+  MOCK_METHOD1(loadFocusedRun,
+               boost::optional<std::string>(const std::string &filename));
 };
 
 GCC_DIAG_ON_SUGGEST_OVERRIDE
diff --git a/qt/scientific_interfaces/test/EnggDiffGSASFittingModelTest.h b/qt/scientific_interfaces/test/EnggDiffGSASFittingModelTest.h
index 7a1c8afd05be17399e761bd3684d7d37eddafded..96b811a885f1854cbdd18fa59d671a908c24b3df 100644
--- a/qt/scientific_interfaces/test/EnggDiffGSASFittingModelTest.h
+++ b/qt/scientific_interfaces/test/EnggDiffGSASFittingModelTest.h
@@ -49,7 +49,7 @@ public:
   void addRwpValue(const RunLabel &runLabel, const double rwp);
 
 private:
-  inline boost::optional<double> doGSASRefinementAlgorithm(
+  inline double doGSASRefinementAlgorithm(
       API::MatrixWorkspace_sptr inputWorkspace,
       const std::string &outputWorkspaceName,
       const std::string &latticeParamsName, const std::string &refinementMethod,
@@ -85,8 +85,7 @@ inline bool TestEnggDiffGSASFittingModel::containsFocusedRun(
   return hasFocusedRun(runLabel);
 }
 
-inline boost::optional<double>
-TestEnggDiffGSASFittingModel::doGSASRefinementAlgorithm(
+inline double TestEnggDiffGSASFittingModel::doGSASRefinementAlgorithm(
     API::MatrixWorkspace_sptr inputWorkspace,
     const std::string &outputWorkspaceName,
     const std::string &latticeParamsName, const std::string &refinementMethod,
@@ -140,9 +139,9 @@ public:
     const static std::string inputFilename = "ENGINX_277208_focused_bank_2.nxs";
     TestEnggDiffGSASFittingModel model;
 
-    bool success = false;
-    TS_ASSERT_THROWS_NOTHING(success = model.loadFocusedRun(inputFilename));
-    TS_ASSERT(success);
+    boost::optional<std::string> failure(boost::none);
+    TS_ASSERT_THROWS_NOTHING(failure = model.loadFocusedRun(inputFilename));
+    TS_ASSERT(!failure);
 
     TS_ASSERT(model.containsFocusedRun(RunLabel(277208, 2)));
   }
@@ -151,9 +150,9 @@ public:
     const static std::string inputFilename = "ENGINX_277209_focused_bank_2.nxs";
     TestEnggDiffGSASFittingModel model;
 
-    bool success = false;
-    TS_ASSERT_THROWS_NOTHING(success = model.loadFocusedRun(inputFilename));
-    TS_ASSERT(!success);
+    boost::optional<std::string> failure(boost::none);
+    TS_ASSERT_THROWS_NOTHING(failure = model.loadFocusedRun(inputFilename));
+    TS_ASSERT(failure);
   }
 
   void test_getFocusedRun() {
@@ -288,11 +287,11 @@ public:
         API::WorkspaceFactory::Instance().create("Workspace2D", 1, 10, 10);
     model.addFocusedWorkspace(runLabel, ws);
 
-    bool success = false;
+    boost::optional<std::string> failure(boost::none);
     TS_ASSERT_THROWS_NOTHING(
-        success = model.doPawleyRefinement(
+        failure = model.doPawleyRefinement(
             runLabel, "", std::vector<std::string>({}), "", "", 0, 0));
-    TS_ASSERT(success);
+    TS_ASSERT(!failure);
 
     const auto rwp = model.getRwp(runLabel);
     TS_ASSERT(rwp);
@@ -317,11 +316,11 @@ public:
         API::WorkspaceFactory::Instance().create("Workspace2D", 1, 10, 10);
     model.addFocusedWorkspace(runLabel, ws);
 
-    bool success = false;
+    boost::optional<std::string> failure(boost::none);
     TS_ASSERT_THROWS_NOTHING(
-        success = model.doRietveldRefinement(
+        failure = model.doRietveldRefinement(
             runLabel, "", std::vector<std::string>({}), "", ""));
-    TS_ASSERT(success);
+    TS_ASSERT(!failure);
 
     const auto rwp = model.getRwp(runLabel);
     TS_ASSERT(rwp);
diff --git a/qt/scientific_interfaces/test/EnggDiffGSASFittingPresenterTest.h b/qt/scientific_interfaces/test/EnggDiffGSASFittingPresenterTest.h
index c92c5eda5780db1a2022d5d4f705af6a06586041..2c0a42520173122682e800af3f5604390fb1c1df 100644
--- a/qt/scientific_interfaces/test/EnggDiffGSASFittingPresenterTest.h
+++ b/qt/scientific_interfaces/test/EnggDiffGSASFittingPresenterTest.h
@@ -27,7 +27,7 @@ public:
         .WillOnce(Return(std::vector<std::string>({filename})));
     EXPECT_CALL(*m_mockModelPtr, loadFocusedRun(filename))
         .Times(1)
-        .WillOnce(Return(true));
+        .WillOnce(Return(boost::none));
 
     const std::vector<RunLabel> runLabels({RunLabel(123, 1)});
 
@@ -36,7 +36,7 @@ public:
         .WillOnce(Return(runLabels));
     EXPECT_CALL(*m_mockViewPtr, updateRunList(runLabels)).Times(1);
 
-    EXPECT_CALL(*m_mockViewPtr, userWarning(testing::_)).Times(0);
+    EXPECT_CALL(*m_mockViewPtr, userWarning(testing::_, testing::_)).Times(0);
 
     presenter->notify(IEnggDiffGSASFittingPresenter::LoadRun);
     assertMocksUsedCorrectly();
@@ -52,12 +52,11 @@ public:
 
     EXPECT_CALL(*m_mockModelPtr, loadFocusedRun(filename))
         .Times(1)
-        .WillOnce(Return(false));
+        .WillOnce(Return(boost::make_optional<std::string>("Failure message")));
 
     EXPECT_CALL(*m_mockModelPtr, getRunLabels()).Times(0);
 
-    EXPECT_CALL(*m_mockViewPtr,
-                userWarning("Load failed, see the log for more details"))
+    EXPECT_CALL(*m_mockViewPtr, userWarning("Load failed", "Failure message"))
         .Times(1);
 
     presenter->notify(IEnggDiffGSASFittingPresenter::LoadRun);
@@ -79,7 +78,7 @@ public:
         .WillOnce(Return(sampleWorkspace));
 
     EXPECT_CALL(*m_mockViewPtr, resetCanvas()).Times(1);
-    EXPECT_CALL(*m_mockViewPtr, userWarning(testing::_)).Times(0);
+    EXPECT_CALL(*m_mockViewPtr, userWarning(testing::_, testing::_)).Times(0);
 
     presenter->notify(IEnggDiffGSASFittingPresenter::SelectRun);
     assertMocksUsedCorrectly();
@@ -96,10 +95,10 @@ public:
         .Times(1)
         .WillOnce(Return(boost::none));
 
-    EXPECT_CALL(
-        *m_mockViewPtr,
-        userWarning(
-            "Tried to access invalid run, runNumber 123 and bank ID 1"));
+    EXPECT_CALL(*m_mockViewPtr,
+                userError("Invalid run identifier",
+                          "Tried to access invalid run, runNumber 123 and "
+                          "bank ID 1. Please contact the development team"));
 
     EXPECT_CALL(*m_mockViewPtr, resetCanvas()).Times(0);
 
@@ -144,7 +143,7 @@ public:
 
     EXPECT_CALL(*m_mockViewPtr, resetCanvas()).Times(1);
     EXPECT_CALL(*m_mockViewPtr, plotCurve(testing::_)).Times(2);
-    EXPECT_CALL(*m_mockViewPtr, userWarning(testing::_)).Times(0);
+    EXPECT_CALL(*m_mockViewPtr, userWarning(testing::_, testing::_)).Times(0);
 
     presenter->notify(IEnggDiffGSASFittingPresenter::SelectRun);
     assertMocksUsedCorrectly();
@@ -183,9 +182,10 @@ public:
                 doRietveldRefinement(runLabel, instParams, phaseFiles,
                                      pathToGSASII, GSASIIProjectFile))
         .Times(1)
-        .WillOnce(Return(false));
-    EXPECT_CALL(*m_mockViewPtr,
-                userWarning("Refinement failed, see the log for more details"));
+        .WillOnce(Return(boost::make_optional<std::string>(
+            "Refinement failure description")));
+    EXPECT_CALL(*m_mockViewPtr, userWarning("Refinement failed",
+                                            "Refinement failure description"));
 
     presenter->notify(IEnggDiffGSASFittingPresenter::DoRefinement);
     assertMocksUsedCorrectly();
@@ -233,9 +233,10 @@ public:
                                    pathToGSASII, GSASIIProjectFile, dmin,
                                    negativeWeight))
         .Times(1)
-        .WillOnce(Return(false));
-    EXPECT_CALL(*m_mockViewPtr,
-                userWarning("Refinement failed, see the log for more details"));
+        .WillOnce(Return(boost::make_optional<std::string>(
+            "Refinement failure description")));
+    EXPECT_CALL(*m_mockViewPtr, userWarning("Refinement failed",
+                                            "Refinement failure description"));
 
     presenter->notify(IEnggDiffGSASFittingPresenter::DoRefinement);
     assertMocksUsedCorrectly();
@@ -258,15 +259,15 @@ private:
   }
 
   void assertMocksUsedCorrectly() {
-    if (m_mockViewPtr) {
-      delete m_mockViewPtr;
-    }
     TSM_ASSERT("View mock not used as expected: some EXPECT_CALL conditions "
                "not satisfied",
                testing::Mock::VerifyAndClearExpectations(m_mockModelPtr));
     TSM_ASSERT("Model mock not used as expected: some EXPECT_CALL conditions "
                "not satisfied",
                testing::Mock::VerifyAndClearExpectations(m_mockViewPtr));
+    if (m_mockViewPtr) {
+      delete m_mockViewPtr;
+    }
   }
 };
 
diff --git a/qt/scientific_interfaces/test/EnggDiffGSASFittingViewMock.h b/qt/scientific_interfaces/test/EnggDiffGSASFittingViewMock.h
index 746ee222fac13cd479602f16a043942f66f34349..d1b6a00c3273d2669b1c0379d2f0b3d68d98086d 100644
--- a/qt/scientific_interfaces/test/EnggDiffGSASFittingViewMock.h
+++ b/qt/scientific_interfaces/test/EnggDiffGSASFittingViewMock.h
@@ -47,7 +47,11 @@ public:
 
   MOCK_METHOD1(updateRunList, void(const std::vector<RunLabel> &runLabels));
 
-  MOCK_CONST_METHOD1(userWarning, void(const std::string &warningDescription));
+  MOCK_CONST_METHOD2(userError, void(const std::string &errorTitle,
+                                     const std::string &errorDescription));
+
+  MOCK_CONST_METHOD2(userWarning, void(const std::string &warningTitle,
+                                       const std::string &warningDescription));
 };
 
 GCC_DIAG_ON_SUGGEST_OVERRIDE
diff --git a/qt/scientific_interfaces/test/EnggDiffMultiRunFittingWidgetModelMock.h b/qt/scientific_interfaces/test/EnggDiffMultiRunFittingWidgetModelMock.h
index 04ab22745c9fe861a968a7b48f31371e2dfda224..c84f0cd22a633c4ee03363fb3ea8e4f86a63b7f2 100644
--- a/qt/scientific_interfaces/test/EnggDiffMultiRunFittingWidgetModelMock.h
+++ b/qt/scientific_interfaces/test/EnggDiffMultiRunFittingWidgetModelMock.h
@@ -30,6 +30,8 @@ public:
   MOCK_CONST_METHOD1(getFocusedRun,
                      boost::optional<Mantid::API::MatrixWorkspace_sptr>(
                          const RunLabel &runLabel));
+
+  MOCK_CONST_METHOD1(hasFittedPeaksForRun, bool(const RunLabel &runLabel));
 };
 
 GCC_DIAG_ON_SUGGEST_OVERRIDE
diff --git a/qt/scientific_interfaces/test/EnggDiffMultiRunFittingWidgetModelTest.h b/qt/scientific_interfaces/test/EnggDiffMultiRunFittingWidgetModelTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..b359ef2697124196bea84805fb63e8de1cce4f09
--- /dev/null
+++ b/qt/scientific_interfaces/test/EnggDiffMultiRunFittingWidgetModelTest.h
@@ -0,0 +1,84 @@
+#ifndef MANTIDQT_CUSTOMINTERFACES_ENGGDIFFMULTIRUNFITTINGWIDGETMODELTEST_H_
+#define MANTIDQT_CUSTOMINTERFACES_ENGGDIFFMULTIRUNFITTINGWIDGETMODELTEST_H_
+
+#include "../EnggDiffraction/EnggDiffMultiRunFittingWidgetModel.h"
+
+#include "MantidTestHelpers/WorkspaceCreationHelper.h"
+
+#include <cxxtest/TestSuite.h>
+
+using namespace MantidQt::CustomInterfaces;
+
+class EnggDiffMultiRunFittingWidgetModelTest : public CxxTest::TestSuite {
+public:
+  void test_addAndGetFittedPeaks() {
+    EnggDiffMultiRunFittingWidgetModel model;
+
+    Mantid::API::MatrixWorkspace_sptr ws =
+        WorkspaceCreationHelper::create2DWorkspaceBinned(4, 4, 0.5);
+    const RunLabel runLabel(123, 1);
+    TS_ASSERT_THROWS_NOTHING(model.addFittedPeaks(runLabel, ws));
+
+    boost::optional<Mantid::API::MatrixWorkspace_sptr> retrievedWS(boost::none);
+    TS_ASSERT_THROWS_NOTHING(retrievedWS = model.getFittedPeaks(runLabel));
+    TS_ASSERT(retrievedWS);
+    TS_ASSERT_EQUALS(ws, *retrievedWS);
+  }
+
+  void test_getFittedPeaksSucceedsWhenWorkspaceNotInModel() {
+    EnggDiffMultiRunFittingWidgetModel model;
+    boost::optional<Mantid::API::MatrixWorkspace_sptr> retrievedWS(boost::none);
+    TS_ASSERT_THROWS_NOTHING(retrievedWS =
+                                 model.getFittedPeaks(RunLabel(123, 1)));
+    TS_ASSERT(!retrievedWS);
+  }
+
+  void test_addAndGetFocusedRun() {
+    EnggDiffMultiRunFittingWidgetModel model;
+
+    Mantid::API::MatrixWorkspace_sptr ws =
+        WorkspaceCreationHelper::create2DWorkspaceBinned(4, 4, 0.5);
+    const RunLabel runLabel(123, 1);
+    TS_ASSERT_THROWS_NOTHING(model.addFocusedRun(runLabel, ws));
+
+    boost::optional<Mantid::API::MatrixWorkspace_sptr> retrievedWS(boost::none);
+    TS_ASSERT_THROWS_NOTHING(retrievedWS = model.getFocusedRun(runLabel));
+    TS_ASSERT(retrievedWS);
+    TS_ASSERT_EQUALS(ws, *retrievedWS);
+  }
+
+  void test_getFocusedRunSucceedsWhenWorkspaceNotInModel() {
+    EnggDiffMultiRunFittingWidgetModel model;
+    boost::optional<Mantid::API::MatrixWorkspace_sptr> retrievedWS(boost::none);
+    TS_ASSERT_THROWS_NOTHING(retrievedWS =
+                                 model.getFocusedRun(RunLabel(123, 1)));
+    TS_ASSERT(!retrievedWS);
+  }
+
+  void test_getAllWorkspaceLabels() {
+    EnggDiffMultiRunFittingWidgetModel model;
+
+    Mantid::API::MatrixWorkspace_sptr ws =
+        WorkspaceCreationHelper::create2DWorkspaceBinned(4, 4, 0.5);
+
+    const RunLabel label1(123, 1);
+    model.addFocusedRun(label1, ws);
+    const RunLabel label2(456, 2);
+    model.addFocusedRun(label2, ws);
+
+    const std::vector<RunLabel> expectedLabels1({label1, label2});
+
+    std::vector<RunLabel> retrievedLabels;
+    TS_ASSERT_THROWS_NOTHING(retrievedLabels = model.getAllWorkspaceLabels());
+    TS_ASSERT_EQUALS(expectedLabels1, retrievedLabels);
+
+    const RunLabel label3(456, 1);
+    model.addFocusedRun(label3, ws);
+    model.addFittedPeaks(RunLabel(123, 2), ws);
+    const std::vector<RunLabel> expectedLabels2 = {label1, label3, label2};
+    TS_ASSERT_THROWS_NOTHING(retrievedLabels = model.getAllWorkspaceLabels());
+    TS_ASSERT_EQUALS(expectedLabels2, retrievedLabels);
+  }
+};
+
+#endif // MANTIDQT_CUSTOMINTERFACES_ENGGDIFFMULTIRUNFITTINGWIDGETMODELTEST_H_
diff --git a/qt/scientific_interfaces/test/EnggDiffMultiRunFittingWidgetPresenterTest.h b/qt/scientific_interfaces/test/EnggDiffMultiRunFittingWidgetPresenterTest.h
index 3cf09b7dbfe9d53dd45dac4703d823d7a38f735d..281698ad98fcbed1755a170b34de3a7237280ba7 100644
--- a/qt/scientific_interfaces/test/EnggDiffMultiRunFittingWidgetPresenterTest.h
+++ b/qt/scientific_interfaces/test/EnggDiffMultiRunFittingWidgetPresenterTest.h
@@ -6,6 +6,7 @@
 #include "EnggDiffMultiRunFittingWidgetViewMock.h"
 
 #include "MantidAPI/WorkspaceFactory.h"
+#include "MantidTestHelpers/WorkspaceCreationHelper.h"
 
 #include <cxxtest/TestSuite.h>
 
@@ -14,12 +15,17 @@ using namespace Mantid;
 using namespace MantidQt::CustomInterfaces;
 using testing::Return;
 
+namespace {
+API::MatrixWorkspace_sptr createSampleWorkspace() {
+  return API::WorkspaceFactory::Instance().create("Workspace2D", 1, 1, 1);
+}
+}
+
 class EnggDiffMultiRunFittingWidgetPresenterTest : public CxxTest::TestSuite {
 public:
   void test_addFittedPeaks() {
     auto presenter = setUpPresenter();
-    const API::MatrixWorkspace_sptr ws =
-        API::WorkspaceFactory::Instance().create("Workspace2D", 1, 1, 1);
+    const auto ws = createSampleWorkspace();
 
     const RunLabel runLabel(123, 1);
     EXPECT_CALL(*m_mockModel, addFittedPeaks(runLabel, ws)).Times(1);
@@ -30,9 +36,7 @@ public:
 
   void test_focusedRunIsAddedToModel() {
     auto presenter = setUpPresenter();
-    const API::MatrixWorkspace_sptr ws =
-        API::WorkspaceFactory::Instance().create("Workspace2D", 1, 1, 1);
-
+    const API::MatrixWorkspace_sptr ws = createSampleWorkspace();
     const RunLabel runLabel(123, 1);
 
     EXPECT_CALL(*m_mockModel, addFocusedRun(runLabel, ws)).Times(1);
@@ -48,8 +52,6 @@ public:
 
   void test_loadRunUpdatesView() {
     auto presenter = setUpPresenter();
-    const API::MatrixWorkspace_sptr ws =
-        API::WorkspaceFactory::Instance().create("Workspace2D", 1, 1, 1);
 
     const RunLabel runLabel(123, 1);
     const std::vector<RunLabel> workspaceLabels({runLabel});
@@ -57,7 +59,7 @@ public:
         .WillByDefault(Return(workspaceLabels));
     EXPECT_CALL(*m_mockView, updateRunList(workspaceLabels));
 
-    presenter->addFocusedRun(runLabel, ws);
+    presenter->addFocusedRun(runLabel, createSampleWorkspace());
     assertMocksUsedCorrectly();
   }
 
@@ -85,6 +87,73 @@ public:
     assertMocksUsedCorrectly();
   }
 
+  void test_selectValidRunWithoutFittedPeaks() {
+    auto presenter = setUpPresenter();
+
+    const RunLabel runLabel(123, 1);
+    EXPECT_CALL(*m_mockView, getSelectedRunLabel())
+        .Times(1)
+        .WillOnce(Return(runLabel));
+
+    EXPECT_CALL(*m_mockModel, getFocusedRun(runLabel))
+        .Times(1)
+        .WillOnce(Return(createSampleWorkspace()));
+
+    EXPECT_CALL(*m_mockView, reportPlotInvalidFocusedRun(testing::_)).Times(0);
+    EXPECT_CALL(*m_mockView, resetCanvas()).Times(1);
+    EXPECT_CALL(*m_mockView, plotFocusedRun(testing::_)).Times(1);
+
+    ON_CALL(*m_mockModel, hasFittedPeaksForRun(runLabel))
+        .WillByDefault(Return(false));
+    EXPECT_CALL(*m_mockView, plotFittedPeaks(testing::_)).Times(0);
+
+    presenter->notify(
+        IEnggDiffMultiRunFittingWidgetPresenter::Notification::SelectRun);
+    assertMocksUsedCorrectly();
+  }
+
+  void test_selectRunInvalid() {
+    auto presenter = setUpPresenter();
+
+    const RunLabel runLabel(123, 1);
+    EXPECT_CALL(*m_mockView, getSelectedRunLabel())
+        .Times(1)
+        .WillOnce(Return(runLabel));
+    EXPECT_CALL(*m_mockModel, getFocusedRun(runLabel))
+        .Times(1)
+        .WillOnce(Return(boost::none));
+    EXPECT_CALL(*m_mockView, reportPlotInvalidFocusedRun(runLabel)).Times(1);
+    EXPECT_CALL(*m_mockView, resetCanvas()).Times(0);
+
+    presenter->notify(
+        IEnggDiffMultiRunFittingWidgetPresenter::Notification::SelectRun);
+    assertMocksUsedCorrectly();
+  }
+
+  void test_selectValidRunWithFittedPeaks() {
+    auto presenter = setUpPresenter();
+
+    const RunLabel runLabel(123, 1);
+    ON_CALL(*m_mockView, getSelectedRunLabel()).WillByDefault(Return(runLabel));
+
+    const auto sampleWorkspace = createSampleWorkspace();
+    ON_CALL(*m_mockModel, getFocusedRun(runLabel))
+        .WillByDefault(Return(sampleWorkspace));
+
+    ON_CALL(*m_mockModel, hasFittedPeaksForRun(runLabel))
+        .WillByDefault(Return(true));
+    ON_CALL(*m_mockView, showFitResultsSelected()).WillByDefault(Return(true));
+    EXPECT_CALL(*m_mockModel, getFittedPeaks(runLabel))
+        .Times(1)
+        .WillOnce(Return(sampleWorkspace));
+    EXPECT_CALL(*m_mockView, reportPlotInvalidFittedPeaks(testing::_)).Times(0);
+    EXPECT_CALL(*m_mockView, plotFittedPeaks(testing::_)).Times(1);
+
+    presenter->notify(
+        IEnggDiffMultiRunFittingWidgetPresenter::Notification::SelectRun);
+    assertMocksUsedCorrectly();
+  }
+
 private:
   MockEnggDiffMultiRunFittingWidgetModel *m_mockModel;
   MockEnggDiffMultiRunFittingWidgetView *m_mockView;
diff --git a/qt/scientific_interfaces/test/EnggDiffMultiRunFittingWidgetViewMock.h b/qt/scientific_interfaces/test/EnggDiffMultiRunFittingWidgetViewMock.h
index 27b3ea0d80b23710397ed4bb885a1eb904dbb4f4..707723d4e5d4474c10896286a85f67e24d3c44e8 100644
--- a/qt/scientific_interfaces/test/EnggDiffMultiRunFittingWidgetViewMock.h
+++ b/qt/scientific_interfaces/test/EnggDiffMultiRunFittingWidgetViewMock.h
@@ -14,7 +14,26 @@ class MockEnggDiffMultiRunFittingWidgetView
     : public IEnggDiffMultiRunFittingWidgetView {
 
 public:
+  MOCK_CONST_METHOD0(getSelectedRunLabel, RunLabel());
+
+  MOCK_METHOD1(plotFittedPeaks,
+               void(const std::vector<boost::shared_ptr<QwtData>> &curve));
+
+  MOCK_METHOD1(plotFocusedRun,
+               void(const std::vector<boost::shared_ptr<QwtData>> &curve));
+
+  MOCK_METHOD1(reportPlotInvalidFittedPeaks, void(const RunLabel &runLabel));
+
+  MOCK_METHOD1(reportPlotInvalidFocusedRun, void(const RunLabel &runLabel));
+
+  MOCK_METHOD0(resetCanvas, void());
+
+  MOCK_CONST_METHOD0(showFitResultsSelected, bool());
+
   MOCK_METHOD1(updateRunList, void(const std::vector<RunLabel> &runLabels));
+
+  MOCK_METHOD2(userError, void(const std::string &errorTitle,
+                               const std::string &errorDescription));
 };
 
 GCC_DIAG_ON_SUGGEST_OVERRIDE
diff --git a/qt/scientific_interfaces/test/ReflDataProcessorPresenterTest.h b/qt/scientific_interfaces/test/ReflDataProcessorPresenterTest.h
index 28dfb0131711cff9f2b71395a07b470da773f446..dce77a368a8c78d4e8eb55354c0ee1d59b558c0f 100644
--- a/qt/scientific_interfaces/test/ReflDataProcessorPresenterTest.h
+++ b/qt/scientific_interfaces/test/ReflDataProcessorPresenterTest.h
@@ -138,6 +138,8 @@ public:
     DefaultValue<ColumnOptionsQMap>::Clear();
   }
 
+  static auto constexpr DEFAULT_GROUP_NUMBER = 1;
+
   void testProcessEventWorkspacesUniformEvenSlicing() {
     NiceMock<MockDataProcessorView> mockDataProcessorView;
     NiceMock<MockProgressableView> mockProgress;
@@ -153,7 +155,7 @@ public:
         .Times(1)
         .WillOnce(Return(""));
 
-    auto presenter = presenterFactory.create();
+    auto presenter = presenterFactory.create(DEFAULT_GROUP_NUMBER);
     presenter->acceptViews(&mockDataProcessorView, &mockProgress);
     presenter->accept(&mockMainPresenter);
 
@@ -238,7 +240,7 @@ public:
         .Times(1)
         .WillOnce(Return(""));
 
-    auto presenter = presenterFactory.create();
+    auto presenter = presenterFactory.create(DEFAULT_GROUP_NUMBER);
     presenter->acceptViews(&mockDataProcessorView, &mockProgress);
     presenter->accept(&mockMainPresenter);
 
@@ -334,7 +336,7 @@ public:
         .Times(1)
         .WillOnce(Return(""));
 
-    auto presenter = presenterFactory.create();
+    auto presenter = presenterFactory.create(DEFAULT_GROUP_NUMBER);
     presenter->acceptViews(&mockDataProcessorView, &mockProgress);
     presenter->accept(&mockMainPresenter);
 
@@ -418,7 +420,7 @@ public:
     EXPECT_CALL(mockMainPresenter, getPostprocessingOptionsAsString())
         .Times(1)
         .WillOnce(Return(""));
-    auto presenter = presenterFactory.create();
+    auto presenter = presenterFactory.create(DEFAULT_GROUP_NUMBER);
     presenter->acceptViews(&mockDataProcessorView, &mockProgress);
     presenter->accept(&mockMainPresenter);
 
@@ -509,7 +511,7 @@ public:
         .Times(1)
         .WillOnce(Return(true));
 
-    auto presenter = presenterFactory.create();
+    auto presenter = presenterFactory.create(DEFAULT_GROUP_NUMBER);
     presenter->acceptViews(&mockDataProcessorView, &mockProgress);
     presenter->accept(&mockMainPresenter);
 
@@ -565,7 +567,7 @@ public:
         .Times(1)
         .WillOnce(Return(""));
 
-    auto presenter = presenterFactory.create();
+    auto presenter = presenterFactory.create(DEFAULT_GROUP_NUMBER);
     presenter->acceptViews(&mockDataProcessorView, &mockProgress);
     presenter->accept(&mockMainPresenter);
 
@@ -614,7 +616,7 @@ public:
     NiceMock<MockDataProcessorView> mockDataProcessorView;
     NiceMock<MockProgressableView> mockProgress;
     NiceMock<MockMainPresenter> mockMainPresenter;
-    auto presenter = presenterFactory.create();
+    auto presenter = presenterFactory.create(DEFAULT_GROUP_NUMBER);
     presenter->acceptViews(&mockDataProcessorView, &mockProgress);
     presenter->accept(&mockMainPresenter);
 
@@ -690,7 +692,7 @@ public:
     NiceMock<MockDataProcessorView> mockDataProcessorView;
     NiceMock<MockProgressableView> mockProgress;
     NiceMock<MockMainPresenter> mockMainPresenter;
-    auto presenter = presenterFactory.create();
+    auto presenter = presenterFactory.create(DEFAULT_GROUP_NUMBER);
     presenter->acceptViews(&mockDataProcessorView, &mockProgress);
     presenter->accept(&mockMainPresenter);
 
@@ -765,7 +767,7 @@ public:
     NiceMock<MockDataProcessorView> mockDataProcessorView;
     NiceMock<MockProgressableView> mockProgress;
     NiceMock<MockMainPresenter> mockMainPresenter;
-    auto presenter = presenterFactory.create();
+    auto presenter = presenterFactory.create(DEFAULT_GROUP_NUMBER);
     presenter->acceptViews(&mockDataProcessorView, &mockProgress);
     presenter->accept(&mockMainPresenter);
 
@@ -810,7 +812,7 @@ public:
     NiceMock<MockDataProcessorView> mockDataProcessorView;
     NiceMock<MockProgressableView> mockProgress;
     NiceMock<MockMainPresenter> mockMainPresenter;
-    auto presenter = presenterFactory.create();
+    auto presenter = presenterFactory.create(DEFAULT_GROUP_NUMBER);
     presenter->acceptViews(&mockDataProcessorView, &mockProgress);
     presenter->accept(&mockMainPresenter);
 
diff --git a/qt/scientific_interfaces/test/ReflEventPresenterTest.h b/qt/scientific_interfaces/test/ReflEventPresenterTest.h
index 80e00afef36b5d32615c1213d30052c84a1cb3f0..1b609697e0b517d7c69281441d27911e4ad0de68 100644
--- a/qt/scientific_interfaces/test/ReflEventPresenterTest.h
+++ b/qt/scientific_interfaces/test/ReflEventPresenterTest.h
@@ -45,6 +45,26 @@ public:
 
     TS_ASSERT(Mock::VerifyAndClearExpectations(&mockView));
   }
+
+  void testDisablesControlsOnReductionResumed() {
+    MockEventView mockView;
+    ReflEventPresenter presenter(&mockView);
+    EXPECT_CALL(mockView, disableAll()).Times(AtLeast(1));
+
+    presenter.onReductionResumed();
+
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&mockView));
+  }
+
+  void testEnabledControlsOnReductionPaused() {
+    MockEventView mockView;
+    ReflEventPresenter presenter(&mockView);
+    EXPECT_CALL(mockView, enableAll()).Times(AtLeast(1));
+
+    presenter.onReductionPaused();
+
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&mockView));
+  }
 };
 
 #endif /* MANTID_CUSTOMINTERFACES_REFLEVENTPRESENTERTEST_H */
diff --git a/qt/scientific_interfaces/test/ReflEventTabPresenterTest.h b/qt/scientific_interfaces/test/ReflEventTabPresenterTest.h
index 675e8466d6249a4a7b7808596d801caf0a197582..6ce5690c517ae682f62613a95ce3aab877911233 100644
--- a/qt/scientific_interfaces/test/ReflEventTabPresenterTest.h
+++ b/qt/scientific_interfaces/test/ReflEventTabPresenterTest.h
@@ -45,7 +45,37 @@ public:
     TS_ASSERT(Mock::VerifyAndClearExpectations(&presenter_2));
   }
 
-  void test_get_slicing_type() {
+  void testPassesMessageToCorrectChildPresenterWhenReductionPaused() {
+    MockEventPresenter presenter_1;
+    MockEventPresenter presenter_2;
+    std::vector<IReflEventPresenter *> settingsPresenters;
+    settingsPresenters.push_back(&presenter_1);
+    settingsPresenters.push_back(&presenter_2);
+    ReflEventTabPresenter presenter(settingsPresenters);
+    EXPECT_CALL(presenter_2, onReductionPaused());
+
+    presenter.onReductionPaused(1);
+
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&presenter_1));
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&presenter_2));
+  }
+
+  void testPassesMessageToCorrectChildPresenterWhenReductionResumed() {
+    MockEventPresenter presenter_1;
+    MockEventPresenter presenter_2;
+    std::vector<IReflEventPresenter *> settingsPresenters;
+    settingsPresenters.push_back(&presenter_1);
+    settingsPresenters.push_back(&presenter_2);
+    ReflEventTabPresenter presenter(settingsPresenters);
+    EXPECT_CALL(presenter_1, onReductionResumed());
+
+    presenter.onReductionResumed(0);
+
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&presenter_1));
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&presenter_2));
+  }
+
+  void testGetSlicingType() {
     MockEventPresenter presenter_1;
     MockEventPresenter presenter_2;
     std::vector<IReflEventPresenter *> settingsPresenters;
diff --git a/qt/scientific_interfaces/test/ReflMockObjects.h b/qt/scientific_interfaces/test/ReflMockObjects.h
index bb406f63d1cb9a51d10a3f2d352a1c16a3915fdf..f71f6cafb6e4796046825a1095c2e731a5c4b6ff 100644
--- a/qt/scientific_interfaces/test/ReflMockObjects.h
+++ b/qt/scientific_interfaces/test/ReflMockObjects.h
@@ -4,6 +4,7 @@
 #include "MantidKernel/ICatalogInfo.h"
 #include "MantidKernel/ProgressBase.h"
 #include "MantidKernel/WarningSuppressions.h"
+#include "MantidAPI/AlgorithmManager.h"
 #include "../ISISReflectometry/IReflEventPresenter.h"
 #include "../ISISReflectometry/IReflEventTabPresenter.h"
 #include "../ISISReflectometry/IReflEventView.h"
@@ -112,6 +113,8 @@ public:
   MOCK_METHOD1(setDetectorCorrectionEnabled, void(bool));
   MOCK_METHOD1(setExpDefaults, void(ExperimentOptionDefaults));
   MOCK_METHOD1(setInstDefaults, void(InstrumentOptionDefaults));
+  MOCK_METHOD0(disableAll, void());
+  MOCK_METHOD0(enableAll, void());
   MOCK_CONST_METHOD0(getDetectorCorrectionType, std::string());
   MOCK_CONST_METHOD0(experimentSettingsEnabled, bool());
   MOCK_CONST_METHOD0(instrumentSettingsEnabled, bool());
@@ -132,6 +135,8 @@ public:
   // Global options
   MOCK_CONST_METHOD0(getTimeSlicingValues, std::string());
   MOCK_CONST_METHOD0(getTimeSlicingType, std::string());
+  MOCK_METHOD0(enableAll, void());
+  MOCK_METHOD0(disableAll, void());
 
   // Calls we don't care about
   IReflEventPresenter *getPresenter() const override { return nullptr; }
@@ -191,6 +196,8 @@ class MockEventPresenter : public IReflEventPresenter {
 public:
   MOCK_CONST_METHOD0(getTimeSlicingValues, std::string());
   MOCK_CONST_METHOD0(getTimeSlicingType, std::string());
+  MOCK_METHOD0(onReductionPaused, void());
+  MOCK_METHOD0(onReductionResumed, void());
   ~MockEventPresenter() override{};
 };
 
@@ -199,11 +206,15 @@ public:
   std::string getTimeSlicingValues(int group) const override {
     UNUSED_ARG(group)
     return std::string();
-  };
+  }
   std::string getTimeSlicingType(int group) const override {
     UNUSED_ARG(group)
     return std::string();
-  };
+  }
+
+  MOCK_METHOD1(onReductionPaused, void(int));
+  MOCK_METHOD1(onReductionResumed, void(int));
+
   ~MockEventTabPresenter() override{};
 };
 
@@ -213,9 +224,14 @@ public:
   MOCK_CONST_METHOD0(getTransmissionOptions, OptionsQMap());
   MOCK_CONST_METHOD0(getReductionOptions, OptionsQMap());
   MOCK_CONST_METHOD0(getStitchOptions, std::string());
+  MOCK_METHOD0(onReductionPaused, void());
+  MOCK_METHOD0(onReductionResumed, void());
   MOCK_METHOD1(acceptTabPresenter, void(IReflSettingsTabPresenter *));
   MOCK_METHOD1(setInstrumentName, void(const std::string &));
   void notify(IReflSettingsPresenter::Flag flag) override { UNUSED_ARG(flag); }
+  IAlgorithm_sptr createReductionAlg() override {
+    return AlgorithmManager::Instance().create("ReflectometryReductionOneAuto");
+  }
   ~MockSettingsPresenter() override{};
 };
 
@@ -230,6 +246,8 @@ public:
   void setInstrumentName(const std::string &instName) override {
     UNUSED_ARG(instName);
   };
+  MOCK_METHOD1(onReductionPaused, void(int));
+  MOCK_METHOD1(onReductionResumed, void(int));
   ~MockSettingsTabPresenter() override{};
 };
 
@@ -239,6 +257,9 @@ public:
   void acceptMainPresenter(IReflMainWindowPresenter *presenter) override {
     UNUSED_ARG(presenter);
   };
+
+  MOCK_METHOD0(onAnyReductionPaused, void());
+  MOCK_METHOD0(onAnyReductionResumed, void());
   ~MockSaveTabPresenter() override{};
 };
 
@@ -251,6 +272,8 @@ public:
   MOCK_CONST_METHOD1(setInstrumentName, void(const std::string &instName));
   MOCK_CONST_METHOD0(getInstrumentName, std::string());
   MOCK_METHOD1(notify, void(IReflMainWindowPresenter::Flag));
+  MOCK_METHOD1(notifyReductionPaused, void(int));
+  MOCK_METHOD1(notifyReductionResumed, void(int));
   MOCK_METHOD3(askUserString,
                std::string(const std::string &, const std::string &,
                            const std::string &));
diff --git a/qt/scientific_interfaces/test/ReflRunsTabPresenterTest.h b/qt/scientific_interfaces/test/ReflRunsTabPresenterTest.h
index c3fb9ffa5097980885ab55bf00781c29d26c16e3..1e756c7bd772457599a33b34e725fed5996910a4 100644
--- a/qt/scientific_interfaces/test/ReflRunsTabPresenterTest.h
+++ b/qt/scientific_interfaces/test/ReflRunsTabPresenterTest.h
@@ -336,13 +336,12 @@ public:
                                    tablePresenterVec);
     presenter.acceptMainPresenter(&mockMainPresenter);
 
+    constexpr int GROUP_NUMBER = 0;
     // Expect that the main presenter is notified that data reduction is paused
-    EXPECT_CALL(
-        mockMainPresenter,
-        notify(IReflMainWindowPresenter::Flag::ConfirmReductionPausedFlag))
+    EXPECT_CALL(mockMainPresenter, notifyReductionPaused(GROUP_NUMBER))
         .Times(Exactly(1));
 
-    presenter.confirmReductionPaused();
+    presenter.confirmReductionPaused(GROUP_NUMBER);
 
     // Verify expectations
     TS_ASSERT(Mock::VerifyAndClearExpectations(&mockRunsTabView));
@@ -359,13 +358,12 @@ public:
                                    tablePresenterVec);
     presenter.acceptMainPresenter(&mockMainPresenter);
 
+    auto GROUP_NUMBER = 0;
     // Expect that the main presenter is notified that data reduction is resumed
-    EXPECT_CALL(
-        mockMainPresenter,
-        notify(IReflMainWindowPresenter::Flag::ConfirmReductionResumedFlag))
+    EXPECT_CALL(mockMainPresenter, notifyReductionResumed(GROUP_NUMBER))
         .Times(Exactly(1));
 
-    presenter.confirmReductionResumed();
+    presenter.confirmReductionResumed(GROUP_NUMBER);
 
     // Verify expectations
     TS_ASSERT(Mock::VerifyAndClearExpectations(&mockRunsTabView));
diff --git a/qt/scientific_interfaces/test/ReflSettingsPresenterTest.h b/qt/scientific_interfaces/test/ReflSettingsPresenterTest.h
index 0e53580ea71be2ade7d6c9ef8a8d8bbdfea28e83..6bf9d12b4576b4bcfd63627d98bfbd4c14ccb5ba 100644
--- a/qt/scientific_interfaces/test/ReflSettingsPresenterTest.h
+++ b/qt/scientific_interfaces/test/ReflSettingsPresenterTest.h
@@ -439,6 +439,27 @@ public:
     EXPECT_CALL(mockView, setPolarisationOptionsEnabled(true))
         .Times(Exactly(1));
     presenter.setInstrumentName("POLREF");
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&mockView));
+  }
+
+  void testDisablesControlsWhenReductionResumed() {
+    NiceMock<MockSettingsView> mockView;
+    auto presenter = makeReflSettingsPresenter(&mockView);
+
+    EXPECT_CALL(mockView, disableAll());
+    presenter.onReductionResumed();
+
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&mockView));
+  }
+
+  void testEnablesControlsWhenReductionPaused() {
+    NiceMock<MockSettingsView> mockView;
+    auto presenter = makeReflSettingsPresenter(&mockView);
+
+    EXPECT_CALL(mockView, enableAll());
+    presenter.onReductionPaused();
+
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&mockView));
   }
 
   void testExperimentDefaults() {
diff --git a/qt/scientific_interfaces/test/ReflSettingsTabPresenterTest.h b/qt/scientific_interfaces/test/ReflSettingsTabPresenterTest.h
index 270bc6793b6ce828acc03a33a594f12553e97cd3..f23c6092f39092e67590c96739a7ab2e67f7c2e0 100644
--- a/qt/scientific_interfaces/test/ReflSettingsTabPresenterTest.h
+++ b/qt/scientific_interfaces/test/ReflSettingsTabPresenterTest.h
@@ -206,6 +206,46 @@ public:
     TS_ASSERT(Mock::VerifyAndClearExpectations(&presenter_1));
     TS_ASSERT(Mock::VerifyAndClearExpectations(&presenter_2));
   }
+
+  void test_passes_message_to_child_presenters_when_reduction_paused() {
+    MockSettingsPresenter presenter_0;
+    MockSettingsPresenter presenter_1;
+    MockSettingsPresenter presenter_2;
+
+    std::vector<IReflSettingsPresenter *> settingsPresenters;
+    settingsPresenters.push_back(&presenter_0);
+    settingsPresenters.push_back(&presenter_1);
+    settingsPresenters.push_back(&presenter_2);
+    ReflSettingsTabPresenter presenter(settingsPresenters);
+
+    EXPECT_CALL(presenter_1, onReductionPaused());
+
+    presenter.onReductionPaused(1);
+
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&presenter_0));
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&presenter_1));
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&presenter_2));
+  }
+
+  void test_passes_message_to_child_presenters_when_reduction_resumed() {
+    MockSettingsPresenter presenter_0;
+    MockSettingsPresenter presenter_1;
+    MockSettingsPresenter presenter_2;
+
+    std::vector<IReflSettingsPresenter *> settingsPresenters;
+    settingsPresenters.push_back(&presenter_0);
+    settingsPresenters.push_back(&presenter_1);
+    settingsPresenters.push_back(&presenter_2);
+    ReflSettingsTabPresenter presenter(settingsPresenters);
+
+    EXPECT_CALL(presenter_1, onReductionResumed());
+
+    presenter.onReductionResumed(1);
+
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&presenter_0));
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&presenter_1));
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&presenter_2));
+  }
 };
 
 #endif /* MANTID_CUSTOMINTERFACES_REFLSETTINGSTABPRESENTERTEST_H */
diff --git a/qt/widgets/common/CMakeLists.txt b/qt/widgets/common/CMakeLists.txt
index d69a7d1c144fecc0434a54494ed4b4ae456c16d6..9524c4ba0525ec3b1b87608642237e2bcf00ab21 100644
--- a/qt/widgets/common/CMakeLists.txt
+++ b/qt/widgets/common/CMakeLists.txt
@@ -497,6 +497,8 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsCommon
   OSX_INSTALL_RPATH
     @loader_path/../MacOS
     @loader_path/../Libraries
+  LINUX_INSTALL_RPATH
+    "\$ORIGIN/../../${LIB_DIR}"
 )
 
 ###########################################################################
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/DataProcessorMainPresenter.h b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/DataProcessorMainPresenter.h
index 2042a0e79e1fa44ef7aa9f455de10072dff532a2..e33e3272f8a49b1de149d3c6d820bb5deebd58f3 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/DataProcessorMainPresenter.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/DataProcessorMainPresenter.h
@@ -72,8 +72,8 @@ public:
   virtual void resume() const {}
 
   /// Handle data reduction paused/resumed confirmation
-  virtual void confirmReductionPaused() const {}
-  virtual void confirmReductionResumed() const {}
+  virtual void confirmReductionPaused(int group) { UNUSED_ARG(group); }
+  virtual void confirmReductionResumed(int group) { UNUSED_ARG(group); }
 };
 }
 }
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/DataProcessorView.h b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/DataProcessorView.h
index 798395d0175565e72bd35eb6ddccd0cec6c4276d..ce30ce10ef6a784b4cdc1caa9c91b941d342f9f5 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/DataProcessorView.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/DataProcessorView.h
@@ -10,7 +10,6 @@
 #include <string>
 
 class AbstractTreeModel;
-
 namespace MantidQt {
 namespace MantidWidgets {
 class HintStrategy;
@@ -117,6 +116,8 @@ public:
 
   //
   virtual void skipProcessing() = 0;
+  virtual void enableGrouping() = 0;
+  virtual void disableGrouping() = 0;
 };
 }
 }
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/DataProcessorWidget.ui b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/DataProcessorWidget.ui
index caf16fde8afc2754ba3b0111b12a661b8bc9c53e..6cc935bd9880bdccfe8461b3da32a99053ad1acd 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/DataProcessorWidget.ui
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/DataProcessorWidget.ui
@@ -11,143 +11,146 @@
    </rect>
   </property>
   <layout class="QHBoxLayout" name="layoutTableView">
-    <property name="spacing">
-           <number>1</number>
+   <property name="spacing">
+    <number>1</number>
+   </property>
+   <property name="leftMargin">
+    <number>1</number>
+   </property>
+   <property name="topMargin">
+    <number>1</number>
+   </property>
+   <property name="rightMargin">
+    <number>1</number>
+   </property>
+   <property name="bottomMargin">
+    <number>1</number>
+   </property>
+   <item>
+    <layout class="QVBoxLayout" name="layoutButtonsTable">
+     <property name="spacing">
+      <number>1</number>
+     </property>
+     <item>
+      <widget class="QToolBar" name="rowToolBar">
+       <property name="styleSheet">
+        <string>QToolBar{border: none;}</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QTreeView" name="viewTable">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="contextMenuPolicy">
+        <enum>Qt::CustomContextMenu</enum>
+       </property>
+       <property name="editTriggers">
+        <set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
+       </property>
+       <property name="alternatingRowColors">
+        <bool>true</bool>
+       </property>
+       <property name="selectionMode">
+        <enum>QAbstractItemView::ExtendedSelection</enum>
+       </property>
+       <property name="selectionBehavior">
+        <enum>QAbstractItemView::SelectRows</enum>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <layout class="QGridLayout" name="processLayout"/>
+     </item>
+     <item>
+      <layout class="QHBoxLayout" name="layoutRowButtons">
+       <property name="leftMargin">
+        <number>1</number>
+       </property>
+       <property name="topMargin">
+        <number>1</number>
+       </property>
+       <property name="rightMargin">
+        <number>1</number>
+       </property>
+       <property name="bottomMargin">
+        <number>1</number>
+       </property>
+       <item>
+        <widget class="QProgressBar" name="progressBar">
+         <property name="toolTip">
+          <string>The progress through processing the current reduction.</string>
+         </property>
+         <property name="whatsThis">
+          <string>Shows the current progress when processing.</string>
+         </property>
+         <property name="value">
+          <number>0</number>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="labelProcessInstrument">
+         <property name="text">
+          <string>Instrument:</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QComboBox" name="comboProcessInstrument">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="toolTip">
+          <string>Select the instrument to assume for fetching runs</string>
+         </property>
+         <property name="whatsThis">
+          <string>Specifies the instrument that data being processed was generated by. This is used to help identify the correct data to load when given sample run numbers to process.</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QCheckBox" name="checkEnableNotebook">
+         <property name="toolTip">
+          <string>Whether to generate a python notebook when performing a reduction</string>
          </property>
-    <property name="margin">
-           <number>1</number>
+         <property name="text">
+          <string>Output Notebook</string>
          </property>
-    <item>
-           <layout class="QVBoxLayout" name="layoutButtonsTable">
-             <property name="spacing">
-               <number>1</number>
-             </property>
-             <item>
-               <widget class="QToolBar" name="rowToolBar">
-                 <property name="styleSheet">
-                   <string>QToolBar{border: none;}</string>
-                 </property>
-               </widget>
-             </item>
-             <item>
-               <widget class="QTreeView" name="viewTable">
-                 <property name="sizePolicy">
-                   <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
-                     <horstretch>0</horstretch>
-                     <verstretch>0</verstretch>
-                   </sizepolicy>
-                 </property>
-                 <property name="contextMenuPolicy">
-                   <enum>Qt::CustomContextMenu</enum>
-                 </property>
-                 <property name="editTriggers">
-                   <set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
-                 </property>
-                 <property name="alternatingRowColors">
-                   <bool>true</bool>
-                 </property>
-                 <property name="selectionMode">
-                   <enum>QAbstractItemView::ExtendedSelection</enum>
-                 </property>
-                 <property name="selectionBehavior">
-                   <enum>QAbstractItemView::SelectRows</enum>
-                 </property>
-                 <attribute name="horizontalHeaderDefaultSectionSize">
-                   <number>60</number>
-                 </attribute>
-                 <attribute name="horizontalHeaderMinimumSectionSize">
-                   <number>20</number>
-                 </attribute>
-                 <attribute name="horizontalHeaderStretchLastSection">
-                   <bool>true</bool>
-                 </attribute>
-                 <attribute name="verticalHeaderDefaultSectionSize">
-                   <number>20</number>
-                 </attribute>
-               </widget>
-             </item>
-             <item>
-               <layout class="QGridLayout" name="processLayout">
-               </layout>
-             </item>
-             <item>
-               <layout class="QHBoxLayout" name="layoutRowButtons">
-                 <property name="margin">
-                   <number>1</number>
-                 </property>
-                 <item>
-                   <widget class="QProgressBar" name="progressBar">
-                     <property name="whatsThis">
-                       <string>Shows the current progress when processing.</string>
-                     </property>
-                     <property name="value">
-                       <number>0</number>
-                     </property>
-                   </widget>
-                 </item>
-                 <item>
-                   <widget class="QLabel" name="labelProcessInstrument">
-                     <property name="text">
-                       <string>Instrument:</string>
-                     </property>
-                   </widget>
-                 </item>
-                 <item>
-                   <widget class="QComboBox" name="comboProcessInstrument">
-                     <property name="sizePolicy">
-                       <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
-                         <horstretch>0</horstretch>
-                         <verstretch>0</verstretch>
-                       </sizepolicy>
-                     </property>
-                     <property name="toolTip">
-                       <string>Select the instrument to assume for fetching runs</string>
-                     </property>
-                     <property name="whatsThis">
-                       <string>Specifies the instrument that data being processed was generated by. This is used to help identify the correct data to load when given sample run numbers to process.</string>
-                     </property>
-                   </widget>
-                 </item>
-                 <item>
-                   <widget class="QCheckBox" name="checkEnableNotebook">
-                     <property name="text">
-                       <string>Output Notebook</string>
-                     </property>
-                   </widget>
-                 </item>
-                 <item>
-                   <widget class="QToolButton" name="buttonProcess">
-                     <property name="text">
-                       <string>Process</string>
-                     </property>
-                     <property name="icon">
-                       <iconset resource="../../../../MantidPlot/icons/icons.qrc">
-                         <normaloff>:/stat_rows.png</normaloff>:/stat_rows.png
-                       </iconset>
-                     </property>
-                     <property name="toolButtonStyle">
-                       <enum>Qt::ToolButtonTextBesideIcon</enum>
-                     </property>
-                     <property name="arrowType">
-                       <enum>Qt::NoArrow</enum>
-                     </property>
-                   </widget>
-                 </item>
-               </layout>
-             </item>
-           </layout>
-         </item>
+        </widget>
+       </item>
+       <item>
+        <widget class="QToolButton" name="buttonProcess">
+         <property name="toolTip">
+          <string>Begin reducing the data in the table</string>
+         </property>
+         <property name="text">
+          <string>Process</string>
+         </property>
+         <property name="icon">
+          <iconset>
+           <normaloff>:/stat_rows.png</normaloff>:/stat_rows.png</iconset>
+         </property>
+         <property name="toolButtonStyle">
+          <enum>Qt::ToolButtonTextBesideIcon</enum>
+         </property>
+         <property name="arrowType">
+          <enum>Qt::NoArrow</enum>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
   </layout>
-  <widget class="QMenu" name="menuOpenTable">
-     <property name="title">
-       <string>Open Table</string>
-     </property>
-     <property name="icon">
-       <iconset resource="../../../../MantidPlot/icons/icons.qrc">
-         <normaloff>:/multiload.png</normaloff>:/multiload.png
-       </iconset>
-     </property>
-   </widget>
  </widget>
  <tabstops>
   <tabstop>viewTable</tabstop>
@@ -155,4 +158,5 @@
  <resources>
   <include location="../../../../MantidPlot/icons/icons.qrc"/>
  </resources>
+ <connections/>
 </ui>
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/GenericDataProcessorPresenter.h b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/GenericDataProcessorPresenter.h
index 966b9b3fb866d0ed50ce07cb4c64bd11ad03b0e0..3719feffeecb0adc0ff53d084f561e82ed5654ca 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/GenericDataProcessorPresenter.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/GenericDataProcessorPresenter.h
@@ -108,31 +108,34 @@ public:
       WhiteList whitelist,
       std::map<QString, PreprocessingAlgorithm> preprocessMap,
       ProcessingAlgorithm processor, PostprocessingAlgorithm postprocessor,
+      int group,
       std::map<QString, QString> postprocessMap = std::map<QString, QString>(),
       QString loader = "Load");
   // Constructor: no pre-processing, post-processing
   GenericDataProcessorPresenter(WhiteList whitelist,
                                 ProcessingAlgorithm processor,
-                                PostprocessingAlgorithm postprocessor);
+                                PostprocessingAlgorithm postprocessor,
+                                int group);
   // Constructor: pre-processing, no post-processing
   GenericDataProcessorPresenter(
       WhiteList whitelist,
       std::map<QString, PreprocessingAlgorithm> preprocessMap,
-      ProcessingAlgorithm processor);
+      ProcessingAlgorithm processor, int group);
   // Constructor: no pre-processing, no post-processing
   GenericDataProcessorPresenter(WhiteList whitelist,
-                                ProcessingAlgorithm processor);
+                                ProcessingAlgorithm processor, int group);
   // Constructor: only whitelist
-  GenericDataProcessorPresenter(WhiteList whitelist);
+  GenericDataProcessorPresenter(WhiteList whitelist, int group);
   // Delegating constructor: pre-processing, no post-processing
   GenericDataProcessorPresenter(WhiteList whitelist,
                                 PreprocessMap preprocessMap,
-                                ProcessingAlgorithm processor);
+                                ProcessingAlgorithm processor, int group);
   // Delegating Constructor: pre-processing and post-processing
   GenericDataProcessorPresenter(WhiteList whitelist,
                                 PreprocessMap preprocessMap,
                                 ProcessingAlgorithm processor,
-                                PostprocessingAlgorithm postprocessor);
+                                PostprocessingAlgorithm postprocessor,
+                                int group);
   virtual ~GenericDataProcessorPresenter() override;
   void notify(DataProcessorPresenter::Flag flag) override;
   const std::map<QString, QVariant> &options() const override;
@@ -217,6 +220,8 @@ protected:
   // Get the name of a post-processed workspace
   QString getPostprocessedWorkspaceName(const GroupData &groupData);
   bool rowOutputExists(RowItem const &row) const;
+  // Refl GUI Group.
+  int m_group;
 protected slots:
   void reductionError(QString ex);
   void threadFinished(const int exitCode);
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/GridDelegate.h b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/GridDelegate.h
new file mode 100644
index 0000000000000000000000000000000000000000..8f8cad70c3ccb600c969a3955aa1b3b7164e36e8
--- /dev/null
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/GridDelegate.h
@@ -0,0 +1,26 @@
+#include <QStyledItemDelegate>
+#include <QPainter>
+
+namespace MantidQt {
+namespace MantidWidgets {
+namespace DataProcessor {
+
+class GridDelegate : public QStyledItemDelegate {
+public:
+  explicit GridDelegate(QObject *parent = 0) : QStyledItemDelegate(parent){};
+
+  void paint(QPainter *painter, const QStyleOptionViewItem &option,
+             const QModelIndex &index) const override {
+
+    QStyledItemDelegate::paint(painter, option, index);
+
+    painter->save();
+    painter->setPen(QColor(Qt::black));
+    painter->drawRect(option.rect);
+    painter->restore();
+  }
+};
+
+} // namespace DataProcessor
+} // namespace MantidWidgets
+} // namespace Mantid
\ No newline at end of file
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/MockObjects.h b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/MockObjects.h
index 47f47f016cfc44c38a2746632291e1062c1d2451..4d295aa3866e627e466ef41935c83df38dda575c 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/MockObjects.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/MockObjects.h
@@ -72,6 +72,10 @@ public:
   MOCK_METHOD1(setForcedReProcessing, void(bool));
   MOCK_METHOD0(skipProcessing, void());
 
+  // Grouping options
+  MOCK_METHOD0(enableGrouping, void());
+  MOCK_METHOD0(disableGrouping, void());
+
   // Accessor
   MOCK_CONST_METHOD0(getCurrentInstrument, QString());
 
@@ -124,8 +128,8 @@ public:
   MOCK_CONST_METHOD0(resume, void());
 
   // Calls we don't care about
-  void confirmReductionPaused() const override{};
-  void confirmReductionResumed() const override{};
+  MOCK_METHOD1(confirmReductionPaused, void(int));
+  MOCK_METHOD1(confirmReductionResumed, void(int));
 };
 
 class MockDataProcessorPresenter : public DataProcessorPresenter {
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/OneLevelTreeManager.h b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/OneLevelTreeManager.h
index fb4d96201d4b34047175693e5370fcef416c9299..d9d34d0adf2bcdf11266a07a290351ddf137e7ee 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/OneLevelTreeManager.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/OneLevelTreeManager.h
@@ -51,6 +51,7 @@ public:
   /// Destructor
   ~OneLevelTreeManager() override;
 
+  bool isMultiLevel() const override;
   /// Publish commands
   std::vector<std::unique_ptr<Command>> publishCommands() override;
   /// Append a row
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/QDataProcessorWidget.h b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/QDataProcessorWidget.h
index 77dce512e071551eddbdc2732e580af404429ff4..7ed01a959e32db1280e564609ddbf44a94b01ef3 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/QDataProcessorWidget.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/QDataProcessorWidget.h
@@ -9,6 +9,7 @@
 #include "MantidQtWidgets/Common/DllOption.h"
 #include "ui_DataProcessorWidget.h"
 #include <QSignalMapper>
+#include "MantidQtWidgets/Common/HintStrategy.h"
 
 namespace MantidQt {
 namespace MantidWidgets {
@@ -54,16 +55,19 @@ class EXPORT_OPT_MANTIDQT_COMMON QDataProcessorWidget
 public:
   QDataProcessorWidget(std::unique_ptr<DataProcessorPresenter> presenter,
                        QWidget *parent = nullptr);
-  QDataProcessorWidget(const WhiteList &, QWidget *parent);
+  QDataProcessorWidget(const WhiteList &, QWidget *parent, int group = 0);
   QDataProcessorWidget(const WhiteList &, const ProcessingAlgorithm &,
-                       QWidget *parent);
+                       QWidget *parent, int group = 0);
   QDataProcessorWidget(const WhiteList &, const PreprocessMap &,
-                       const ProcessingAlgorithm &, QWidget *parent);
+                       const ProcessingAlgorithm &, QWidget *parent,
+                       int group = 0);
   QDataProcessorWidget(const WhiteList &, const ProcessingAlgorithm &,
-                       const PostprocessingAlgorithm &, QWidget *parent);
+                       const PostprocessingAlgorithm &, QWidget *parent,
+                       int group = 0);
   QDataProcessorWidget(const WhiteList &, const PreprocessMap &,
                        const ProcessingAlgorithm &,
-                       const PostprocessingAlgorithm &, QWidget *parent);
+                       const PostprocessingAlgorithm &, QWidget *parent,
+                       int group = 0);
   ~QDataProcessorWidget() override;
 
   // Add actions to the toolbar
@@ -151,6 +155,10 @@ public:
   void emitProcessingFinished() override { emit processingFinished(); }
 
   void skipProcessing() override;
+
+  void enableGrouping() override;
+  void disableGrouping() override;
+
   void settingsChanged();
 signals:
   void processButtonClicked();
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/TreeManager.h b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/TreeManager.h
index 93a4313a9301897d469c46e39b7a4b2c8a2b60cd..79579c2eef79d98d71d86e8e0312ec8dc3a4061d 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/TreeManager.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/TreeManager.h
@@ -4,6 +4,7 @@
 #include "MantidAPI/ITableWorkspace_fwd.h"
 #include "MantidAPI/Workspace_fwd.h"
 #include "MantidQtWidgets/Common/DataProcessorUI/AbstractTreeModel.h"
+#include "MantidQtWidgets/Common/DataProcessorUI/Command.h"
 #include "MantidQtWidgets/Common/DataProcessorUI/TreeData.h"
 #include <map>
 #include <memory>
@@ -15,7 +16,6 @@ namespace MantidQt {
 namespace MantidWidgets {
 namespace DataProcessor {
 
-class Command;
 class WhiteList;
 
 /** @class TreeManager
@@ -52,7 +52,7 @@ public:
   virtual ~TreeManager(){};
 
   /// Actions/commands
-
+  virtual bool isMultiLevel() const = 0;
   /// Publish actions/commands
   virtual std::vector<std::unique_ptr<Command>> publishCommands() = 0;
   /// Append a row
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/TwoLevelTreeManager.h b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/TwoLevelTreeManager.h
index 61648bc730b15258e927fa7276ff76a726e80744..4bb8a21c034bfa7f2043289702ee0039c43f7cab 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/TwoLevelTreeManager.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/DataProcessorUI/TwoLevelTreeManager.h
@@ -109,6 +109,8 @@ public:
   /// Return the table workspace
   Mantid::API::ITableWorkspace_sptr getTableWorkspace() override;
 
+  bool isMultiLevel() const override;
+
 private:
   /// The DataProcessor presenter
   DataProcessorPresenter *m_presenter;
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/HintingLineEdit.h b/qt/widgets/common/inc/MantidQtWidgets/Common/HintingLineEdit.h
index 41904b5de4d32db7f000e781c05272c20d17ef06..9f36d636a57b76d7375a3c5994780e198db33dc5 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/HintingLineEdit.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/HintingLineEdit.h
@@ -64,9 +64,6 @@ protected:
 protected slots:
   void updateHints(const QString &text);
   void hideHints();
-
-private:
-  static QPalette createFixedPalette();
 };
 } // namespace MantidWidgets
 } // namepsace MantidQt
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/HintingLineEditFactory.h b/qt/widgets/common/inc/MantidQtWidgets/Common/HintingLineEditFactory.h
index bfe7d1ac0576bca55e4d91db09a8f7345bcfac77..ca57f705af0027165b6063c3ed690d21675d8b8f 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/HintingLineEditFactory.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/HintingLineEditFactory.h
@@ -2,6 +2,7 @@
 #define MANTID_MANTIDWIDGETS_HINTINGLINEEDITFACTORY_H
 
 #include <QStyledItemDelegate>
+#include <QPainter>
 
 #include "MantidAPI/AlgorithmManager.h"
 #include "MantidQtWidgets/Common/HintingLineEdit.h"
@@ -38,6 +39,7 @@ class HintingLineEditFactory : public QStyledItemDelegate {
 public:
   HintingLineEditFactory(HintStrategy *hintStrategy)
       : m_strategy(hintStrategy){};
+
   QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                         const QModelIndex &index) const override {
     Q_UNUSED(option);
@@ -49,6 +51,16 @@ public:
     return editor;
   }
 
+  void paint(QPainter *painter, const QStyleOptionViewItem &option,
+             const QModelIndex &index) const override {
+    QStyledItemDelegate::paint(painter, option, index);
+
+    painter->save();
+    painter->setPen(QColor(Qt::black));
+    painter->drawRect(option.rect);
+    painter->restore();
+  }
+
 protected:
   boost::scoped_ptr<HintStrategy> m_strategy;
 };
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/QtPropertyBrowser/CMakeLists.txt b/qt/widgets/common/inc/MantidQtWidgets/Common/QtPropertyBrowser/CMakeLists.txt
index f85d832d13679b9d99258e93e1d52e6347252a44..dc988a282d6666dd20ec4b5f10554232c70837ad 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/QtPropertyBrowser/CMakeLists.txt
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/QtPropertyBrowser/CMakeLists.txt
@@ -45,7 +45,7 @@ qt4_generate_moc (
   ${CMAKE_CURRENT_BINARY_DIR}/moc_qtpropertymanager.cpp
 )
 qt4_generate_moc (
-  src/qteditorfactory.h 
+  src/qteditorfactory.h
   ${CMAKE_CURRENT_BINARY_DIR}/moc_qteditorfactory.cpp
 )
 qt4_generate_moc (
@@ -108,7 +108,7 @@ set (
 )
 
 qt4_wrap_cpp ( EXTRA_MOCS src/ButtonEditorFactory.h
-                          src/DoubleEditorFactory.h 
+                          src/DoubleEditorFactory.h
                           src/DoubleDialogEditor.h
                           src/FormulaDialogEditor.h
                           src/FilenameDialogEditor.h
@@ -138,7 +138,9 @@ set ( SRCS
 add_library ( ${PROJECT_NAME} SHARED ${SRCS} )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( ${PROJECT_NAME} PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+  set_target_properties(${PROJECT_NAME} PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(${PROJECT_NAME} PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 target_link_libraries ( ${PROJECT_NAME} LINK_PRIVATE ${TCMALLOC_LIBRARIES_LINKTIME} ${QT_LIBRARIES} )
diff --git a/qt/widgets/common/src/DataProcessorUI/GenericDataProcessorPresenter.cpp b/qt/widgets/common/src/DataProcessorUI/GenericDataProcessorPresenter.cpp
index 34ea7dc13e2f4a4da34aac30b3e696e5850fbbec..089d90e5a8cb518cecae117f8e25cc727779425c 100644
--- a/qt/widgets/common/src/DataProcessorUI/GenericDataProcessorPresenter.cpp
+++ b/qt/widgets/common/src/DataProcessorUI/GenericDataProcessorPresenter.cpp
@@ -76,6 +76,8 @@ namespace DataProcessor {
 * @param processor : A ProcessingAlgorithm
 * @param postprocessor : A PostprocessingAlgorithm
 * workspaces
+* @param group : The zero based index of this presenter within the tab
+* (reflectometry).
 * @param postprocessMap : A map containing instructions for post-processing.
 * This map links column name to properties of the post-processing algorithm
 * @param loader : The algorithm responsible for loading data
@@ -84,7 +86,7 @@ GenericDataProcessorPresenter::GenericDataProcessorPresenter(
     WhiteList whitelist,
     std::map<QString, PreprocessingAlgorithm> preprocessMap,
     ProcessingAlgorithm processor, PostprocessingAlgorithm postprocessor,
-    std::map<QString, QString> postprocessMap, QString loader)
+    int group, std::map<QString, QString> postprocessMap, QString loader)
     : WorkspaceObserver(), m_view(nullptr), m_progressView(nullptr),
       m_mainPresenter(), m_loader(std::move(loader)),
       m_postprocessing(postprocessor.name().isEmpty()
@@ -92,11 +94,12 @@ GenericDataProcessorPresenter::GenericDataProcessorPresenter(
                            : PostprocessingStep(QString(),
                                                 std::move(postprocessor),
                                                 std::move(postprocessMap))),
+
       m_preprocessing(ColumnOptionsMap(), std::move(preprocessMap)),
-      m_whitelist(std::move(whitelist)), m_processor(std::move(processor)),
-      m_progressReporter(nullptr), m_promptUser(true), m_tableDirty(false),
-      m_pauseReduction(false), m_reductionPaused(true),
-      m_nextActionFlag(ReductionFlag::StopReduceFlag) {
+      m_group(group), m_whitelist(std::move(whitelist)),
+      m_processor(std::move(processor)), m_progressReporter(nullptr),
+      m_promptUser(true), m_tableDirty(false), m_pauseReduction(false),
+      m_reductionPaused(true), m_nextActionFlag(ReductionFlag::StopReduceFlag) {
 
   // Column Options must be added to the whitelist
   m_whitelist.addElement("Options", "Options",
@@ -143,23 +146,27 @@ GenericDataProcessorPresenter::GenericDataProcessorPresenter(
 * @param processor : A ProcessingAlgorithm
 * @param postprocessor : A PostprocessingAlgorithm
 * workspaces
+* @param group : The zero based index of this presenter within the tab
+* (reflectometry).
 */
 GenericDataProcessorPresenter::GenericDataProcessorPresenter(
     WhiteList whitelist, ProcessingAlgorithm processor,
-    PostprocessingAlgorithm postprocessor)
+    PostprocessingAlgorithm postprocessor, int group)
     : GenericDataProcessorPresenter(
           std::move(whitelist), std::map<QString, PreprocessingAlgorithm>(),
-          std::move(processor), std::move(postprocessor)) {}
+          std::move(processor), std::move(postprocessor), group) {}
 
 /**
  * Delegating constructor (only whitelist specified)
  * @param whitelist : The set of properties we want to show as columns
+ * @param group : The zero based index of this presenter within the tab
+ * (reflectometry)
  */
 GenericDataProcessorPresenter::GenericDataProcessorPresenter(
-    WhiteList whitelist)
+    WhiteList whitelist, int group)
     : GenericDataProcessorPresenter(
           std::move(whitelist), std::map<QString, PreprocessingAlgorithm>(),
-          ProcessingAlgorithm(), PostprocessingAlgorithm()) {}
+          ProcessingAlgorithm(), PostprocessingAlgorithm(), group) {}
 
 /**
 * Delegating constructor (no post-processing needed)
@@ -167,26 +174,30 @@ GenericDataProcessorPresenter::GenericDataProcessorPresenter(
 * @param preprocessMap : A map containing instructions for pre-processing
 * @param processor : A ProcessingAlgorithm
 * workspaces
+* @param group : The zero based index of this presenter within the tab
+* (reflectometry)
 */
 GenericDataProcessorPresenter::GenericDataProcessorPresenter(
     WhiteList whitelist,
     std::map<QString, PreprocessingAlgorithm> preprocessMap,
-    ProcessingAlgorithm processor)
+    ProcessingAlgorithm processor, int group)
     : GenericDataProcessorPresenter(
           std::move(whitelist), std::move(preprocessMap), std::move(processor),
-          PostprocessingAlgorithm()) {}
+          PostprocessingAlgorithm(), group) {}
 
 /**
 * Delegating constructor (no pre-processing needed, no post-processing needed)
 * @param whitelist : The set of properties we want to show as columns
 * @param processor : A ProcessingAlgorithm
 * workspaces
+* @param group : The zero based index of this presenter within the tab
+* (reflectometry)
 */
 GenericDataProcessorPresenter::GenericDataProcessorPresenter(
-    WhiteList whitelist, ProcessingAlgorithm processor)
+    WhiteList whitelist, ProcessingAlgorithm processor, int group)
     : GenericDataProcessorPresenter(
           std::move(whitelist), std::map<QString, PreprocessingAlgorithm>(),
-          std::move(processor), PostprocessingAlgorithm()) {}
+          std::move(processor), PostprocessingAlgorithm(), group) {}
 
 /**
 * Destructor
@@ -217,6 +228,11 @@ void GenericDataProcessorPresenter::acceptViews(
   // Add actions to toolbar
   addCommands();
 
+  if (m_manager->isMultiLevel())
+    m_view->enableGrouping();
+  else
+    m_view->disableGrouping();
+
   // Initialise options
   // Load saved values from disk
   initOptions();
@@ -386,7 +402,7 @@ void GenericDataProcessorPresenter::nextRow() {
 
   if (m_pauseReduction) {
     // Notify presenter that reduction is paused
-    m_mainPresenter->confirmReductionPaused();
+    m_mainPresenter->confirmReductionPaused(m_group);
     m_reductionPaused = true;
     return;
   }
@@ -432,7 +448,7 @@ void GenericDataProcessorPresenter::nextGroup() {
 
   if (m_pauseReduction) {
     // Notify presenter that reduction is paused
-    m_mainPresenter->confirmReductionPaused();
+    m_mainPresenter->confirmReductionPaused(m_group);
     m_reductionPaused = true;
     return;
   }
@@ -491,7 +507,7 @@ void GenericDataProcessorPresenter::endReduction() {
 
   pause();
   m_reductionPaused = true;
-  m_mainPresenter->confirmReductionPaused();
+  m_mainPresenter->confirmReductionPaused(m_group);
 }
 
 /**
@@ -1455,7 +1471,7 @@ void GenericDataProcessorPresenter::resume() {
 
   m_pauseReduction = false;
   m_reductionPaused = false;
-  m_mainPresenter->confirmReductionResumed();
+  m_mainPresenter->confirmReductionResumed(m_group);
 
   doNextAction();
 }
diff --git a/qt/widgets/common/src/DataProcessorUI/OneLevelTreeManager.cpp b/qt/widgets/common/src/DataProcessorUI/OneLevelTreeManager.cpp
index 7295b79bb35921e6a0575755a37cd7fe3460cea5..49ea8fd8f73a7a6188618df4148c0aa431503e25 100644
--- a/qt/widgets/common/src/DataProcessorUI/OneLevelTreeManager.cpp
+++ b/qt/widgets/common/src/DataProcessorUI/OneLevelTreeManager.cpp
@@ -60,6 +60,8 @@ OneLevelTreeManager::OneLevelTreeManager(DataProcessorPresenter *presenter,
 */
 OneLevelTreeManager::~OneLevelTreeManager() {}
 
+bool OneLevelTreeManager::isMultiLevel() const { return false; }
+
 /**
 * Publishes a list of available commands
 * @return : The list of available commands
diff --git a/qt/widgets/common/src/DataProcessorUI/QDataProcessorWidget.cpp b/qt/widgets/common/src/DataProcessorUI/QDataProcessorWidget.cpp
index dac1125300c869882cf3004ab2b4e22352b2ee3e..6d6e0e459ac058a554109708c317e8a28353f6f6 100644
--- a/qt/widgets/common/src/DataProcessorUI/QDataProcessorWidget.cpp
+++ b/qt/widgets/common/src/DataProcessorUI/QDataProcessorWidget.cpp
@@ -3,6 +3,7 @@
 #include "MantidQtWidgets/Common/DataProcessorUI/QtCommandAdapter.h"
 #include "MantidQtWidgets/Common/DataProcessorUI/DataProcessorMainPresenter.h"
 #include "MantidQtWidgets/Common/DataProcessorUI/GenericDataProcessorPresenter.h"
+#include "MantidQtWidgets/Common/DataProcessorUI/GridDelegate.h"
 #include "MantidQtWidgets/Common/HintingLineEditFactory.h"
 
 #include <QClipboard>
@@ -40,24 +41,29 @@ QDataProcessorWidget::QDataProcessorWidget(
 /** Delegating constructor
  * @param whitelist :: [input] The white list
  * @param parent :: [input] The parent of this view
+ * @param group :: [input] The zero based index of this widget within the parent
+ * presenter (reflectometry).
  */
 QDataProcessorWidget::QDataProcessorWidget(const WhiteList &whitelist,
-                                           QWidget *parent)
+                                           QWidget *parent, int group)
     : QDataProcessorWidget(
-          Mantid::Kernel::make_unique<GenericDataProcessorPresenter>(whitelist),
+          Mantid::Kernel::make_unique<GenericDataProcessorPresenter>(whitelist,
+                                                                     group),
           parent) {}
 
 /** Delegating constructor
 * @param whitelist :: [input] The white list
 * @param algorithm :: [input] The processing algorithm
 * @param parent :: [input] The parent of this view
+* @param group :: [input] The zero based index of this widget within the parent
+* presenter (reflectometry).
 */
 QDataProcessorWidget::QDataProcessorWidget(const WhiteList &whitelist,
                                            const ProcessingAlgorithm &algorithm,
-                                           QWidget *parent)
+                                           QWidget *parent, int group)
     : QDataProcessorWidget(
-          Mantid::Kernel::make_unique<GenericDataProcessorPresenter>(whitelist,
-                                                                     algorithm),
+          Mantid::Kernel::make_unique<GenericDataProcessorPresenter>(
+              whitelist, algorithm, group),
           parent) {}
 
 /** Delegating constructor: pre-processing, no post-processing
@@ -65,14 +71,16 @@ QDataProcessorWidget::QDataProcessorWidget(const WhiteList &whitelist,
 * @param preprocessMap :: [input] Pre-processing instructions as a map
 * @param algorithm :: [input] The processing algorithm
 * @param parent :: [input] The parent of this view
+* @param group :: [input] The zero based index of this widget within the parent
+* presenter (reflectometry).
 */
 QDataProcessorWidget::QDataProcessorWidget(const WhiteList &whitelist,
                                            const PreprocessMap &preprocessMap,
                                            const ProcessingAlgorithm &algorithm,
-                                           QWidget *parent)
+                                           QWidget *parent, int group)
     : QDataProcessorWidget(
           Mantid::Kernel::make_unique<GenericDataProcessorPresenter>(
-              whitelist, preprocessMap.asMap(), algorithm),
+              whitelist, preprocessMap.asMap(), algorithm, group),
           parent) {}
 
 /** Delegating constructor: no pre-processing, post-processing
@@ -80,13 +88,15 @@ QDataProcessorWidget::QDataProcessorWidget(const WhiteList &whitelist,
 * @param algorithm :: [input] The processing algorithm
 * @param postprocessor :: [input] The post-processing algorithm
 * @param parent :: [input] The parent of this view
+* @param group :: [input] The zero based index of this widget within the parent
+* presenter (reflectometry).
 */
 QDataProcessorWidget::QDataProcessorWidget(
     const WhiteList &whitelist, const ProcessingAlgorithm &algorithm,
-    const PostprocessingAlgorithm &postprocessor, QWidget *parent)
+    const PostprocessingAlgorithm &postprocessor, QWidget *parent, int group)
     : QDataProcessorWidget(
           Mantid::Kernel::make_unique<GenericDataProcessorPresenter>(
-              whitelist, algorithm, postprocessor),
+              whitelist, algorithm, postprocessor, group),
           parent) {}
 
 /** Delegating constructor: pre-processing, post-processing
@@ -95,14 +105,17 @@ QDataProcessorWidget::QDataProcessorWidget(
 * @param algorithm :: [input] The processing algorithm
 * @param postprocessor :: [input] The post-processing algorithm
 * @param parent :: [input] The parent of this view
+* @param group :: [input] The zero based index of this widget within the parent
+* presenter (reflectometry).
 */
 QDataProcessorWidget::QDataProcessorWidget(
     const WhiteList &whitelist, const PreprocessMap &preprocessMap,
     const ProcessingAlgorithm &algorithm,
-    const PostprocessingAlgorithm &postprocessor, QWidget *parent)
+    const PostprocessingAlgorithm &postprocessor, QWidget *parent, int group)
     : QDataProcessorWidget(
           Mantid::Kernel::make_unique<GenericDataProcessorPresenter>(
-              whitelist, preprocessMap.asMap(), algorithm, postprocessor),
+              whitelist, preprocessMap.asMap(), algorithm, postprocessor,
+              group),
           parent) {}
 
 /** Destructor
@@ -118,6 +131,7 @@ void QDataProcessorWidget::createTable() {
   // Allow rows and columns to be reordered
   QHeaderView *header = new QHeaderView(Qt::Horizontal);
   header->setStretchLastSection(true);
+  header->setStyleSheet("QHeaderView {font-size:11pt;}");
 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
   header->setMovable(true);
   header->setResizeMode(QHeaderView::ResizeToContents);
@@ -199,6 +213,9 @@ void QDataProcessorWidget::showTable(
   connect(m_model.get(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
           this, SLOT(rowsUpdated(const QModelIndex &, int, int)));
   ui.viewTable->setModel(m_model.get());
+  ui.viewTable->setStyleSheet("QTreeView {font-size:11pt;}");
+  ui.viewTable->setAlternatingRowColors(false);
+  ui.viewTable->setItemDelegate(new GridDelegate(ui.viewTable));
 
   // Hide the Hidden Options column
   ui.viewTable->hideColumn(m_model->columnCount() - 1);
@@ -447,8 +464,8 @@ column.
 @param hintStrategy : The hinting strategy to use
 @param column : The index of the 'Options' column
 */
-void QDataProcessorWidget::setOptionsHintStrategy(HintStrategy *hintStrategy,
-                                                  int column) {
+void QDataProcessorWidget::setOptionsHintStrategy(
+    MantidQt::MantidWidgets::HintStrategy *hintStrategy, int column) {
   ui.viewTable->setItemDelegateForColumn(
       column, new HintingLineEditFactory(hintStrategy));
 }
@@ -694,6 +711,13 @@ QString QDataProcessorWidget::getCurrentInstrument() const {
 }
 
 void QDataProcessorWidget::skipProcessing() { m_presenter->skipProcessing(); }
+
+void QDataProcessorWidget::enableGrouping() {
+  ui.viewTable->setRootIsDecorated(true);
+}
+void QDataProcessorWidget::disableGrouping() {
+  ui.viewTable->setRootIsDecorated(false);
+}
 } // namespace DataProcessor
 } // namespace MantidWidgets
 } // namespace Mantid
diff --git a/qt/widgets/common/src/DataProcessorUI/TwoLevelTreeManager.cpp b/qt/widgets/common/src/DataProcessorUI/TwoLevelTreeManager.cpp
index be33c9aa83074503c68140cb5b2d7bd941958416..9ff3e0fac561ce89d7db2880a48a8d77751dd588 100644
--- a/qt/widgets/common/src/DataProcessorUI/TwoLevelTreeManager.cpp
+++ b/qt/widgets/common/src/DataProcessorUI/TwoLevelTreeManager.cpp
@@ -69,6 +69,7 @@ TwoLevelTreeManager::TwoLevelTreeManager(DataProcessorPresenter *presenter,
 */
 TwoLevelTreeManager::~TwoLevelTreeManager() {}
 
+bool TwoLevelTreeManager::isMultiLevel() const { return true; }
 /**
 * Publishes a list of available commands
 * @return : The list of available commands
diff --git a/qt/widgets/common/src/HintingLineEdit.cpp b/qt/widgets/common/src/HintingLineEdit.cpp
index ada5ffda61f3bb72bac710f01581a3e5a3a36198..abb0906f6855af538d10666ae00b7a0d46973841 100644
--- a/qt/widgets/common/src/HintingLineEdit.cpp
+++ b/qt/widgets/common/src/HintingLineEdit.cpp
@@ -21,7 +21,6 @@ HintingLineEdit::HintingLineEdit(
   m_hintLabel->setWordWrap(true);
   m_hintLabel->setIndent(1);
   m_hintLabel->setAutoFillBackground(true);
-  m_hintLabel->setPalette(createFixedPalette());
   m_hintLabel->setForegroundRole(QPalette::ToolTipText);
   m_hintLabel->setBackgroundRole(QPalette::ToolTipBase);
   m_hintLabel->ensurePolished();
@@ -31,29 +30,6 @@ HintingLineEdit::HintingLineEdit(
   connect(this, SIGNAL(editingFinished()), this, SLOT(hideHints()));
 }
 
-/** Fixes the colour pallete so that the hints are readable.
-
-    Tooltips use the inactive text colours since they are never 'in focus'.
-    This causes problems on some linux distributions (specifically Fedora 26
-    and Ubuntu 16.04) where the inactive text colours are particularly light
-    and lack contrast with the colour used for the background.
-
-    This method creates a modified tooltip pallete which uses the active
-    window text colour instead.
-
-    @returns A tooltip pallete with contrasting tooltip text and background
-    colours.
-*/
-QPalette HintingLineEdit::createFixedPalette() {
-  auto toolTipPalette = QToolTip::palette();
-
-  auto const activeTextColour =
-      toolTipPalette.color(QPalette::ColorGroup::Active, QPalette::WindowText);
-  toolTipPalette.setColor(QPalette::ColorGroup::Inactive, QPalette::ToolTipText,
-                          activeTextColour);
-  return toolTipPalette;
-}
-
 HintingLineEdit::~HintingLineEdit() {}
 
 /** Handle a key press event.
diff --git a/qt/widgets/common/test/DataProcessorUI/GenericDataProcessorPresenterTest.h b/qt/widgets/common/test/DataProcessorUI/GenericDataProcessorPresenterTest.h
index ec60f19526825ddea9260923cfeddbe3268a8084..8afea98ff5d3a350e403b9537753dd4cae0c93a4 100644
--- a/qt/widgets/common/test/DataProcessorUI/GenericDataProcessorPresenterTest.h
+++ b/qt/widgets/common/test/DataProcessorUI/GenericDataProcessorPresenterTest.h
@@ -23,6 +23,7 @@ using namespace testing;
 //=====================================================================================
 // Functional tests
 //=====================================================================================
+auto const DEFAULT_GROUP_NUMBER = 0;
 
 // Use this if you need the Test class to be a friend of the data processor
 // presenter
@@ -36,24 +37,26 @@ public:
       const WhiteList &whitelist,
       const std::map<QString, PreprocessingAlgorithm> &preprocessingStep,
       const ProcessingAlgorithm &processor,
-      const PostprocessingAlgorithm &postprocessor,
+      const PostprocessingAlgorithm &postprocessor, int group,
       const std::map<QString, QString> &postprocessMap =
           std::map<QString, QString>(),
       const QString &loader = "Load")
       : GenericDataProcessorPresenter(whitelist, std::move(preprocessingStep),
-                                      processor, postprocessor, postprocessMap,
-                                      loader) {}
+                                      processor, postprocessor, group,
+                                      postprocessMap, loader) {}
 
   // Delegating constructor (no pre-processing required)
   GenericDataProcessorPresenterFriend(
       const WhiteList &whitelist, const ProcessingAlgorithm &processor,
-      const PostprocessingAlgorithm &postprocessor)
-      : GenericDataProcessorPresenter(whitelist, processor, postprocessor) {}
+      const PostprocessingAlgorithm &postprocessor, int group)
+      : GenericDataProcessorPresenter(whitelist, processor, postprocessor,
+                                      group) {}
 
   // Delegating constructor (no pre- or post-processing required)
   GenericDataProcessorPresenterFriend(const WhiteList &whitelist,
-                                      const ProcessingAlgorithm &processor)
-      : GenericDataProcessorPresenter(whitelist, processor) {}
+                                      const ProcessingAlgorithm &processor,
+                                      int group)
+      : GenericDataProcessorPresenter(whitelist, processor, group) {}
 
   // Destructor
   ~GenericDataProcessorPresenterFriend() override {}
@@ -70,19 +73,20 @@ public:
       const WhiteList &whitelist,
       const std::map<QString, PreprocessingAlgorithm> &preprocessingStep,
       const ProcessingAlgorithm &processor,
-      const PostprocessingAlgorithm &postprocessor,
+      const PostprocessingAlgorithm &postprocessor, int group,
       const std::map<QString, QString> &postprocessMap =
           std::map<QString, QString>(),
       const QString &loader = "Load")
       : GenericDataProcessorPresenter(whitelist, std::move(preprocessingStep),
-                                      processor, postprocessor, postprocessMap,
-                                      loader) {}
+                                      processor, postprocessor, group,
+                                      postprocessMap, loader) {}
 
   // Delegating constructor (no pre-processing required)
   GenericDataProcessorPresenterNoThread(
       const WhiteList &whitelist, const ProcessingAlgorithm &processor,
-      const PostprocessingAlgorithm &postprocessor)
-      : GenericDataProcessorPresenter(whitelist, processor, postprocessor) {}
+      const PostprocessingAlgorithm &postprocessor, int group)
+      : GenericDataProcessorPresenter(whitelist, processor, postprocessor,
+                                      group) {}
 
   // Destructor
   ~GenericDataProcessorPresenterNoThread() override {}
@@ -403,14 +407,16 @@ private:
   std::unique_ptr<GenericDataProcessorPresenterFriend> makeDefaultPresenter() {
     return Mantid::Kernel::make_unique<GenericDataProcessorPresenterFriend>(
         createReflectometryWhiteList(), createReflectometryPreprocessingStep(),
-        createReflectometryProcessor(), createReflectometryPostprocessor());
+        createReflectometryProcessor(), createReflectometryPostprocessor(),
+        DEFAULT_GROUP_NUMBER);
   }
 
   std::unique_ptr<GenericDataProcessorPresenterNoThread>
   makeDefaultPresenterNoThread() {
     return Mantid::Kernel::make_unique<GenericDataProcessorPresenterNoThread>(
         createReflectometryWhiteList(), createReflectometryPreprocessingStep(),
-        createReflectometryProcessor(), createReflectometryPostprocessor());
+        createReflectometryProcessor(), createReflectometryPostprocessor(),
+        DEFAULT_GROUP_NUMBER);
   }
 
   // Expect the view's widgets to be set in a particular state according to
@@ -643,6 +649,37 @@ public:
     EXPECT_CALL(mockDataProcessorView, loadSettings(_)).Times(Exactly(1));
     // Expect that the layout containing pre-processing, processing and
     // post-processing options is created
+    EXPECT_CALL(mockDataProcessorView, enableGrouping()).Times(Exactly(1));
+    std::vector<QString> stages = {"Pre-process", "Pre-process", "Process",
+                                   "Post-process"};
+    std::vector<QString> algorithms = {
+        "Plus", "CreateTransmissionWorkspaceAuto",
+        "ReflectometryReductionOneAuto", "Stitch1DMany"};
+
+    // Expect that the autocompletion hints are populated
+    EXPECT_CALL(mockDataProcessorView, setOptionsHintStrategy(_, 7))
+        .Times(Exactly(1));
+    // Now accept the views
+    presenter->acceptViews(&mockDataProcessorView, &mockProgress);
+
+    // Verify expectations
+    TS_ASSERT(Mock::VerifyAndClearExpectations(&mockDataProcessorView));
+  }
+
+  void testNonPostProcessPresenterAcceptsViews() {
+    NiceMock<MockDataProcessorView> mockDataProcessorView;
+    MockProgressableView mockProgress;
+
+    auto presenter = makeNonPostProcessPresenter();
+
+    // When the presenter accepts the views, expect the following:
+    // Expect that the list of actions is published
+    EXPECT_CALL(mockDataProcessorView, addActionsProxy()).Times(Exactly(1));
+    // Expect that the list of settings is populated
+    EXPECT_CALL(mockDataProcessorView, loadSettings(_)).Times(Exactly(1));
+    // Expect that the layout containing pre-processing, processing and
+    // post-processing options is created
+    EXPECT_CALL(mockDataProcessorView, enableGrouping()).Times(Exactly(0));
     std::vector<QString> stages = {"Pre-process", "Pre-process", "Process",
                                    "Post-process"};
     std::vector<QString> algorithms = {
@@ -1165,6 +1202,16 @@ public:
     TS_ASSERT(Mock::VerifyAndClearExpectations(&mockDataProcessorView));
   }
 
+  void expectNotifiedReductionPaused(MockMainPresenter &mockMainPresenter) {
+    EXPECT_CALL(mockMainPresenter,
+                confirmReductionPaused(DEFAULT_GROUP_NUMBER));
+  }
+
+  void expectNotifiedReductionResumed(MockMainPresenter &mockMainPresenter) {
+    EXPECT_CALL(mockMainPresenter,
+                confirmReductionPaused(DEFAULT_GROUP_NUMBER));
+  }
+
   void testProcess() {
     NiceMock<MockDataProcessorView> mockDataProcessorView;
     NiceMock<MockProgressableView> mockProgress;
@@ -1190,6 +1237,7 @@ public:
     expectGetSelection(mockDataProcessorView, Exactly(1), RowList(), grouplist);
     expectUpdateViewToProcessingState(mockDataProcessorView, Exactly(1));
     expectNotebookIsDisabled(mockDataProcessorView, Exactly(1));
+    expectNotifiedReductionResumed(mockMainPresenter);
     presenter->notify(DataProcessorPresenter::ProcessFlag);
 
     // Check output and tidy up
@@ -1543,6 +1591,12 @@ public:
     TS_ASSERT(Mock::VerifyAndClearExpectations(&mockMainPresenter));
   }
 
+  std::unique_ptr<GenericDataProcessorPresenter> makeNonPostProcessPresenter() {
+    return Mantid::Kernel::make_unique<GenericDataProcessorPresenter>(
+        createReflectometryWhiteList(), createReflectometryPreprocessingStep(),
+        createReflectometryProcessor(), DEFAULT_GROUP_NUMBER);
+  }
+
   void testBadWorkspaceType() {
     ITableWorkspace_sptr ws = WorkspaceFactory::Instance().createTable();
 
@@ -2934,7 +2988,7 @@ public:
 
     GenericDataProcessorPresenterNoThread presenter(
         createReflectometryWhiteList(), createReflectometryProcessor(),
-        createReflectometryPostprocessor());
+        createReflectometryPostprocessor(), DEFAULT_GROUP_NUMBER);
 
     // Verify expectations
     TS_ASSERT(Mock::VerifyAndClearExpectations(&mockDataProcessorView));
@@ -3060,7 +3114,8 @@ public:
     NiceMock<MockDataProcessorView> mockDataProcessorView;
     MockProgressableView mockProgress;
     GenericDataProcessorPresenterFriend presenter(
-        createReflectometryWhiteList(), createReflectometryProcessor());
+        createReflectometryWhiteList(), createReflectometryProcessor(),
+        DEFAULT_GROUP_NUMBER);
     presenter.acceptViews(&mockDataProcessorView, &mockProgress);
 
     // Calls that should throw
@@ -3088,7 +3143,7 @@ public:
     GenericDataProcessorPresenterNoThread presenter(
         createReflectometryWhiteList(), createReflectometryPreprocessingStep(),
         createReflectometryProcessor(), createReflectometryPostprocessor(),
-        postprocesssMap);
+        DEFAULT_GROUP_NUMBER, postprocesssMap);
     presenter.acceptViews(&mockDataProcessorView, &mockProgress);
     presenter.accept(&mockMainPresenter);
 
@@ -3161,7 +3216,8 @@ public:
     NiceMock<MockDataProcessorView> mockDataProcessorView;
     MockProgressableView mockProgress;
     GenericDataProcessorPresenter presenter(createReflectometryWhiteList(),
-                                            createReflectometryProcessor());
+                                            createReflectometryProcessor(),
+                                            DEFAULT_GROUP_NUMBER);
     presenter.acceptViews(&mockDataProcessorView, &mockProgress);
 
     EXPECT_CALL(mockDataProcessorView,
@@ -3174,5 +3230,4 @@ public:
     TS_ASSERT(Mock::VerifyAndClearExpectations(&mockDataProcessorView));
   }
 };
-
 #endif /* MANTID_MANTIDWIDGETS_GENERICDATAPROCESSORPRESENTERTEST_H */
diff --git a/qt/widgets/instrumentview/CMakeLists.txt b/qt/widgets/instrumentview/CMakeLists.txt
index 80a0417f190fefb9e2c7a55d844f41a59e410a3e..04520f23ba7b3144942462938bbafc22889f97e5 100644
--- a/qt/widgets/instrumentview/CMakeLists.txt
+++ b/qt/widgets/instrumentview/CMakeLists.txt
@@ -153,5 +153,7 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsInstrumentView
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( MantidQtWidgetsInstrumentViewQt4 PROPERTIES INSTALL_RPATH "@loader_path/../MacOS;@loader_path/../Libraries")
+  set_target_properties(MantidQtWidgetsInstrumentViewQt4 PROPERTIES INSTALL_RPATH "@loader_path/../MacOS;@loader_path/../Libraries")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(MantidQtWidgetsInstrumentViewQt4 PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
diff --git a/qt/widgets/legacyqwt/CMakeLists.txt b/qt/widgets/legacyqwt/CMakeLists.txt
index c19b086755e1d545262ff38fa8783053ae670be8..ad33de02b3aa1c7794eeb637e0e626078f41b7f7 100644
--- a/qt/widgets/legacyqwt/CMakeLists.txt
+++ b/qt/widgets/legacyqwt/CMakeLists.txt
@@ -79,6 +79,8 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsLegacyQwt
     MantidQtWidgetsCommon
   OSX_INSTALL_RPATH
     @loader_path/../MacOS
+  LINUX_INSTALL_RPATH
+    "\$ORIGIN/../../${LIB_DIR}"
 )
 
 ###########################################################################
diff --git a/qt/widgets/plugins/algorithm_dialogs/CMakeLists.txt b/qt/widgets/plugins/algorithm_dialogs/CMakeLists.txt
index bd3d6b916abcdc6f8859c8a94b891d5f593d243f..7bd1de7ee9559d20fcffaf77f163de52ea85f1fc 100644
--- a/qt/widgets/plugins/algorithm_dialogs/CMakeLists.txt
+++ b/qt/widgets/plugins/algorithm_dialogs/CMakeLists.txt
@@ -75,6 +75,8 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsPluginsAlgorithmDialogs
     MantidQtWidgetsCommon
   OSX_INSTALL_RPATH
     @loader_path/../../Contents/MacOS
+  LINUX_INSTALL_RPATH
+    "\$ORIGIN/../../${LIB_DIR}"
   INSTALL_DIR_BASE
     ${PLUGINS_DIR}
 )
diff --git a/qt/widgets/refdetectorview/CMakeLists.txt b/qt/widgets/refdetectorview/CMakeLists.txt
index 23f6d45c89abe0c2fe10d969d50f13d3f1635000..e0f806beb037465e0332b81c78721d8cc9d769e8 100644
--- a/qt/widgets/refdetectorview/CMakeLists.txt
+++ b/qt/widgets/refdetectorview/CMakeLists.txt
@@ -53,7 +53,9 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsRefDetectorView
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( MantidQtWidgetsRefDetectorViewQt4 PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+  set_target_properties( MantidQtWidgetsRefDetectorViewQt4 PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties( MantidQtWidgetsRefDetectorViewQt4 PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
 
 ###########################################################################
diff --git a/qt/widgets/sliceviewer/CMakeLists.txt b/qt/widgets/sliceviewer/CMakeLists.txt
index 597f9066dab5d3cd0f27eddc7356c51fefa2657c..3d55564e41298d9f585f0b2c9020480e95f78865 100644
--- a/qt/widgets/sliceviewer/CMakeLists.txt
+++ b/qt/widgets/sliceviewer/CMakeLists.txt
@@ -139,6 +139,8 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsSliceViewer
     MantidQtWidgetsLegacyQwt
   OSX_INSTALL_RPATH
     loader_path/../MacOS
+  LINUX_INSTALL_RPATH
+    "\$ORIGIN/../../${LIB_DIR}"
 )
 
 ###########################################################################
diff --git a/qt/widgets/spectrumviewer/CMakeLists.txt b/qt/widgets/spectrumviewer/CMakeLists.txt
index 71a326f782df23c1b5b73f6021f5944977e6abb1..233088f1c7a150dfbf97d02d72335167420906f2 100644
--- a/qt/widgets/spectrumviewer/CMakeLists.txt
+++ b/qt/widgets/spectrumviewer/CMakeLists.txt
@@ -77,5 +77,7 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsSpectrumViewer
 )
 
 if (OSX_VERSION VERSION_GREATER 10.8)
-  set_target_properties ( MantidQtWidgetsSpectrumViewerQt4 PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+  set_target_properties(MantidQtWidgetsSpectrumViewerQt4 PROPERTIES INSTALL_RPATH "@loader_path/../MacOS")
+elseif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" )
+  set_target_properties(MantidQtWidgetsSpectrumViewerQt4 PROPERTIES INSTALL_RPATH "\$ORIGIN/../${LIB_DIR}")
 endif ()
diff --git a/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_advanced_config.py b/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_advanced_config.py
index b7e7107c1016bbc9e2f39a5e687209db428a3182..7df173d9c88aafb4bccdbbe7c53c29fb9ca2e8ed 100644
--- a/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_advanced_config.py
+++ b/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_advanced_config.py
@@ -39,11 +39,11 @@ window_30_130_params = {
 }
 
 window_100_200_params = {
-    "vanadium_tof_cropping": (1e5, 2.15e5),
+    "vanadium_tof_cropping": (1e5, 200500),
     "focused_cropping_values": [
-        (1e5, 2e5),      # Bank 1
-        (8.7e4, 2.1e5),  # Bank 2
-        (9.9e4, 2.1e5)   # Bank 3
+        (100200, 1.99e5),  # Bank 1
+        (100100, 1.99e5),  # Bank 2
+        (100100, 1.99e5)   # Bank 3
     ]
 }
 
diff --git a/scripts/Diffraction/isis_powder/pearl_routines/pearl_advanced_config.py b/scripts/Diffraction/isis_powder/pearl_routines/pearl_advanced_config.py
index 13bc2e7cb29a4ed059ab8a82a5eee92874d728f1..9e61b6e2038796ae2ce3b6a51966c7311d3f3f53 100644
--- a/scripts/Diffraction/isis_powder/pearl_routines/pearl_advanced_config.py
+++ b/scripts/Diffraction/isis_powder/pearl_routines/pearl_advanced_config.py
@@ -15,6 +15,8 @@ general_params = {
          "tt70_grouping_filename": "pearl_group_12_1_TT70.cal",
          "tt35_grouping_filename": "pearl_group_12_1_TT35.cal"
     },
+
+    "subtract_empty_instrument": True,
 }
 
 long_mode_off_params = {
diff --git a/scripts/Diffraction/isis_powder/pearl_routines/pearl_param_mapping.py b/scripts/Diffraction/isis_powder/pearl_routines/pearl_param_mapping.py
index d936dab31de65c4879b91debda7ec0a3e5097f89..e0527511a34885cd0bf49af69590cda8bbd62ac7 100644
--- a/scripts/Diffraction/isis_powder/pearl_routines/pearl_param_mapping.py
+++ b/scripts/Diffraction/isis_powder/pearl_routines/pearl_param_mapping.py
@@ -38,6 +38,7 @@ attr_mapping = \
         ParamMapEntry(ext_name="run_in_cycle",                    int_name="run_in_range"),
         ParamMapEntry(ext_name="run_number",                      int_name="run_number"),
         ParamMapEntry(ext_name="spline_coefficient",              int_name="spline_coefficient"),
+        ParamMapEntry(ext_name="subtract_empty_instrument",       int_name="subtract_empty_inst"),
         ParamMapEntry(ext_name="suffix",                          int_name="suffix", optional=True),
         ParamMapEntry(ext_name="tt88_grouping_filename",          int_name="tt88_grouping"),
         ParamMapEntry(ext_name="tt70_grouping_filename",          int_name="tt70_grouping"),
diff --git a/scripts/Diffraction/isis_powder/routines/common.py b/scripts/Diffraction/isis_powder/routines/common.py
index 1254d498d1e7d0f5299248adf2e2c4fb2993a27b..0ead7bc07e2cd2e84a66012e227a70fd0bfa4394 100644
--- a/scripts/Diffraction/isis_powder/routines/common.py
+++ b/scripts/Diffraction/isis_powder/routines/common.py
@@ -283,12 +283,27 @@ def load_current_normalised_ws_list(run_number_string, instrument, input_batchin
         remove_intermediate_workspace(raw_ws_list)
         raw_ws_list = [summed_ws]
 
+    if instrument._inst_prefix == "HRPD":
+        for ws in raw_ws_list:
+            _mask_all_prompt_pulses(ws)
+
     normalised_ws_list = _normalise_workspaces(ws_list=raw_ws_list, run_details=run_information,
                                                instrument=instrument)
 
     return normalised_ws_list
 
 
+def _mask_prompt_pulse(workspace, middle, left_crop, right_crop):
+    min_crop = middle - left_crop
+    max_crop = middle + right_crop
+    mantid.MaskBins(InputWorkspace=workspace, OutputWorkspace=workspace, XMin=min_crop, XMax=max_crop)
+
+
+def _mask_all_prompt_pulses(workspace):
+    for i in range(6):
+        _mask_prompt_pulse(workspace=workspace, middle=100000 + 20000 * i, left_crop=30, right_crop=140)
+
+
 def rebin_workspace(workspace, new_bin_width, start_x=None, end_x=None):
     """
     Rebins the specified workspace with the specified new bin width. Allows the user
@@ -372,6 +387,14 @@ def run_normalise_by_current(ws):
     return ws
 
 
+def runs_overlap(run_string1, run_string2):
+    """
+    Get whether two runs, specified using the usual run string format (eg 123-125 referring to 123, 124 and 125)
+    contain any individual runs in common
+    """
+    return len(set(generate_run_numbers(run_string1)).intersection(generate_run_numbers(run_string2))) > 0
+
+
 def spline_vanadium_workspaces(focused_vanadium_spectra, spline_coefficient):
     """
     Returns a splined vanadium workspace from the focused vanadium bank list.
diff --git a/scripts/Diffraction/isis_powder/routines/focus.py b/scripts/Diffraction/isis_powder/routines/focus.py
index e8b8421897fe33e62195ab3ebce3f6b8debf2ac2..83d463afa34e4cea98fd607b0c5317ff0b9a09e1 100644
--- a/scripts/Diffraction/isis_powder/routines/focus.py
+++ b/scripts/Diffraction/isis_powder/routines/focus.py
@@ -19,14 +19,18 @@ def focus(run_number_string, instrument, perform_vanadium_norm, absorb, sample_d
         raise ValueError("Input batching not passed through. Please contact development team.")
 
 
-def _focus_one_ws(ws, run_number, instrument, perform_vanadium_norm, absorb, sample_details):
+def _focus_one_ws(input_workspace, run_number, instrument, perform_vanadium_norm, absorb, sample_details):
     run_details = instrument._get_run_details(run_number_string=run_number)
     if perform_vanadium_norm:
         _test_splined_vanadium_exists(instrument, run_details)
 
-    # Subtract empty instrument runs
-    input_workspace = common.subtract_summed_runs(ws_to_correct=ws, instrument=instrument,
-                                                  empty_sample_ws_string=run_details.empty_runs)
+    # Subtract empty instrument runs, as long as this run isn't an empty and user hasn't turned empty subtraction off
+    if not common.runs_overlap(run_number, run_details.empty_runs) and \
+            (not hasattr(instrument._inst_settings, "subtract_empty_inst") or
+             instrument._inst_settings.subtract_empty_inst):
+        input_workspace = common.subtract_summed_runs(ws_to_correct=input_workspace, instrument=instrument,
+                                                      empty_sample_ws_string=run_details.empty_runs)
+
     # Subtract a sample empty if specified
     if run_details.sample_empty:
         input_workspace = common.subtract_summed_runs(ws_to_correct=input_workspace, instrument=instrument,
@@ -99,7 +103,7 @@ def _batched_run_focusing(instrument, perform_vanadium_norm, run_number_string,
                                                           instrument=instrument)
     output = None
     for ws in read_ws_list:
-        output = _focus_one_ws(ws=ws, run_number=run_number_string, instrument=instrument,
+        output = _focus_one_ws(input_workspace=ws, run_number=run_number_string, instrument=instrument,
                                perform_vanadium_norm=perform_vanadium_norm, absorb=absorb,
                                sample_details=sample_details)
     return output
@@ -139,7 +143,7 @@ def _individual_run_focusing(instrument, perform_vanadium_norm, run_number, abso
     output = None
     for run in run_numbers:
         ws = common.load_current_normalised_ws_list(run_number_string=run, instrument=instrument)
-        output = _focus_one_ws(ws=ws[0], run_number=run, instrument=instrument, absorb=absorb,
+        output = _focus_one_ws(input_workspace=ws[0], run_number=run, instrument=instrument, absorb=absorb,
                                perform_vanadium_norm=perform_vanadium_norm, sample_details=sample_details)
     return output
 
diff --git a/scripts/Diffraction/isis_powder/routines/run_details.py b/scripts/Diffraction/isis_powder/routines/run_details.py
index c8cdd7c99e844cb6e91c23643d7df692e24a713d..45079bbe066daa62d69a0e7f5a58f8e683a766ed 100644
--- a/scripts/Diffraction/isis_powder/routines/run_details.py
+++ b/scripts/Diffraction/isis_powder/routines/run_details.py
@@ -33,9 +33,9 @@ def create_run_details_object(run_number_string, inst_settings, is_vanadium_run,
     if splined_name_list:
         # Force Python to make a copy so we don't modify original
         new_splined_list = list(splined_name_list)
-        new_splined_list.append(offset_file_name)
+        new_splined_list.append(os.path.basename(offset_file_name))
     else:
-        new_splined_list = [offset_file_name]
+        new_splined_list = [os.path.basename(offset_file_name)]
 
     # These can either be generic or custom so defer to another method
     results_dict = _get_customisable_attributes(
@@ -67,8 +67,14 @@ def create_run_details_object(run_number_string, inst_settings, is_vanadium_run,
 
     # Generate the paths
     grouping_file_path = os.path.join(calibration_dir, results_dict["grouping_file_name"])
-    # Offset  and splined vanadium is within the correct label folder
-    offset_file_path = os.path.join(calibration_dir, label, offset_file_name)
+
+    # By default, offset file sits in correct label folder, but it can also be given as an absolute path
+    if os.path.exists(offset_file_name):
+        offset_file_path = offset_file_name
+    else:
+        offset_file_path = os.path.join(calibration_dir, label, offset_file_name)
+
+    # splined vanadium is within the correct label folder
     splined_van_path = os.path.join(calibration_dir, label, results_dict["splined_van_name"])
     unsplined_van_path = os.path.join(calibration_dir, label, results_dict["unsplined_van_name"])
     van_absorb_path = os.path.join(calibration_dir, van_abs_file_name) if van_abs_file_name else None
diff --git a/scripts/HFIR_4Circle_Reduction/MainWindow.ui b/scripts/HFIR_4Circle_Reduction/MainWindow.ui
index 55023b024559ee61a2876e43aa53699bb3712c4b..84fd360d6e881358e6f739ddca171a85c000870b 100644
--- a/scripts/HFIR_4Circle_Reduction/MainWindow.ui
+++ b/scripts/HFIR_4Circle_Reduction/MainWindow.ui
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>1568</width>
-    <height>1116</height>
+    <height>1147</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -911,7 +911,7 @@ p, li { white-space: pre-wrap; }
                    <widget class="QPushButton" name="pushButton_viewRawSpice">
                     <property name="font">
                      <font>
-                      <pointsize>12</pointsize>
+                      <pointsize>11</pointsize>
                      </font>
                     </property>
                     <property name="text">
@@ -923,7 +923,7 @@ p, li { white-space: pre-wrap; }
                    <widget class="QPushButton" name="pushButton_viewSurveyPeak">
                     <property name="font">
                      <font>
-                      <pointsize>12</pointsize>
+                      <pointsize>11</pointsize>
                      </font>
                     </property>
                     <property name="toolTip">
@@ -1737,73 +1737,95 @@ p, li { white-space: pre-wrap; }
                   </property>
                   <layout class="QGridLayout" name="gridLayout_18">
                    <item row="5" column="0">
-                    <widget class="QPushButton" name="pushButton_cancelROI">
+                    <layout class="QHBoxLayout" name="horizontalLayout_24">
+                     <item>
+                      <widget class="QCheckBox" name="checkBox_autoMask">
+                       <property name="font">
+                        <font>
+                         <pointsize>9</pointsize>
+                        </font>
+                       </property>
+                       <property name="text">
+                        <string>Auto Mask</string>
+                       </property>
+                      </widget>
+                     </item>
+                    </layout>
+                   </item>
+                   <item row="6" column="0">
+                    <widget class="QPushButton" name="pushButton_integrateROI">
                      <property name="font">
                       <font>
                        <pointsize>10</pointsize>
                       </font>
                      </property>
                      <property name="text">
-                      <string>Reset ROI</string>
+                      <string>Integrate</string>
                      </property>
                     </widget>
                    </item>
+                   <item row="4" column="0">
+                    <widget class="QComboBox" name="comboBox_viewRawDataMasks"/>
+                   </item>
                    <item row="0" column="0">
-                    <widget class="QPushButton" name="pushButton_addROI">
+                    <widget class="QPushButton" name="pushButton_switchROIMode">
                      <property name="font">
                       <font>
                        <pointsize>10</pointsize>
                       </font>
                      </property>
                      <property name="text">
-                      <string>Edit ROI</string>
+                      <string>Enter ROI-Edit Mode</string>
                      </property>
                     </widget>
                    </item>
-                   <item row="1" column="0">
-                    <widget class="QPushButton" name="pushButton_maskScanPt">
+                   <item row="2" column="0">
+                    <widget class="QPushButton" name="pushButton_removeROICanvas">
                      <property name="font">
                       <font>
                        <pointsize>10</pointsize>
                       </font>
                      </property>
+                     <property name="toolTip">
+                      <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt;&quot;&gt;Remove ROI from 2D detector and restore original image.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                     </property>
                      <property name="text">
-                      <string>Mask</string>
+                      <string>Remove In-Edit ROI</string>
                      </property>
                     </widget>
                    </item>
-                   <item row="2" column="0">
-                    <widget class="QPushButton" name="pushButton_saveMask">
+                   <item row="1" column="0">
+                    <widget class="QPushButton" name="pushButton_maskScanPt">
                      <property name="font">
                       <font>
                        <pointsize>10</pointsize>
                       </font>
                      </property>
-                     <property name="toolTip">
-                      <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop up a window to allow user to specify the name of the mask;&lt;/p&gt;&lt;p&gt;And it will be added to the combo box for available mask names in tab 'merge scan' and 'peak integration'&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-                     </property>
                      <property name="text">
-                      <string>Save</string>
+                      <string>Apply In-Edit ROI</string>
                      </property>
                     </widget>
                    </item>
-                   <item row="3" column="0">
-                    <widget class="QPushButton" name="pushButton_integrateROI">
+                   <item row="7" column="0">
+                    <widget class="QPushButton" name="pushButton_exportMaskToFile">
                      <property name="font">
                       <font>
                        <pointsize>10</pointsize>
                       </font>
                      </property>
                      <property name="text">
-                      <string>Integrate</string>
+                      <string>Export ROI</string>
                      </property>
                     </widget>
                    </item>
-                   <item row="4" column="0">
+                   <item row="3" column="0">
                     <spacer name="verticalSpacer_31">
                      <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>
@@ -2132,18 +2154,6 @@ p, li { white-space: pre-wrap; }
                   </property>
                  </widget>
                 </item>
-                <item>
-                 <widget class="QPushButton" name="pushButton_2">
-                  <property name="font">
-                   <font>
-                    <pointsize>10</pointsize>
-                   </font>
-                  </property>
-                  <property name="text">
-                   <string>Plot Scan 2D</string>
-                  </property>
-                 </widget>
-                </item>
                 <item>
                  <widget class="QPushButton" name="pushButton_plotSelectedData">
                   <property name="sizePolicy">
@@ -4341,6 +4351,21 @@ p, li { white-space: pre-wrap; }
                 </property>
                </widget>
               </item>
+              <item row="2" column="10">
+               <widget class="QCheckBox" name="checkBox_fpHighPrecision">
+                <property name="font">
+                 <font>
+                  <pointsize>9</pointsize>
+                 </font>
+                </property>
+                <property name="toolTip">
+                 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt;&quot;&gt;If selected, the output peak intensity will have higher precision, i.e., (3i4,2f18.5,i4).&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt;&quot;&gt;Otherwise, it peak intensities will be written as (3i4,2f8.2,i4).&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                </property>
+                <property name="text">
+                 <string>High Precision</string>
+                </property>
+               </widget>
+              </item>
              </layout>
             </item>
            </layout>
@@ -5556,6 +5581,9 @@ p, li { white-space: pre-wrap; }
         </item>
        </layout>
       </item>
+      <item>
+       <widget class="QLineEdit" name="lineEdit_message"/>
+      </item>
      </layout>
     </item>
    </layout>
diff --git a/scripts/HFIR_4Circle_Reduction/NTableWidget.py b/scripts/HFIR_4Circle_Reduction/NTableWidget.py
index 49b736234e141202d33365091ce209744b63a52b..eb248276645a088a2f005e7bae42434c3974bd33 100644
--- a/scripts/HFIR_4Circle_Reduction/NTableWidget.py
+++ b/scripts/HFIR_4Circle_Reduction/NTableWidget.py
@@ -144,10 +144,10 @@ class NTableWidget(QtGui.QTableWidget):
         :return:
         """
         # check
-        assert isinstance(row_index, int)
-        assert isinstance(col_index, int)
-        assert 0 <= row_index < self.rowCount()
-        assert 0 <= col_index < self.columnCount()
+        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'
 
         # get cell type
         cell_data_type = self._myColumnTypeList[col_index]
@@ -169,7 +169,13 @@ class NTableWidget(QtGui.QTableWidget):
             elif cell_data_type == 'int':
                 return_value = int(return_value)
             elif cell_data_type == 'float' or cell_data_type == 'double':
-                return_value = float(return_value)
+                try:
+                    return_value = float(return_value)
+                except ValueError as val_err:
+                    raise RuntimeError('Unable to convert cell ({0}, {1}) with value "{2}" to float due to {3}.'
+                                       ''.format(row_index, col_index, return_value, val_err))
+            # END-IF-ELSE
+        # END-IF-ELSE
 
         return return_value
 
diff --git a/scripts/HFIR_4Circle_Reduction/PeaksIntegrationReport.py b/scripts/HFIR_4Circle_Reduction/PeaksIntegrationReport.py
index def4b05f0095737f9658e624028615f86743490d..19d2c7007887e44225dce41e1f686478deff9a5d 100644
--- a/scripts/HFIR_4Circle_Reduction/PeaksIntegrationReport.py
+++ b/scripts/HFIR_4Circle_Reduction/PeaksIntegrationReport.py
@@ -74,33 +74,34 @@ class PeaksIntegrationReportDialog(QtGui.QDialog):
 
         scan_number_list = sorted(peak_integration_summary.keys())
         for scan_number in scan_number_list:
-            print('[DB...BAT] Scan {0} Peak integration report '
-                  'keys: {1}'.format(scan_number, peak_integration_summary[scan_number].keys()))
-
-            spice_hkl = peak_integration_summary[scan_number]['SPICE HKL']
-            calculated_hkl = peak_integration_summary[scan_number]['Mantid HKL']
-            mask_name = peak_integration_summary[scan_number]['Mask']
-            intensity1 = peak_integration_summary[scan_number]['Raw Intensity']
-            error1 = peak_integration_summary[scan_number]['Raw Intensity Error']
-            intensity2 = peak_integration_summary[scan_number]['Intensity 2']
-            error2 = peak_integration_summary[scan_number]['Intensity 2 Error']
-            intensity3 = peak_integration_summary[scan_number]['Gauss Intensity']
-            error3 = peak_integration_summary[scan_number]['Gauss Error']
-            lorentz_factor = peak_integration_summary[scan_number]['Lorentz']
-            estimated_bkgd = peak_integration_summary[scan_number]['Estimated Background']
-            gauss_bkgd = peak_integration_summary[scan_number]['Fitted Background']
-            gauss_a = peak_integration_summary[scan_number]['Fitted A']
-            gauss_sigma = peak_integration_summary[scan_number]['Fitted Sigma']
-            motor_name = peak_integration_summary[scan_number]['Motor']
-            motor_step = peak_integration_summary[scan_number]['Motor Step']
-            k_shift = peak_integration_summary[scan_number]['K-vector']
-            absorption_correction = peak_integration_summary[scan_number]['Absorption Correction']
-
-            self.ui.tableWidget_spreadsheet.add_scan_information(scan_number, spice_hkl, calculated_hkl,
-                                                                 mask_name, intensity1, error1, intensity2, error2,
-                                                                 intensity3, error3, lorentz_factor, estimated_bkgd,
-                                                                 gauss_bkgd, gauss_sigma, gauss_a, motor_name,
-                                                                 motor_step, k_shift, absorption_correction)
+            try:
+                spice_hkl = peak_integration_summary[scan_number]['SPICE HKL']
+                calculated_hkl = peak_integration_summary[scan_number]['Mantid HKL']
+                mask_name = peak_integration_summary[scan_number]['Mask']
+                intensity1 = peak_integration_summary[scan_number]['Raw Intensity']
+                error1 = peak_integration_summary[scan_number]['Raw Intensity Error']
+                intensity2 = peak_integration_summary[scan_number]['Intensity 2']
+                error2 = peak_integration_summary[scan_number]['Intensity 2 Error']
+                intensity3 = peak_integration_summary[scan_number]['Gauss Intensity']
+                error3 = peak_integration_summary[scan_number]['Gauss Error']
+                lorentz_factor = peak_integration_summary[scan_number]['Lorentz']
+                estimated_bkgd = peak_integration_summary[scan_number]['Estimated Background']
+                gauss_bkgd = peak_integration_summary[scan_number]['Fitted Background']
+                gauss_a = peak_integration_summary[scan_number]['Fitted A']
+                gauss_sigma = peak_integration_summary[scan_number]['Fitted Sigma']
+                motor_name = peak_integration_summary[scan_number]['Motor']
+                motor_step = peak_integration_summary[scan_number]['Motor Step']
+                k_shift = peak_integration_summary[scan_number]['K-vector']
+                absorption_correction = peak_integration_summary[scan_number]['Absorption Correction']
+
+                self.ui.tableWidget_spreadsheet.add_scan_information(scan_number, spice_hkl, calculated_hkl,
+                                                                     mask_name, intensity1, error1, intensity2, error2,
+                                                                     intensity3, error3, lorentz_factor, estimated_bkgd,
+                                                                     gauss_bkgd, gauss_sigma, gauss_a, motor_name,
+                                                                     motor_step, k_shift, absorption_correction)
+            except KeyError as key_err:
+                print ('ERROR: Unable to add scan {0} to report due to {1}'.format(scan_number, key_err))
+
         # END-FOR
 
         return
diff --git a/scripts/HFIR_4Circle_Reduction/PreprocessWindow.py b/scripts/HFIR_4Circle_Reduction/PreprocessWindow.py
index d54453a8fed5989e9c0a5584f3b35241373e2590..e2862de91f6e7c9455546eaf55884e40bd7b1a85 100644
--- a/scripts/HFIR_4Circle_Reduction/PreprocessWindow.py
+++ b/scripts/HFIR_4Circle_Reduction/PreprocessWindow.py
@@ -398,9 +398,6 @@ class ScanPreProcessWindow(QtGui.QMainWindow):
         self._recordLogMutex = False
 
         # check whether it is time to write all the scans to file
-        print '[DB...BAT] Scans to process: {0} vs \n\tScans processed: {1}' \
-              ''.format(self._scansToProcess, self._scanNumbersProcessed)
-
         if len(self._scansToProcess) == len(self._scanNumbersProcessed):
             self.update_record_file(self._currExpNumber, check_duplicates=False, scan_list=self._scanNumbersProcessed)
             if self._scansToProcess != self._scanNumbersProcessed:
@@ -427,11 +424,12 @@ class ScanPreProcessWindow(QtGui.QMainWindow):
         return
 
     def update_record_file(self, exp_number, check_duplicates, scan_list):
-        """
-        update the record file
+        """ update the record file
         it is an option to append file or check and remove duplication.
         duplication can be removed in the record file loading method by checking the time stamp
+        :param exp_number:
         :param check_duplicates:
+        :param scan_list:
         :return:
         """
         # check inputs
diff --git a/scripts/HFIR_4Circle_Reduction/detector2dview.py b/scripts/HFIR_4Circle_Reduction/detector2dview.py
index 4b8e0a275b00eebb804e9a14d95326a7d054e90f..a1c65446afe383d39049f78fb68423236f9b0016 100644
--- a/scripts/HFIR_4Circle_Reduction/detector2dview.py
+++ b/scripts/HFIR_4Circle_Reduction/detector2dview.py
@@ -1,6 +1,7 @@
 #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
 
@@ -15,6 +16,8 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView):
         LEFT = 1
         RIGHT = 3
 
+    newROIDefinedSignal = QtCore.pyqtSignal(int, int, int, int)  # return coordinate of the
+
     def __init__(self, parent):
         """
 
@@ -34,7 +37,7 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView):
         # class status variables
         self._roiSelectMode = False
         # region of interest. None or 2 tuple of 2-tuple for upper left corner and lower right corner
-        self._myROI = None
+        # mouse positions as start and end
         self._roiStart = None
         self._roiEnd = None
 
@@ -52,22 +55,40 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView):
 
         return
 
-    def add_roi(self, roi_start, roi_end):
-        """ Add region of interest
-        :param roi_start:
-        :param roi_end:
+    # 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)
         :return:
         """
-        # check
-        assert isinstance(roi_start, tuple) and len(roi_start) == 2
-        assert isinstance(roi_end, tuple) and len(roi_end) == 2
+        # clear the current record
+        self._myPolygon = None
 
+        # reset mouse selection ROI
         # set
-        self._roiStart = roi_start
-        self._roiEnd = roi_end
+        self._roiStart = None
+        self._roiEnd = None
 
-        # plot
-        self.plot_roi()
+        # call base class
+        super(Detector2DView, self).clear_canvas()
 
         return
 
@@ -76,16 +97,18 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView):
         Enter or leave the region of interest (ROI) selection mode
         :return:
         """
-        assert isinstance(state, bool)
+        assert isinstance(state, bool), 'blabla'
 
         # set
         self._roiSelectMode = state
 
         if state:
             # new in add-ROI mode
+            self.remove_roi()
+        else:
+            # reset roi start and roi end
             self._roiStart = None
             self._roiEnd = None
-            self._myPolygon = None
 
         # # reset _myPolygen
         # if state is False:
@@ -154,6 +177,16 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView):
 
         return message
 
+    @property
+    def is_roi_selection_drawn(self):
+        """
+        whether ROI is drawn
+        :return:
+        """
+        is_drawn = not (self._myPolygon is None)
+
+        return is_drawn
+
     def get_roi(self):
         """
         :return: A list for polygon0
@@ -162,18 +195,49 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView):
         assert self._roiEnd is not None
 
         # rio start is upper left, roi end is lower right
-        lower_left = (self._roiStart[0], self._roiEnd[1])
-        upper_right = (self._roiEnd[0], self._roiStart[1])
+        lower_left_x = min(self._roiStart[0], self._roiEnd[0])
+        lower_left_y = min(self._roiStart[1], self._roiEnd[1])
+        lower_left = lower_left_x, lower_left_y
+
+        # ROI upper right
+        upper_right_x = max(self._roiStart[0], self._roiEnd[0])
+        upper_right_y = max(self._roiStart[1], self._roiEnd[1])
+        upper_right = upper_right_x, upper_right_y
 
         return lower_left, upper_right
 
+    def plot_detector_counts(self, raw_det_data):
+        """
+        plot detector counts as 2D plot
+        :param raw_det_data:
+        :return:
+        """
+        x_min = 0
+        x_max = raw_det_data.shape[0]
+        y_min = 0
+        y_max = raw_det_data.shape[1]
+
+        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 self._myPolygon is not None:
+            print ('[DB...BAT...] Add PATCH')
+            self._myCanvas.add_patch(self._myPolygon)
+        else:
+            print ('[DB...BAT...] NO PATCH')
+
+        print ('[DB...BAT...AFTER]  ROI Rect: {0}.  2D plot: {1}'.format(self._myPolygon, count_plot))
+
+        return
+
     def plot_roi(self):
         """ Plot region of interest (as rectangular) to the canvas from the region set from
         :return:
         """
         # check
-        assert self._roiStart is not None
-        assert self._roiEnd is not None
+        # TODO FIXME - Fill blabla
+        assert self._roiStart is not None, 'blabla'
+        assert self._roiEnd is not None, 'blabla'
 
         # create a vertex list of a rectangular
         vertex_array = np.ndarray(shape=(4, 2))
@@ -194,6 +258,9 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView):
         vertex_array[3][1] = self._roiEnd[1]
 
         # register
+        if self._myPolygon is not None:
+            self._myPolygon.remove()
+            self._myPolygon = None
         self._myPolygon = self._myCanvas.plot_polygon(vertex_array, fill=False, color='w')
 
         return
@@ -203,6 +270,7 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView):
         Remove the rectangular for region of interest
         :return:
         """
+        print ('[DB...BAT] Try to remove ROI {0}'.format(self._myPolygon))
         if self._myPolygon is not None:
             # polygon is of type matplotlib.patches.Polygon
             self._myPolygon.remove()
@@ -214,6 +282,9 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView):
             self._roiStart = None
             self._roiEnd = None
 
+        else:
+            print ('[NOTICE] Polygon is None.  Nothing to remove')
+
         return
 
     def on_mouse_motion(self, event):
@@ -301,6 +372,10 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView):
         if self._roiSelectMode and prev_mouse_pressed == Detector2DView.MousePress.LEFT:
             # end the ROI selection mode
             self.update_roi_poly(self._currX, self._currY)
+
+            # send a signal to parent such that a rew ROI is defined
+            self.newROIDefinedSignal.emit(self._roiStart[0], self._roiStart[1], self._roiEnd[0], self._roiEnd[1])
+
         # END-IF
 
         return
@@ -329,6 +404,32 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView):
 
         self._myParentWindow = parent_window
 
+        self.newROIDefinedSignal.connect(self._myParentWindow.evt_new_roi)
+
+        return
+
+    def set_roi(self, lower_left_corner, upper_right_corner, plot=True):
+        """
+        set ROI to class variables
+        :param lower_left_corner:
+        :param upper_right_corner:
+        :param plot: if True, then plot ROI
+        :return:
+        """
+        # check inputs
+        assert len(lower_left_corner) == 2, 'Lower left corner row/col coordinate {0} must have 2 items.' \
+                                            ''.format(lower_left_corner)
+        assert len(upper_right_corner) == 2, 'Upper right corner row/col coordinate {0} must have 2 items.' \
+                                             ''.format(upper_right_corner)
+
+        # set lower left corner and upper right corner
+        self._roiStart = lower_left_corner
+        self._roiEnd = upper_right_corner
+
+        # plot
+        if plot:
+            self.plot_roi()
+
         return
 
     def update_roi_poly(self, cursor_x, cursor_y):
@@ -353,8 +454,8 @@ class Detector2DView(mpl2dgraphicsview.Mpl2dGraphicsView):
         # plot the new polygon
         self.plot_roi()
 
-        # update
-        if self._myPolygon is not None:
-            self._myParentWindow.do_apply_roi()
+        # # update: no need to do this!
+        # if self._myPolygon is not None:
+        #     self._myParentWindow.do_apply_roi()
 
         return
diff --git a/scripts/HFIR_4Circle_Reduction/downloaddialog.py b/scripts/HFIR_4Circle_Reduction/downloaddialog.py
index 469703f43df81579d5af04bc8dcf3228ae6c956c..9747b7ae7a7b5cdb0170863266902c1a59404192 100644
--- a/scripts/HFIR_4Circle_Reduction/downloaddialog.py
+++ b/scripts/HFIR_4Circle_Reduction/downloaddialog.py
@@ -60,7 +60,7 @@ class DataDownloadDialog(QtGui.QDialog):
             self._myControl = None
             self._myControl = parent.controller
         except AttributeError as att_err:
-            print att_err
+            print (att_err)
 
         # experiment number
         self._expNumber = None
diff --git a/scripts/HFIR_4Circle_Reduction/fourcircle_utility.py b/scripts/HFIR_4Circle_Reduction/fourcircle_utility.py
index 26f056438727a4af4c79a4e13578cc62a8212b4d..9b942a99a085154c471c474fde687ed50c33a2a1 100644
--- a/scripts/HFIR_4Circle_Reduction/fourcircle_utility.py
+++ b/scripts/HFIR_4Circle_Reduction/fourcircle_utility.py
@@ -129,24 +129,17 @@ def generate_mask_file(file_path, ll_corner, ur_corner, rectangular=True, num_de
     else:
         start_row = mpl_start_row
         start_col = mpl_start_col
-        end_row =   mpl_end_row
-        end_col =   mpl_end_col
+        end_row = mpl_end_row
+        end_col = mpl_end_col
 
     assert start_col < end_col, 'Start column {0} cannot be smaller than end column {1}.'.format(start_col, end_col)
 
     det_sub_xml = ''
-    if False:
-        for col_number in range(start_col, end_col+1):
-            start_det_id = 1 + col_number * NUM_DET_ROW + start_row
-            end_det_id = 1 + col_number * NUM_DET_ROW + end_row
-            det_sub_xml += '%d-%d,' % (start_det_id, end_det_id)
-    else:
-        # print('[DB...BAT] Row numbers from {0} to {1}'.format(start_row, end_row))
-        # print('[DB...BAT] Col numbers from {0} to {1}'.format(start_col, end_col))
-        for row_number in range(start_row, end_row+1):
-            start_det_id = 1 + row_number * num_det_row + start_col
-            end_det_id = 1 + row_number * num_det_row + end_col
-            det_sub_xml += '{0}-{1},'.format(start_det_id, end_det_id)
+    # the plot out data is in row major (while the raw data is in column major, which is rotated 90 later)
+    for row_number in range(start_row, end_row + 1):
+        start_det_id = 1 + row_number * num_det_row + start_col
+        end_det_id = 1 + row_number * num_det_row + end_col
+        det_sub_xml += '{0}-{1},'.format(start_det_id, end_det_id)
     # END-FOR
 
     # remove last ','
diff --git a/scripts/HFIR_4Circle_Reduction/fputility.py b/scripts/HFIR_4Circle_Reduction/fputility.py
index 9a3f1402897b35940178624ffd09ee90a3ef7e5c..c0c4fd9698685c2d08aa519cf715ba264c6631cc 100644
--- a/scripts/HFIR_4Circle_Reduction/fputility.py
+++ b/scripts/HFIR_4Circle_Reduction/fputility.py
@@ -2,7 +2,6 @@
 # Utility methods for Fullprof
 from __future__ import (absolute_import, division, print_function)
 import os
-import sys
 import math
 
 
@@ -70,8 +69,7 @@ def load_scd_fullprof_intensity_file(file_name):
 
         if line_index == 0:
             # line 1 as header
-            header = line
-            print('[INFO] Header:', header)
+            pass
         elif line.startswith('('):
             # line 2 format line, skip
             continue
@@ -136,7 +134,7 @@ def convert_to_peak_dict_list(refection_dict):
 
 
 def write_scd_fullprof_kvector(user_header, wave_length, k_vector_dict, peak_dict_list, fp_file_name,
-                               with_absorption):
+                               with_absorption, high_precision):
     """
     Purpose: Export integrated peaks to single crystal diffraction Fullprof file
     Requirements:
@@ -149,6 +147,7 @@ def write_scd_fullprof_kvector(user_header, wave_length, k_vector_dict, peak_dic
     :param peak_dict_list: a list of peak parameters stored in dictionaries.
     :param fp_file_name:
     :param with_absorption:
+    :param high_precision:
     :return:
     """
     # check input validity
@@ -156,12 +155,13 @@ def write_scd_fullprof_kvector(user_header, wave_length, k_vector_dict, peak_dic
     assert isinstance(wave_length, float), 'Neutron wave length must be a float.'
     assert isinstance(k_vector_dict, dict), 'K-vector list must be a dictionary.'
     assert isinstance(peak_dict_list, list), 'Peak-dictionary list must be a list.'
+    assert isinstance(high_precision, bool), 'blabla'
 
     # determine whether the output is for magnetic peaks or nuclear peaks
     # assuming that either all peaks are magnetic or all peaks are nuclear
     num_k_vectors = len(k_vector_dict)
     peak_0_dict = peak_dict_list[0]
-    assert isinstance(peak_0_dict, dict) and 'kindex' in peak_0_dict
+    assert isinstance(peak_0_dict, dict) and 'kindex' in peak_0_dict, 'blabla'
     peak0_is_magnetic = peak_0_dict['kindex'] > 0 and num_k_vectors > 0
 
     # set up fullprof .ini file buffer
@@ -177,8 +177,14 @@ def write_scd_fullprof_kvector(user_header, wave_length, k_vector_dict, peak_dic
 
     if with_absorption:
         file_format = '(%s,2f8.2,i4,6f8.5)' % first_3_terms_foramt
+    elif high_precision:
+        # precisions: (3i4,2f18.5,i4)
+        file_format = '({0},2f18.5,i4)'.format(first_3_terms_foramt)
     else:
+        # precisions: (3i4,2f8.2,i4)
         file_format = '(%s,2f8.2,i4)' % first_3_terms_foramt
+    # END-IF
+
     # wave length
     lambda_line = '%.4f  0  0' % wave_length
 
@@ -197,6 +203,13 @@ def write_scd_fullprof_kvector(user_header, wave_length, k_vector_dict, peak_dic
         fp_buffer += kline + '\n'
     # END-IF
 
+    # again in body,
+    if high_precision:
+        # '%18.5f%18.5f%4d'
+        part3_format = '{0:18.5f}{1:18.5f}{2:4d}'
+    else:
+        part3_format = '%8.2f%8.2f%4d'
+
     # peak intensities
     for i_peak, peak_dict in enumerate(peak_dict_list):
         # check
@@ -232,7 +245,10 @@ def write_scd_fullprof_kvector(user_header, wave_length, k_vector_dict, peak_dic
 
         # peak intensity and sigma
         try:
-            part3 = '%8.2f%8.2f%4d' % (peak_dict['intensity'], peak_dict['sigma'], 1)
+            if high_precision:
+                part3 = part3_format.format(peak_dict['intensity'], peak_dict['sigma'], 1)
+            else:
+                part3 = '%8.2f%8.2f%4d' % (peak_dict['intensity'], peak_dict['sigma'], 1)
         except TypeError as type_err:
             raise RuntimeError('In writing Fullprof file, unable to convert intensity {0} and/or sigma {1} to '
                                'floats. FYI: {2}'.format(peak_dict['intensity'], peak_dict['sigma'], type_err))
@@ -270,39 +286,3 @@ def nearest_int(number):
         answer = int(number - 0.5)
 
     return answer
-
-
-def main(argv):
-    """
-    main to calculate difference
-    :param argv:
-    :return:
-    """
-    # get input
-    if len(argv) < 4:
-        print('Calculate the difference of two measurements:\n')
-        print('> %s [intensity file 1]  [intensity file 2]  [output intensity file]' % argv[0])
-        return
-    else:
-        int_file_1 = argv[1]
-        int_file_2 = argv[2]
-        out_file_name = argv[3]
-
-    intensity_dict1, wave_length1, error_message1 = load_scd_fullprof_intensity_file(int_file_1)
-    intensity_dict2, wave_length2, error_message2 = load_scd_fullprof_intensity_file(int_file_2)
-
-    if len(error_message1) == 0:
-        print('[Error] %s: %s' % (int_file_1, error_message1))
-    if len(error_message2) == 0:
-        print('[Error] %s: %s' % (int_file_2, error_message2))
-
-    diff_dict = calculate_intensity_difference(intensity_dict1, intensity_dict2)
-    diff_peak_list = convert_to_peak_dict_list(diff_dict)
-    write_scd_fullprof_kvector('difference', wave_length=wave_length1, k_vector_dict=dict(), peak_dict_list=diff_peak_list,
-                               fp_file_name=out_file_name, with_absorption=False)
-
-    return
-
-
-if __name__ == '__main__':
-    main(sys.argv)
diff --git a/scripts/HFIR_4Circle_Reduction/hfctables.py b/scripts/HFIR_4Circle_Reduction/hfctables.py
index 59fb66e84c52ab3a0b8a7a25881729482335e606..dfee87978f983e5f52dc5ddadbb27a903a82d5f7 100644
--- a/scripts/HFIR_4Circle_Reduction/hfctables.py
+++ b/scripts/HFIR_4Circle_Reduction/hfctables.py
@@ -106,7 +106,10 @@ class MatrixTable(tableBase.NTableWidget):
 
         # think of reset
         if self.rowCount() != num_rows or self.columnCount() != num_cols:
-            raise RuntimeError('ASAP')
+            errmsg = 'Number of rows to set {0} is not equal to current number of rows {1} or ' \
+                     'Number of columns to set {2} is not equal to current number of columns {3}' \
+                     ''.format(self.rowCount(), num_rows, self.columnCount(), num_cols)
+            raise RuntimeError(errmsg)
 
         return
 
@@ -899,8 +902,8 @@ class ProcessTableWidget(tableBase.NTableWidget):
     TableSetup = [('Scan', 'int'),
                   ('Status', 'str'),
                   ('Intensity', 'float'),
-                  ('Corrected', 'float'),  # Lorenzian corrected
-                  ('Error', 'float'),
+                  ('F2', 'float'),  # Lorenzian corrected
+                  ('F2 Error', 'float'),
                   ('Integrate', 'str'),  # integration type, Gaussian fit / simple summation
                   ('Mask', 'str'),  # '' for no mask
                   ('HKL', 'str'),
@@ -910,6 +913,11 @@ class ProcessTableWidget(tableBase.NTableWidget):
                   ('K-Index', 'int'),
                   ('Select', 'checkbox')]
 
+    """
+    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'.
+    """
+
     def __init__(self, parent):
         """
         Initialization
@@ -1357,7 +1365,8 @@ class ProcessTableWidget(tableBase.NTableWidget):
         # set up column index
         self._colIndexScan = ProcessTableWidget.TableSetup.index(('Scan', 'int'))
         self._colIndexIntensity = self.TableSetup.index(('Intensity', 'float'))
-        self._colIndexCorrInt = self.TableSetup.index(('Corrected', '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'))
@@ -1368,7 +1377,6 @@ class ProcessTableWidget(tableBase.NTableWidget):
         self._colIndexMotorStep = ProcessTableWidget.TableSetup.index(('Motor Step', 'str'))
         self._colIndexWaveLength = self.TableSetup.index(('Wavelength', 'float'))
         self._colIndexKIndex = self.TableSetup.index(('K-Index', 'int'))
-        self._colIndexErrorBar = self.TableSetup.index(('Error', 'float'))
         # self._colIndexWorkspace = self.TableSetup.index(('Workspace', 'str'))
 
         return
diff --git a/scripts/HFIR_4Circle_Reduction/mpl2dgraphicsview.py b/scripts/HFIR_4Circle_Reduction/mpl2dgraphicsview.py
index 2ccca8920638a65f44903d42485b1492eb49ceaf..0a053582a497c562e7c98ae34dd0f59641133960 100644
--- a/scripts/HFIR_4Circle_Reduction/mpl2dgraphicsview.py
+++ b/scripts/HFIR_4Circle_Reduction/mpl2dgraphicsview.py
@@ -36,6 +36,9 @@ class Mpl2dGraphicsView(QtGui.QWidget):
         self._myImageIndex = 0
         self._myImageDict = dict()
 
+        # current 2D plot
+        self._2dPlot = None
+
         return
 
     @property
@@ -58,9 +61,9 @@ class Mpl2dGraphicsView(QtGui.QWidget):
         :param y_tick_label:
         :return:
         """
-        self._myCanvas.add_2d_plot(array2d, x_min, x_max, y_min, y_max, hold_prev_image, y_tick_label)
+        self._2dPlot = self._myCanvas.add_2d_plot(array2d, x_min, x_max, y_min, y_max, hold_prev_image, y_tick_label)
 
-        return
+        return self._2dPlot
 
     def add_image(self, imagefilename):
         """ Add an image to canvas from an image file
@@ -98,6 +101,17 @@ class Mpl2dGraphicsView(QtGui.QWidget):
         # There is no operation that is defined now
         return
 
+    def remove_last_plot(self):
+        """
+
+        :return:
+        """
+        if self._2dPlot is not None:
+            self._2dPlot.remove()
+            self._2dPlot = None
+
+        return
+
     @property
     def x_min(self):
         """
@@ -154,9 +168,6 @@ class Qt4Mpl2dCanvas(FigureCanvas):
         # legend and color bar
         self._colorBar = None
 
-        # polygon
-        self._myPolygon = None
-
         # Buffer of data
         self._currentArray2D = None
 
@@ -189,7 +200,7 @@ class Qt4Mpl2dCanvas(FigureCanvas):
         :param x_max:
         :param y_min:
         :param y_max:
-        :param hold_prev: hold previous image
+        :param hold_prev: hold previous image.  If False, all 2D image and polygon patches will be removed
         :param yticklabels: list of string for y ticks
         :return:
         """
@@ -236,11 +247,22 @@ class Qt4Mpl2dCanvas(FigureCanvas):
 
         return self._currIndex
 
+    def add_patch(self, patch):
+        """
+        add an artist patch such as polygon
+        :param patch:
+        :return:
+        """
+        self.axes.add_artist(patch)
+
+        # Flush...
+        self._flush()
+
+        return
+
     def addImage(self, imagefilename):
         """ Add an image by file
         """
-        #import matplotlib.image as mpimg
-
         # set aspect to auto mode
         self.axes.set_aspect('auto')
 
@@ -277,10 +299,7 @@ class Qt4Mpl2dCanvas(FigureCanvas):
             self.fig.clear()
             # Re-create subplot
             self.axes = self.fig.add_subplot(111)
-
-        # clear polygon
-        if self._myPolygon is not None:
-            self._myPolygon.remove()
+        # END-FOR
 
         # flush/commit
         self._flush()
diff --git a/scripts/HFIR_4Circle_Reduction/multi_threads_helpers.py b/scripts/HFIR_4Circle_Reduction/multi_threads_helpers.py
index 3b99b54cc3fd2d5767eec72282a0f14dad6a6981..65201d5f2c9593b757ff371fcaaf4cc2e447140b 100644
--- a/scripts/HFIR_4Circle_Reduction/multi_threads_helpers.py
+++ b/scripts/HFIR_4Circle_Reduction/multi_threads_helpers.py
@@ -316,9 +316,6 @@ class IntegratePeaksThread(QThread):
         * add motor step information
         :return:
         """
-        # print '[DB...BAT] Set Integrated Peak Info is called for exp {0} scan {1}.' \
-        #       ''.format(self._expNumber, scan_number)
-
         # get peak information
         peak_info_obj = self._mainWindow.controller.get_peak_info(self._expNumber, scan_number)
 
diff --git a/scripts/HFIR_4Circle_Reduction/peak_integration_utility.py b/scripts/HFIR_4Circle_Reduction/peak_integration_utility.py
index f5695288bdd0d4a21b36595901da1dd28065deca..6071d0b74a97122a5ab28818580232a969dfdf78 100644
--- a/scripts/HFIR_4Circle_Reduction/peak_integration_utility.py
+++ b/scripts/HFIR_4Circle_Reduction/peak_integration_utility.py
@@ -30,9 +30,6 @@ def calculate_lorentz_correction_factor(q_sample, wavelength, motor_step):
     theta = math.asin(sin_theta)
     factor = numpy.sin(2 * theta) * motor_step
 
-    # print('[DB...BAT Lorentz] Q-sample = {0}, wavelength = {1}, motor step = {2}, theta = {3} --> factor = {4}.' \
-    #       ''.format(q_sample, wavelength, motor_step, theta, factor))
-
     return factor
 
 
@@ -45,7 +42,6 @@ def calculate_motor_step(motor_pos_array, motor_step_tolerance=0.5):
     """
     assert isinstance(motor_pos_array, numpy.ndarray), 'Motor positions {0} must be given as a numpy array but not ' \
                                                        'a {1}.'.format(motor_pos_array, type(motor_pos_array))
-    # need to check somewhere: assert len(pt_motor_dict) == len(pt_intensity_dict), 'blabla 3'
 
     motor_step_vector = motor_pos_array[1:] - motor_pos_array[:-1]
 
@@ -177,12 +173,6 @@ def fit_gaussian_linear_background(vec_x, vec_y, vec_e, start_value_list=None, f
     assert isinstance(vec_y, numpy.ndarray), 'Input vec_y must be a numpy.ndarray but not a {0}.'.format(vec_y)
     assert isinstance(vec_e, numpy.ndarray), 'Input vec_e must be a numpy.ndarray but not a {0}.'.format(vec_e)
 
-    # print('[DB] Vec X: ', vec_x)
-    # print('[DB] Vec Y: ', vec_y)
-    # print('[DB] Vec e: ', vec_e)
-    # print('[DB] Start values: ', start_value_list)
-    # print('[DB] Find start value by fit: ', find_start_value_by_fit)
-
     # starting value
     if isinstance(start_value_list, list):
         assert len(start_value_list) == 4, 'If specified, there must be 4 values: a, x0, sigma and b but not {0}.' \
@@ -194,21 +184,11 @@ def fit_gaussian_linear_background(vec_x, vec_y, vec_e, start_value_list=None, f
         # get result
         start_value_list = [start_x0, start_sigma, start_a, 0.0]
 
-        # print('[DB] Start value by fit: ', start_value_list)
-
     else:
         # guess starting value via observation
         start_value_list = find_gaussian_start_values_by_observation(vec_x, vec_y)
-
-        # print('[DB] Start value by observation: ', start_value_list)
     # END-IF-ELSE
 
-    """
-    [DB] Start values:  None
-    [DB] Find start value by fit:  False
-    [DB] Start value by observation:  [21, 19.0, 10.5, 100.0]: should be
-    """
-
     # 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)
@@ -218,8 +198,6 @@ def fit_gaussian_linear_background(vec_x, vec_y, vec_e, start_value_list=None, f
     x0, sigma, a, b = fit2_coeff
     model_vec_y = gaussian_linear_background(vec_x, x0, sigma, a, b)
 
-    print('Covariance matrix: ', fit2_cov_matrix)
-
     cost = calculate_penalty(model_vec_y, vec_y)
 
     return cost, fit2_coeff, fit2_cov_matrix
@@ -266,9 +244,6 @@ def fit_motor_intensity_model(motor_pos_dict, integrated_pt_dict):
 
     # fit
     gauss_error, gauss_parameters, cov_matrix = fit_gaussian_linear_background(vec_x, vec_y, vec_e)
-    # print('[DB] Overall Gaussian error = ', gauss_error)
-    # print('[DB] Gaussian fitted parameters = ', gauss_parameters)
-    # print('[DB] Gaussian covariance matrix = ', cov_matrix)
 
     # function parameters (in order): x0, sigma, a, b
     # construct parameter dictionary and error dictionary
@@ -378,8 +353,6 @@ def gaussian_linear_background(x, x0, sigma, a, b):
     :return:
     """
     # gaussian + linear background
-
-    # print('[DB] Input x0 = ', x0, ', sigma = ', sigma, ', a = ', a, ', b = ', b)
     return a * numpy.exp(-(x - x0) ** 2 / (2. * sigma ** 2)) + b
 
 
@@ -413,7 +386,6 @@ def gaussian_peak_intensity(parameter_dict, error_dict):
 
     # I = A\times s\times\sqrt{2 pi}
     peak_intensity = gauss_a * gauss_sigma * numpy.sqrt(2. * numpy.pi)
-    # print('[DB] Gaussian Peak Intensity: A * S * sqrt(2 Pi) == ', gauss_a, gauss_sigma, ' --> peak intensity = ', peak_intensity)
 
     # calculate error
     # \sigma_I^2 = 2\pi (A^2\cdot \sigma_s^2 + \sigma_A^2\cdot s^2 + 2\cdot A\cdot s\cdot \sigma_{As})
@@ -493,7 +465,7 @@ def integrate_single_scan_peak(merged_scan_workspace_name, integrated_peak_ws_na
                                peak_radius, peak_centre,
                                merge_peaks=True,
                                normalization='', mask_ws_name=None,
-                               scale_factor=1):
+                               scale_factor=1.):
 
     """ Integrate the peak in a single scan with merged Pt.
     :param merged_scan_workspace_name: MDEventWorkspace with merged Pts.
@@ -533,6 +505,7 @@ def integrate_single_scan_peak(merged_scan_workspace_name, integrated_peak_ws_na
         norm_by_mon = True
 
     # integrate peak of a scan
+    print ('[DB....BAT] Peak integration scale factor: {0}'.format(scale_factor))
     mantidsimple.IntegratePeaksCWSD(InputWorkspace=merged_scan_workspace_name,
                                     OutputWorkspace=integrated_peak_ws_name,
                                     PeakRadius=peak_radius,
@@ -756,8 +729,6 @@ def simple_integrate_peak(pt_intensity_dict, bg_value, motor_step_dict, peak_cen
             sum_raw_int += intensity
 
         used_pt_list.append(pt)
-
-        # print('[DB...BAT] Motor step size {0} = {1}'.format(pt, motor_step_i))
     # END-FOR
 
     # error = sqrt(sum I_i) * delta
diff --git a/scripts/HFIR_4Circle_Reduction/peakprocesshelper.py b/scripts/HFIR_4Circle_Reduction/peakprocesshelper.py
index 280e56846ebf41f86c501b591a320d97d9c37df9..835b24787b74625aaf774234d618145af4762a64 100644
--- a/scripts/HFIR_4Circle_Reduction/peakprocesshelper.py
+++ b/scripts/HFIR_4Circle_Reduction/peakprocesshelper.py
@@ -69,14 +69,12 @@ class PeakProcessRecord(object):
         # Figure print
         self._fingerPrint = '{0:.7f}.{1}'.format(time.time(), random.randint(0, 10000000))
 
-        # print('[DB...BAT] Create PeakProcessRecord for Exp {0} Scan {1} ({2} | {3}).' \
-        #       ''.format(self._myExpNumber, self._myScanNumber, self._fingerPrint, hex(id(self))))
         return
 
     def calculate_peak_center(self, allow_bad_monitor=True):
         """ Calculate peak's center by averaging the peaks found and stored in PeakWorkspace
         :param allow_bad_monitor: if specified as True, then a bad monitor (zero) is allowed and set the value to 1.
-        :return:
+        :return: str (error message)
         """
         # Go through the peak workspaces to calculate peak center with weight (monitor and counts)
         peak_ws = AnalysisDataService.retrieve(self._myPeakWorkspaceName)
@@ -94,6 +92,7 @@ class PeakProcessRecord(object):
         q_sample_sum = numpy.array([0., 0., 0.])
         weight_sum = 0.
 
+        err_msg = ''
         for i_peak in range(num_found_peaks):
             # get peak
             peak_i = peak_ws.getPeak(i_peak)
@@ -103,7 +102,7 @@ class PeakProcessRecord(object):
             # get row number and then detector counts and monitor counts
             if pt_number not in pt_spice_row_dict:
                 # skip
-                print('[Error] Scan %d Peak %d Pt %d cannot be located.' % (self._myScanNumber, i_peak, pt_number))
+                err_msg += '\nScan %d Peak %d Pt %d cannot be located.' % (self._myScanNumber, i_peak, pt_number)
                 continue
 
             row_index = pt_spice_row_dict[pt_number]
@@ -119,7 +118,6 @@ class PeakProcessRecord(object):
             q_i = peak_i.getQSampleFrame()
             q_array = numpy.array([q_i.X(), q_i.Y(), q_i.Z()])
             # calculate weight
-            print('[DB] Peak {0}: detector counts = {1}, Monitor counts = {2}.'.format(i_peak, det_counts, monitor_counts))
             weight_i = float(det_counts)/float(monitor_counts)
             # contribute to total
             weight_sum += weight_i
@@ -129,21 +127,17 @@ class PeakProcessRecord(object):
         # END-FOR (i_peak)
 
         try:
-            print('[DB] calculate value error: sum(Q-sample) = {0}, sum(weight) = {1}.'.format(q_sample_sum, weight_sum))
             self._avgPeakCenter = q_sample_sum/weight_sum
         except Exception as e:
             raise RuntimeError('Unable to calculate average peak center due to value error as {0}.'.format(e))
 
-        return
+        return err_msg
 
     def generate_integration_report(self):
         """
         generate a dictionary for this PeakInfo
         :return:
         """
-        # print('[DB...BAT] PeakInfo (Scan: {0}, ID: {1}) generate report.  Spice HKL: {2}' \
-        #       ''.format(self._myScanNumber, hex(id(self)), self._spiceHKL))
-
         report = dict()
 
         if self._spiceHKL is not None:
@@ -282,9 +276,7 @@ class PeakProcessRecord(object):
             # if self._spiceHKL is None:
             self.retrieve_hkl_from_spice_table()
             ret_hkl = self._spiceHKL
-
-            # print('[DB...BAT] PeakInfo (Scan: {0}, ID: {1}) SPICE HKL: {2}' \
-            #       ''.format(self._myScanNumber, hex(id(self)), self._spiceHKL))
+        # END-IF-ELSE
 
         return ret_hkl
 
@@ -366,9 +358,6 @@ class PeakProcessRecord(object):
         assert isinstance(factor, float), 'Lorentz correction factor'
         self._lorenzFactor = factor
 
-        # print('[DB...BAT] Exp {0} Scan {1}  ({2} | {3}) has Lorentz factor set up.' \
-        #       ''.format(self._myExpNumber, self._myScanNumber, self._fingerPrint, hex(id(self))))
-
         return
 
     @property
@@ -520,9 +509,6 @@ class PeakProcessRecord(object):
             'Integrated peak information {0} must be given by a dictionary but not a {1}.' \
             ''.format(peak_integration_dict, type(peak_integration_dict))
 
-        # print('[DB...BAT] Exp {0} Scan {1}  ({2} | {3}) has integrated dictionary set up.' \
-        #       ''.format(self._myExpNumber, self._myScanNumber, self._fingerPrint, hex(id(self))))
-
         self._integrationDict = peak_integration_dict
 
         return
diff --git a/scripts/HFIR_4Circle_Reduction/process_mask.py b/scripts/HFIR_4Circle_Reduction/process_mask.py
new file mode 100644
index 0000000000000000000000000000000000000000..6fb2fd74566a60b5da161862bd00ef18e674bfae
--- /dev/null
+++ b/scripts/HFIR_4Circle_Reduction/process_mask.py
@@ -0,0 +1,198 @@
+import math
+import numpy
+import re
+import xml.etree.ElementTree as ET
+from mantid.api import AnalysisDataService as ADS
+
+
+# This module is still in development
+def parse_mask(xml_name):
+    """
+    parse mask XML file
+    :param xml_name:
+    :return:
+    """
+    # get root and 'Data' ndoe
+    tree = ET.parse(xml_name)
+    root = tree.getroot()
+    main_mask_node = None
+    for child in root:
+        if child.tag == 'group':
+            main_mask_node = child
+            break
+
+    # parse detector ID list string to array
+
+    det_list_node = main_mask_node.find('detids')
+    det_list_str = det_list_node.text
+
+    det_range_list = re.split(',', det_list_str)
+
+    for det_range in det_range_list:
+        print det_range
+
+    # int_count = 0
+
+    # det_list_array = numpy.ndarray(shape=(2359296,), dtype='int')
+
+    # index = 0
+    # for count in det_list_count_list:
+    #     if len(count) > 0:
+    #         count_int = int(count)
+    #         det_list_array[index] = count_int
+    #         index += 1
+    #     else:
+    #         int_count += 1
+
+    # print (int_count)
+
+
+def get_region_of_interest(mask_ws_name):
+    """
+    get region of interest from mask workspace
+    :param mask_ws_name:
+    :return:
+    """
+    # check input
+    assert isinstance(mask_ws_name, str), 'Mask workspace name {0} must be an integer but not a {1}' \
+                                          ''.format(mask_ws_name, type(mask_ws_name))
+    mask_ws = ADS.retrieve(mask_ws_name)
+
+    # construct a 2D matrix
+    size_x = size_y = int(math.sqrt(mask_ws.getNumberHistograms()))
+    mask_matrix = numpy.ndarray(shape=(size_x, size_y), dtype='int')
+
+    # mask or unmask all the matrix element according to mask workspace
+    for iws in range(mask_ws.getNumberHistograms()):
+        det_id = mask_ws.getDetector(iws).getID()
+        pixel_2d_id = det_id / size_y, det_id % size_y
+        mask_matrix[pixel_2d_id] = int(mask_ws.isMasked(iws))
+    # END-FOR
+
+    # find lower left corner
+    lower_left_corner = None
+    for ir in range(size_y):
+        if mask_matrix[ir].min() == 0:
+            ll_row = ir
+            ret_value = numpy.where(mask_matrix[ir] == 0)
+            ll_col = ret_value[0][0]
+            lower_left_corner = ll_row, ll_col
+            break
+        # END-IF
+    # END-FOR
+
+    # find upper right corner
+    upper_right_corner = None
+    for ir in range(size_y-1, -1, -1):
+        if mask_matrix[ir].min() == 0:
+            ur_row = ir
+            ret_value = numpy.where(mask_matrix[ir] == 0)
+            ur_col = ret_value[0][-1]
+            upper_right_corner = ur_row, ur_col
+            break
+        # END-IF
+    # END-FOR
+
+    # check before return
+    if lower_left_corner is None or upper_right_corner is None:
+        raise RuntimeError('It is impossible not to find either lower left corner {0} or upper right corner {1}'
+                           ''.format(lower_left_corner, upper_right_corner))
+
+    return lower_left_corner, upper_right_corner
+
+
+class RegionOfInterest(object):
+    """
+    a class to manage region of interest used in application including mask workspace and ROI information
+    """
+    def __init__(self, roi_name):
+        """
+        initialization with unchangeable ROI name
+        :param roi_name:
+        """
+        assert isinstance(roi_name, str), 'ROI name must be a string'
+
+        self._roiName = roi_name
+        self._maskWorkspaceName = None
+        self._lowerLeftCorner = None
+        self._upperRightCorner = None
+
+        return
+
+    @property
+    def mask_workspace(self):
+        """
+        get name of mask workspace
+        :return:
+        """
+        return self._maskWorkspaceName
+
+    @property
+    def lower_left_corner(self):
+        """
+        get lower left corner position
+        :return: 2-tuple
+        """
+        if self._lowerLeftCorner is None:
+            raise RuntimeError('lower left not set')
+
+        return self._lowerLeftCorner
+
+    @property
+    def upper_right_corner(self):
+        """
+        get upper right corner position
+        :return: 2-tuple
+        """
+        if self._upperRightCorner is None:
+            raise RuntimeError('upper right not set')
+
+        return self._upperRightCorner
+
+    def set_mask_workspace_name(self, ws_name):
+        """
+        set mask workspace name to this instance
+        :param ws_name:
+        :return:
+        """
+        assert isinstance(ws_name, str), 'Mask workspace name {0} must be a string but not a {1}' \
+                                         ''.format(ws_name, type(ws_name))
+
+        # check workspace existing and type
+        if ADS.doesExist(ws_name) is False:
+            raise RuntimeError('Workspace {0} does not exist in ADS.')
+        workspace = ADS.retrieve(ws_name)
+        if workspace.id() != 'MaskWorkspace':
+            raise RuntimeError('Workspace {0} is not a MaskWorkspace but a {1}'
+                               ''.format(ws_name, workspace.id()))
+
+        self._maskWorkspaceName = ws_name
+
+        return
+
+    def set_roi_positions(self, lower_left_corner, upper_right_corner):
+        """
+        blabla
+        :param lower_left_corner:
+        :param upper_right_corner:
+        :return:
+        """
+        # check input type and size
+        assert not isinstance(lower_left_corner, str) and len(lower_left_corner) == 2,\
+            'Lower left corner {0} must be a 2-tuple.'.format(lower_left_corner)
+        assert not isinstance(upper_right_corner, str) and len(upper_right_corner) == 2,\
+            'Upper right corner {0} must be a 2-tuple'.format(upper_right_corner)
+
+        ll_x = int(lower_left_corner[0])
+        ll_y = int(lower_left_corner[1])
+        ur_x = int(upper_right_corner[0])
+        ur_y = int(upper_right_corner[1])
+        if ll_x == ur_x or ll_y == ur_y:
+            err_msg = 'Lower left corner ({0}, {1}) and upper right corner are in a line ({2}, {3})' \
+              ''.format(ll_x, ll_y, ur_x, ur_y)
+            raise RuntimeError(err_msg)
+
+        self._lowerLeftCorner = min(ll_x, ur_x), min(ll_y, ur_y)
+        self._upperRightCorner = max(ll_x, ur_x), max(ll_y, ur_y)
+
+        return
diff --git a/scripts/HFIR_4Circle_Reduction/project_manager.py b/scripts/HFIR_4Circle_Reduction/project_manager.py
index ed3e86cd74f33501ee235912dafb89ee1b337ba8..0b20f7baa8d0f2cdbd8e858b74c156050c025f4e 100644
--- a/scripts/HFIR_4Circle_Reduction/project_manager.py
+++ b/scripts/HFIR_4Circle_Reduction/project_manager.py
@@ -16,6 +16,7 @@ class ProjectManager(object):
         Initialization
         :param mode:  (1) import (2) export
         """
+        assert isinstance(project_file_path, str), 'To-save project path {0} must be a string.'.format(project_file_path)
         self._mode = mode
 
         self._projectPath = project_file_path
@@ -68,27 +69,27 @@ class ProjectManager(object):
 
         return
 
-    def export(self, overwrite=False):
+    def save_to_disk(self, overwrite=False):
         """
         Export the (ideally) whole project to disk
         :param overwrite: if specified, then any existing files with same name will be rewritten
-        :return:
+        :return: error message
         """
         # create workspace directory
         self.create_workspace_directory()
-
         print('[INFO] Saving {0} MDEventWorkspaces to {1}.'.format(len(self._wsList), self._wsDir))
 
         # save MDs
+        err_msg = ''
         for ws_name in self._wsList:
             md_file_name = os.path.join(self._wsDir, ws_name + '.nxs')
             if overwrite or not os.path.exists(md_file_name):
                 try:
                     mantidsimple.SaveMD(InputWorkspace=ws_name, Filename=md_file_name)
                 except RuntimeError as run_err:
-                    print('[ERROR] Unable to save {0} due to RuntimeError {1}.'.format(ws_name, run_err))
+                    err_msg += 'Unable to save {0} due to RuntimeError {1}.\n'.format(ws_name, run_err)
                 except Exception as arb_err:
-                    print('[ERROR] Unable to save {0} due to arbitrary exception {1}.'.format(ws_name, arb_err))
+                    err_msg += 'Unable to save {0} due to arbitrary exception {1}.'.format(ws_name, arb_err)
             # END-IF
         # END-FOR (ws_name)
 
@@ -96,12 +97,11 @@ class ProjectManager(object):
             pickle.dump(self._variableDict, pickle_file, pickle.HIGHEST_PROTOCOL)
             pickle.dump(self._wsList, pickle_file, pickle.HIGHEST_PROTOCOL)
 
-        return
+        return err_msg
 
-    def load(self):
-        """
-
-        :return:
+    def load_from_disk(self):
+        """ Load class variables and parameters from saved pickle file
+        :return: error message
         """
         # open file
         with open(self._projectPath, 'rb') as project_file:
@@ -109,16 +109,17 @@ class ProjectManager(object):
             self._wsList = pickle.load(project_file)
 
         # load data
+        err_msg = ''
         for ws_name in self._wsList:
             md_file_path = os.path.join(self._wsDir, ws_name + '.nxs')
             try:
                 mantidsimple.LoadMD(Filename=md_file_path, OutputWorkspace=ws_name)
             except RuntimeError as run_err:
-                print('[DB] Unable to load file {0} due to RuntimeError {1}.'.format(md_file_path, run_err))
+                err_msg += 'Unable to load file {0} due to RuntimeError {1}.'.format(md_file_path, run_err)
             except OSError as run_err:
-                print('[DB] Unable to load file {0} due to OSError {1}.'.format(md_file_path, run_err))
+                err_msg += 'Unable to load file {0} due to OSError {1}.'.format(md_file_path, run_err)
             except IOError as run_err:
-                print('[DB] Unable to load file {0} due to IOError {1}.'.format(md_file_path, run_err))
+                err_msg += 'Unable to load file {0} due to IOError {1}.'.format(md_file_path, run_err)
         # END-FOR
 
-        return
+        return err_msg
diff --git a/scripts/HFIR_4Circle_Reduction/reduce4circleControl.py b/scripts/HFIR_4Circle_Reduction/reduce4circleControl.py
index 4e86b2bffbcf1b612332dac5e95563cc495a027e..ae0507f22cc93810e9eb525fbf849ae594165a09 100644
--- a/scripts/HFIR_4Circle_Reduction/reduce4circleControl.py
+++ b/scripts/HFIR_4Circle_Reduction/reduce4circleControl.py
@@ -30,6 +30,7 @@ from HFIR_4Circle_Reduction import fputility
 from HFIR_4Circle_Reduction import project_manager
 from HFIR_4Circle_Reduction import peak_integration_utility
 from HFIR_4Circle_Reduction import absorption
+from HFIR_4Circle_Reduction import process_mask
 
 import mantid
 import mantid.simpleapi as mantidsimple
@@ -45,10 +46,43 @@ DebugMode = True
 MAX_SCAN_NUMBER = 100000
 
 
+def check_str_type(variable, var_name):
+    """
+
+    :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, str), '{0} {1} must be an string 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
+    :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), '{0} {1} must be an integer but not a {2}' \
+                                      ''.format(var_name, variable, type(variable))
+
+    return
+
+
 class CWSCDReductionControl(object):
     """ Controlling class for reactor-based single crystal diffraction reduction
     """
 
+    RESERVED_ROI_NAME = '__temp_roi__'
+
     def __init__(self, instrument_name=None):
         """ init
         """
@@ -90,6 +124,7 @@ class CWSCDReductionControl(object):
         self._mySpiceTableDict = {}
         # Container for loaded raw pt workspace
         self._myRawDataWSDict = dict()
+        self._myRawDataMasked = dict()
         # Container for PeakWorkspaces for calculating UB matrix
         # self._myUBPeakWSDict = dict()
         # Container for UB  matrix
@@ -110,8 +145,6 @@ class CWSCDReductionControl(object):
 
         # Record for merged scans
         self._mergedWSManager = list()
-        # Region of interest: key = (experiment, scan), value = 2-tuple of 2-tuple: ( (lx, ly), (ux, uy))
-        self._roiDict = dict()
 
         # About K-shift for output of integrated peak
         self._kVectorIndex = 1
@@ -129,9 +162,14 @@ class CWSCDReductionControl(object):
 
         # reference workspace for LoadMask
         self._refWorkspaceForMask = None
+        # Region of interest: key = (experiment, scan), value = RegionOfInterest instance
+        self._roiDict = dict()
 
         # register startup
-        mantid.UsageService.registerFeatureUsage("Interface","4-Circle Reduction",False)
+        mantid.UsageService.registerFeatureUsage("Interface", "4-Circle Reduction", False)
+
+        # debug mode
+        self._debugPrintMode = True
 
         return
 
@@ -211,10 +249,9 @@ class CWSCDReductionControl(object):
 
         return return_k_index
 
-    @staticmethod
-    def apply_mask(exp_number, scan_number, pt_number):
+    def apply_mask(self, exp_number, scan_number, pt_number, roi_name=None):
         """
-        Apply mask on a Pt./run.
+        Apply mask on a Pt./run. by using a standard non-tag-based mask workspace's name
         Requirements:
         1. exp number, scan number, and pt number are integers
         2. mask workspace for this can must exist!
@@ -223,25 +260,32 @@ class CWSCDReductionControl(object):
         :param exp_number:
         :param scan_number:
         :param pt_number:
+        :param roi_name: a string or a None
         :return:
         """
         # check
-        assert isinstance(exp_number, int)
-        assert isinstance(scan_number, int)
-        assert isinstance(pt_number, int)
+        assert isinstance(exp_number, int), 'Exp 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(pt_number, int), 'Pt number {0} must be an integer but not a {1}' \
+                                           ''.format(pt_number, type(pt_number))
 
-        # get workspaces' names
+        # get raw workspace for counts
         raw_pt_ws_name = get_raw_data_workspace_name(exp_number, scan_number, pt_number)
-        mask_ws_name = get_mask_ws_name(exp_number, scan_number)
 
-        # check workspace existing
-        if AnalysisDataService.doesExist(raw_pt_ws_name) is False:
-            raise RuntimeError('Raw data workspace for exp %d scan %d pt %d does not exist.' % (
-                exp_number, scan_number, pt_number
-            ))
+        # an existing mask
+        if roi_name not in self._roiDict:
+            raise RuntimeError('ROI {0} is not in mask workspace dictionary.  Current keys are {1}'
+                               ''.format(roi_name, self._roiDict.keys()))
+        mask_ws_name = self._roiDict[roi_name].mask_workspace
+        if mask_ws_name is None:
+            raise RuntimeError('ROI {0} has no mask workspace set'.format(roi_name))
 
         # mask detectors
         mantidsimple.MaskDetectors(Workspace=raw_pt_ws_name, MaskedWorkspace=mask_ws_name)
+        # record
+        self._myRawDataMasked[(exp_number, scan_number, pt_number)] = roi_name
 
         return
 
@@ -355,15 +399,27 @@ class CWSCDReductionControl(object):
 
         return True, ub_matrix
 
-    def does_raw_loaded(self, exp_no, scan_no, pt_no):
+    def does_raw_loaded(self, exp_no, scan_no, pt_no, roi_name):
         """
         Check whether the raw Workspace2D for a Pt. exists
         :param exp_no:
         :param scan_no:
         :param pt_no:
+        :param roi_name:
         :return:
         """
-        return (exp_no, scan_no, pt_no) in self._myRawDataWSDict
+        # check input
+        check_int_type(exp_no, 'Experiment number')
+        check_int_type(scan_no, 'Scan number')
+        check_int_type(pt_no, 'Pt number')
+
+        loaded = (exp_no, scan_no, pt_no) in self._myRawDataWSDict
+        if loaded:
+            curr_roi = self._myRawDataMasked[(exp_no, scan_no, pt_no)]
+            if roi_name != curr_roi:
+                loaded = False
+
+        return loaded
 
     def does_spice_loaded(self, exp_no, scan_no):
         """ Check whether a SPICE file has been loaded
@@ -498,17 +554,13 @@ class CWSCDReductionControl(object):
         A MaskWorkspace's name is exactly the same as the tag of the mask specified by user in
         reduction GUI.
 
-        :param exp_number:
-        :param scan_number:
+        :param exp_number: must be integer if not retrieve mask workspace
+        :param scan_number: must be integer if not retrieve mask workspace
         :param mask_tag: string as the tag of the mask.
         :param check_throw
         :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} ({1}) must be an integer.' \
-                                             ''.format(scan_number, type(scan_number))
         assert isinstance(mask_tag, str), 'Mask tag {0} ({1}) must be a string.'.format(mask_tag, type(mask_tag))
 
         # MaskWorkspace's name is same as mask's tag
@@ -516,15 +568,25 @@ class CWSCDReductionControl(object):
 
         if AnalysisDataService.doesExist(mask_ws_name) is False:
             # if the workspace does not exist, create a new mask workspace
-            assert mask_tag in self._roiDict, 'Mask tag |%s| does not exist in ROI dictionary!' % mask_tag
+            if exp_number is None:
+                raise RuntimeError('Experiment number is not given with assumption that mask tag {0} shall '
+                                   'be a workspace.'.format(mask_tag))
+
+            # check for experiment and scan number
+            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} ({1}) must be an integer.' \
+                                                 ''.format(scan_number, type(scan_number))
+            if mask_tag not in self._roiDict:
+                raise RuntimeError('Mask tag |{0}| does not exist in ROI dictionary.'.format(mask_tag))
+
             region_of_interest = self._roiDict[mask_tag]
             ll = region_of_interest[0]
             ur = region_of_interest[1]
             self.generate_mask_workspace(exp_number, scan_number, ll, ur, mask_ws_name)
 
         if check_throw:
-            assert AnalysisDataService.doesExist(mask_ws_name), 'MaskWorkspace %s does not exist.' \
-                                                                 '' % mask_ws_name
+            assert AnalysisDataService.doesExist(mask_ws_name), 'MaskWorkspace {0} does not exist.'.format(mask_ws_name)
 
         return mask_ws_name
 
@@ -682,7 +744,7 @@ class CWSCDReductionControl(object):
         return move_tup[1]
 
     def export_to_fullprof(self, exp_number, scan_number_list, user_header,
-                           export_absorption, fullprof_file_name):
+                           export_absorption, fullprof_file_name, high_precision):
         """
         Export peak intensities to Fullprof data file
         :param exp_number:
@@ -690,7 +752,8 @@ class CWSCDReductionControl(object):
         :param user_header:
         :param export_absorption:
         :param fullprof_file_name:
-        :return: 2-tuples. status and return object (file content or error message)
+        :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.'
@@ -728,6 +791,7 @@ class CWSCDReductionControl(object):
         # get ub matrix
         ub_matrix = self.get_ub_matrix(exp_number)
 
+        mixed_content = None
         for algorithm_type in ['simple', 'mixed', 'gauss']:
             # set list of peaks for exporting
             peaks = list()
@@ -743,8 +807,9 @@ class CWSCDReductionControl(object):
 
                 if intensity < std_dev:
                     # error is huge, very likely bad gaussian fit
-                    print('[INFO] Integration Type {0}: Scan {1} Intensity {2} < Std Dev {2} '
-                          'Excluded from exporting.'.format(algorithm_type, scan_number, intensity, std_dev))
+                    if self._debugPrintMode:
+                        print('[INFO] Integration Type {0}: Scan {1} Intensity {2} < Std Dev {2} '
+                              'Excluded from exporting.'.format(algorithm_type, scan_number, intensity, std_dev))
                     continue
                 # END-IF
 
@@ -774,7 +839,10 @@ class CWSCDReductionControl(object):
                 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)
+                    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:
@@ -783,7 +851,7 @@ class CWSCDReductionControl(object):
             continue
         # END-FOR
 
-        return True, file_content
+        return True, mixed_content
 
     def export_md_data(self, exp_number, scan_number, base_file_name):
         """
@@ -887,32 +955,26 @@ class CWSCDReductionControl(object):
 
         return self._refinedUBTup[1], self._refinedUBTup[2], self._refinedUBTup[3]
 
-    def get_region_of_interest(self, exp_number, scan_number):
+    def get_region_of_interest(self, roi_name):
         """ Get region of interest
-        :param exp_number:
-        :param scan_number:
+        :param roi_name: name of the ROI
         :return: region of interest
         """
-        # check
-        assert isinstance(exp_number, int), 'Experiment number {0} must be an integer.'.format(exp_number)
-        assert isinstance(scan_number, int) or scan_number is None, 'Scan number {0} must be either an integer or None.' \
-                                                                    ''.format(scan_number)
-
-        if (exp_number, scan_number) in self._roiDict:
-            # able to find region of interest for this scan
-            ret_status = True
-            ret_value = self._roiDict[(exp_number, scan_number)]
-        elif exp_number in self._roiDict:
-            # able to find region of interest for this experiment
-            ret_status = True
-            ret_value = self._roiDict[exp_number]
-        else:
-            # region of interest of experiment is not defined
-            ret_status = False
-            ret_value = 'Unable to find ROI for experiment %d. Existing includes %s.' % (exp_number,
-                                                                                         str(self._roiDict.keys()))
+        assert isinstance(roi_name, str),\
+            'ROI name {0} must be a string or None but not a {1}.'.format(roi_name, type(roi_name))
 
-        return ret_status, ret_value
+        if roi_name not in self._roiDict:
+            # ROI: not saved
+            raise RuntimeError('ROI not here blabla')
+
+        # check...
+        lower_left_corner = self._roiDict[roi_name].lower_left_corner
+        upper_right_corner = self._roiDict[roi_name].upper_right_corner
+
+        if lower_left_corner is None or upper_right_corner is None:
+            raise RuntimeError('ROI positions not set')
+
+        return lower_left_corner, upper_right_corner
 
     def get_sample_log_value(self, exp_number, scan_number, pt_number, log_name):
         """
@@ -1063,7 +1125,13 @@ class CWSCDReductionControl(object):
                            ll_corner=roi_start,
                            ur_corner=roi_end)
 
-        # load the mask workspace
+        # check reference workspace for mask workspace
+        if self._refWorkspaceForMask is None:
+            return False, 'There is no reference workspace. Plot a Pt. first!'
+        elif AnalysisDataService.doesExist(self._refWorkspaceForMask) is False:
+            return False, 'Previous reference workspace has been deleted. Plot a Pt. first'
+
+        # get the name of the mask workspace to be loaded to
         if mask_tag is None:
             # use default name
             mask_ws_name = get_mask_ws_name(exp_number, scan_number)
@@ -1071,10 +1139,7 @@ class CWSCDReductionControl(object):
             # use given name
             mask_ws_name = str(mask_tag)
 
-        if self._refWorkspaceForMask is None:
-            return False, 'There is no reference workspace. Plot a Pt. first!'
-        elif AnalysisDataService.doesExist(self._refWorkspaceForMask) is False:
-            return False, 'Previous reference workspace has been deleted. Plot a Pt. first'
+        # load the mask workspace
         mantidsimple.LoadMask(Instrument='HB3A',
                               InputFile=mask_file_name,
                               OutputWorkspace=mask_ws_name,
@@ -1082,7 +1147,17 @@ class CWSCDReductionControl(object):
         mantidsimple.InvertMask(InputWorkspace=mask_ws_name,
                                 OutputWorkspace=mask_ws_name)
 
-        return True, mask_ws_name
+        # register
+        self._roiDict[mask_tag].set_mask_workspace_name(mask_ws_name)
+
+        return True, mask_tag
+
+    def get_working_directory(self):
+        """
+        get working directory
+        :return:
+        """
+        return self._workDir
 
     def group_workspaces(self, exp_number, group_name):
         """
@@ -1187,6 +1262,24 @@ class CWSCDReductionControl(object):
 
         return p_key in self._myPeakInfoDict
 
+    def has_roi_generated(self, roi_name):
+        """
+        check whether a MaskWorkspace has been generated for an ROI
+        :param roi_name:
+        :return:
+        """
+        # check input
+        assert isinstance(roi_name, str), 'ROI name {0} must be a string but not a {1}'.format(roi_name, type(roi_name))
+
+        # check whether it is in the dicationary and has a mask workspace set
+        has = True
+        if roi_name not in self._roiDict:
+            has = False
+        elif self._roiDict[roi_name].mask_workspace is None:
+            has = False
+
+        return has
+
     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)
@@ -1195,9 +1288,9 @@ class CWSCDReductionControl(object):
         :return: boolean, object (list of HKL or error message)
         """
         # Check
-        assert isinstance(ub_matrix, numpy.ndarray)
-        assert ub_matrix.shape == (3, 3)
-        assert isinstance(scan_number, int)
+        assert isinstance(ub_matrix, numpy.ndarray), 'UB matrix must be an ndarray'
+        assert ub_matrix.shape == (3, 3), 'UB matrix must be a 3x3 matrix.'
+        assert isinstance(scan_number, int), 'Scan number must be in integer.'
 
         # Find out the PeakInfo
         exp_number = self._expNumber
@@ -1456,7 +1549,14 @@ class CWSCDReductionControl(object):
 
             # Download SPICE file if necessary
             if os.path.exists(spice_file_name) is False:
-                self.download_spice_file(exp_no, scan_no, over_write=True)
+                file_available, download_result = self.download_spice_file(exp_no, scan_no, over_write=True)
+            else:
+                file_available = True
+                download_result = None
+
+            if not file_available:
+                raise IOError('SPICE file for Exp {0} Scan {1} cannot be found at {2} or downloaded ({3})'
+                              ''.format(exp_no, scan_no, spice_file_name, download_result))
 
             try:
                 spice_table_ws, info_matrix_ws = mantidsimple.LoadSpiceAscii(Filename=spice_file_name,
@@ -1520,6 +1620,8 @@ class CWSCDReductionControl(object):
         assert AnalysisDataService.doesExist(pt_ws_name), 'Unable to locate workspace {0}.'.format(pt_ws_name)
         raw_matrix_ws = AnalysisDataService.retrieve(pt_ws_name)
         self._add_raw_workspace(exp_no, scan_no, pt_no, raw_matrix_ws)
+        # clear the mask/ROI information
+        self._myRawDataMasked[(exp_no, scan_no, pt_no)] = None
 
         return True, pt_ws_name
 
@@ -1655,9 +1757,9 @@ class CWSCDReductionControl(object):
         :return:
         """
         # no record is found. it should not happen!
+        if self._preprocessedInfoDict is None:
+            return False
         if scan_number not in self._preprocessedInfoDict:
-            print ('[DB...BAT] Scan {0} is not in pre-processed scan information dictionary. keys are '
-                   '{1}'.format(scan_number, self._preprocessedInfoDict.keys()))
             return False
 
         # check others
@@ -1685,17 +1787,48 @@ class CWSCDReductionControl(object):
             unmatch_score += 400
 
         if unmatch_score > 0:
-            print('[INFO] Exp {0} Scan {1} has a unmatched calibrated record from pre-processed data. ID = {2}'
-                  ''.format(exp_number, scan_number, unmatch_score))
+            if self._debugPrintMode:
+                print('[INFO] Exp {0} Scan {1} has a unmatched calibrated record from pre-processed data. ID = {2}'
+                      ''.format(exp_number, scan_number, unmatch_score))
             return False
 
-        print('[INFO] Exp {0} Scan {1} has a matched calibrated record from pre-processed data.')
+        if self._debugPrintMode:
+            print('[INFO] Exp {0} Scan {1} has a matched calibrated record from pre-processed data.')
 
         return True
 
+    def load_mask_file(self, mask_file_name, mask_tag):
+        """
+        load an XML mask file to a workspace and parse to ROI that can be mapped pixels in 2D notion
+        :param mask_file_name:
+        :param mask_tag
+        :return: 2-tuple (lower left corner (size = 2), upper right corner (size = 2))
+                both of them are in order of row and column number (y and x respectively)
+        """
+        # load mask file
+        assert isinstance(mask_file_name, str), 'Mask file {0} shall be a string but not a {1}.' \
+                                                ''.format(mask_file_name, type(mask_file_name))
+        assert isinstance(mask_tag, str), 'Mask tag {0} shall be a string but not a {1}.' \
+                                          ''.format(mask_tag, type(mask_tag))
+        if os.path.exists(mask_file_name) is False:
+            raise RuntimeError('Mask file name {0} cannot be found.'.format(mask_tag))
+
+        # load
+        mantidsimple.LoadMask(Instrument='HB3A',
+                              InputFile=mask_file_name,
+                              OutputWorkspace=mask_tag)
+        # record
+        self.set_roi_workspace(roi_name=mask_tag, mask_ws_name=mask_tag)
+
+        # find out the range of the ROI in (Low left, upper right) mode
+        roi_range = process_mask.get_region_of_interest(mask_tag)
+        self.set_roi(mask_tag, roi_range[0], roi_range[1])
+
+        return roi_range
+
     def load_preprocessed_scan(self, exp_number, scan_number, md_dir, output_ws_name):
         """ load preprocessed scan from hard disk
-        :return:
+        :return: (bool, str): loaded, message
         """
         # check inputs
         assert isinstance(exp_number, int), 'Experiment number {0} ({1}) must be an integer' \
@@ -1721,8 +1854,8 @@ class CWSCDReductionControl(object):
 
         # check
         if os.path.exists(md_file_path) is False:
-            print ('[WARNING] MD file {0} does not exist.'.format(md_file_path))
-            return False
+            message = 'Pre-processed MD file {0} does not exist.'.format(md_file_path)
+            return False, message
 
         # load and check
         status = False
@@ -1731,16 +1864,15 @@ class CWSCDReductionControl(object):
             mantidsimple.LoadMD(Filename=md_file_path, OutputWorkspace=output_ws_name)
             # check
             status = AnalysisDataService.doesExist(output_ws_name)
-            print ('[INFO] {0} is loaded from {1} with status {2}'
-                   ''.format(output_ws_name, md_file_path, status))
+            message = '{0} is loaded from {1} with status {2}'.format(output_ws_name, md_file_path, status)
         except RuntimeError as run_err:
-            print('[DB] Unable to load file {0} due to RuntimeError {1}.'.format(md_file_path, run_err))
+            message = 'Unable to load file {0} due to RuntimeError {1}.'.format(md_file_path, run_err)
         except OSError as run_err:
-            print('[DB] Unable to load file {0} due to OSError {1}.'.format(md_file_path, run_err))
+            message = 'Unable to load file {0} due to OSError {1}.'.format(md_file_path, run_err)
         except IOError as run_err:
-            print('[DB] Unable to load file {0} due to IOError {1}.'.format(md_file_path, run_err))
+            message = 'Unable to load file {0} due to IOError {1}.'.format(md_file_path, run_err)
 
-        return status
+        return status, message
 
     def _process_pt_list(self, exp_no, scan_no, pt_num_list):
         """
@@ -1804,51 +1936,22 @@ class CWSCDReductionControl(object):
             error_msg = ret_obj
             return False, error_msg
         pt_num_list, pt_list_str = ret_obj
-        # if len(pt_num_list) > 0:
-        #     # user specified
-        #     pt_num_list = pt_num_list
-        # else:
-        #     # default: all Pt. of scan
-        #     status, pt_num_list = self.get_pt_numbers(exp_no, scan_no)
-        #     if status is False:
-        #         err_msg = pt_num_list
-        #         return False, err_msg
-        # # END-IF-ELSE
-        #
-        # # construct a list of Pt as the input of CollectHB3AExperimentInfo
-        # pt_list_str = '-1'  # header
-        # err_msg = ''
-        # for pt in pt_num_list:
-        #     # Download file
-        #     try:
-        #         self.download_spice_xml_file(scan_no, pt, exp_no=exp_no, overwrite=False)
-        #     except RuntimeError as e:
-        #         err_msg += 'Unable to download xml file for pt %d due to %s\n' % (pt, str(e))
-        #         continue
-        #     pt_list_str += ',%d' % pt
-        # # END-FOR (pt)
-        # if pt_list_str == '-1':
-        #     return False, err_msg
 
         # create output workspace's name
         out_q_name = get_merged_md_name(self._instrumentName, exp_no, scan_no, pt_num_list)
 
         # find out the cases that rewriting is True
-        print ('[DB...BAT] Rewrite = {0}'.format(rewrite))
-
         if not rewrite:
-            print ('[DB...BAT] pre-processed dir: {0}'.format(preprocessed_dir))
-
             if AnalysisDataService.doesExist(out_q_name):
                 # not re-write, target workspace exists
                 pass
             elif preprocessed_dir is not None:
                 # not re-write, target workspace does not exist, attempt to load from preprocessed
                 if self.is_calibration_match(exp_no, scan_no):
-                    data_loaded = self.load_preprocessed_scan(exp_number=exp_no,
-                                                              scan_number=scan_no,
-                                                              md_dir=preprocessed_dir,
-                                                              output_ws_name=out_q_name)
+                    data_loaded, message = self.load_preprocessed_scan(exp_number=exp_no,
+                                                                       scan_number=scan_no,
+                                                                       md_dir=preprocessed_dir,
+                                                                       output_ws_name=out_q_name)
                     rewrite = not data_loaded
                 else:
                     rewrite = True
@@ -1993,19 +2096,59 @@ class CWSCDReductionControl(object):
 
         return
 
-    def set_roi(self, exp_number, scan_number, lower_left_corner, upper_right_corner):
+    def set_roi(self, roi_name, lower_left_corner, upper_right_corner):
         """
         Purpose: Set region of interest and record it by the combination of experiment number
                  and scan number
-        :param exp_number:
-        :param scan_number:
+        :param roi_name
         :param lower_left_corner:
         :param upper_right_corner:
         :return:
         """
         # Check
-        assert isinstance(exp_number, int)
-        assert isinstance(scan_number, int)
+        check_str_type(roi_name, 'ROI')
+        if len(lower_left_corner) != 2:
+            raise RuntimeError('Size of ROI[0] must be 2 but not {0}'
+                               ''.format(len(lower_left_corner[0])))
+        if len(upper_right_corner) != 2:
+            raise RuntimeError('Size of ROI[1] must be 2 but not {0}'
+                               ''.format(len(upper_right_corner)))
+
+        if roi_name not in self._roiDict:
+            self._roiDict[roi_name] = process_mask.RegionOfInterest(roi_name)
+
+        self._roiDict[roi_name].set_roi_positions(lower_left_corner, upper_right_corner)
+
+        return
+
+    def set_roi_workspace(self, roi_name, mask_ws_name):
+        """ set region of interest (mask) workspace's name to RegionOfInterest
+        instance
+        :param roi_name:
+        :return:
+        """
+        # check input
+        check_str_type(roi_name, 'ROI')
+        check_str_type(mask_ws_name, 'Mask workspace name')
+
+        # add new RegionOfInterest if needed
+        if roi_name not in self._roiDict:
+            self._roiDict[roi_name] = process_mask.RegionOfInterest(roi_name)
+
+        self._roiDict[roi_name].set_mask_workspace_name(mask_ws_name)
+
+        return
+
+    def set_roi_by_name(self, roi_name, lower_left_corner, upper_right_corner):
+        """
+        Set region of interest and record it by user-specified ROI name
+        :param roi_name:
+        :param lower_left_corner:
+        :param upper_right_corner:
+        :return:
+        """
+        assert isinstance(roi_name, str), 'ROI name {0} must be an integer but not a {1}' \
+                                          ''.format(roi_name, type(roi_name))
         assert not isinstance(lower_left_corner, str) and len(lower_left_corner) == 2
         assert not isinstance(upper_right_corner, str) and len(upper_right_corner) == 2
 
@@ -2013,14 +2156,15 @@ class CWSCDReductionControl(object):
         ll_y = int(lower_left_corner[1])
         ur_x = int(upper_right_corner[0])
         ur_y = int(upper_right_corner[1])
-        assert ll_x < ur_x and ll_y < ur_y, 'Lower left corner (%.5f, %.5f) vs. upper right corner ' \
-                                            '(%.5f, %.5f) ' % (ll_x, ll_y, ur_x, ur_y)
+        if ll_x >= ur_x or ll_y >= ur_y:
+            err_msg = 'Lower left corner ({0}, {1}) and upper right corner are in a line ({2}, {3})' \
+                      ''.format(ll_x, ll_y, ur_x, ur_y)
+            raise RuntimeError(err_msg)
 
         # Add to dictionary.  Because usually one ROI is defined for all scans in an experiment,
         # then it is better and easier to support client to search this ROI by experiment number
         # and only the latest is saved by this key
-        self._roiDict[(exp_number, scan_number)] = ((ll_x, ll_y), (ur_x, ur_y))
-        self._roiDict[exp_number] = ((ll_x, ll_y), (ur_x, ur_y))
+        self._roiDict[roi_name] = ((ll_x, ll_y), (ur_x, ur_y))
 
         return
 
@@ -2517,15 +2661,16 @@ class CWSCDReductionControl(object):
             file_name = '%s.csv' % file_name
 
         # Write file
-        titles = ['Max Counts', 'Scan', 'Max Counts Pt', 'H', 'K', 'L', 'Q']
-        with open(file_name, 'w') as csvfile:
-            csv_writer = csv.writer(csvfile, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
+        titles = ['Max Counts', 'Scan', 'Max Counts Pt', 'H', 'K', 'L', 'Q', 'Sample T']
+        with open(file_name, 'w') as csv_file:
+            csv_writer = csv.writer(csv_file, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
             csv_writer.writerow(titles)
 
-            for scan_summary in self._scanSummaryList:
+            for sum_index, scan_summary in enumerate(self._scanSummaryList):
                 # check type
-                assert isinstance(scan_summary, list)
-                assert len(scan_summary) == len(titles)
+                assert isinstance(scan_summary, list), 'TODO'
+                assert len(scan_summary) == len(titles), '{0}-th scan summary {1} must have same items as title {2}' \
+                                                         ''.format(sum_index, scan_summary, titles)
                 # write to csv
                 csv_writer.writerow(scan_summary)
             # END-FOR
@@ -2533,21 +2678,59 @@ class CWSCDReductionControl(object):
 
         return
 
-    def save_roi(self, tag, region_of_interest):
-        """
-        Save region of interest to controller for future use
+    # 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
+        Notice: the saved file is a mask file which masks the detectors out of ROI
+        :param exp_number:
+        :param scan_number:
         :param tag:
-        :param region_of_interest: a 2-tuple for 2-tuple as lower-left and upper-right corners of the region
+        :param file_name:
         :return:
         """
-        # check
-        assert isinstance(tag, str), 'Tag must be a string'
-        assert len(region_of_interest) == 2
-        assert len(region_of_interest[0]) == 2
-        assert len(region_of_interest[1]) == 2
+        # 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))
+
+        # get mask workspace name
+        if tag in self._roiDict:
+            mask_ws_name = self._roiDict[tag].mask_workspace
+            if mask_ws_name is None or AnalysisDataService.doesExist(mask_ws_name) is False:
+                raise RuntimeError('Mask workspace {0} of tag {1} does not exist in ADS.'
+                                   ''.format(mask_ws_name, tag))
+        else:
+            mask_ws_name = self.check_generate_mask_workspace(exp_number=exp_number, scan_number=scan_number,
+                                                              mask_tag=tag, check_throw=True)
 
-        # example:  ret_value = self._roiDict[exp_number]
-        self._roiDict[tag] = region_of_interest
+        # save
+        mantidsimple.SaveMask(InputWorkspace=mask_ws_name, OutputFile=file_name)
 
         return
 
@@ -2639,12 +2822,13 @@ class CWSCDReductionControl(object):
         # check whether there is a redundant creation of PeakProcessRecord for the same (exp, scan) combination
         if (exp_number, scan_number) in self._myPeakInfoDict:
             peak_info = self._myPeakInfoDict[(exp_number, scan_number)]
-            print('[ERROR] PeakProcessRecord for Exp {0} Scan {1} shall not '
-                  'be created twice!'.format(exp_number, scan_number))
-            print('[CONTINUE] New PeaksWorkspace = {0} vs Existing '
-                  'PeaksWorkspace = {1}.'.format(peak_ws_name, peak_info.peaks_workspace))
-            print('[CONTINUE] New MDEventWorkspace = {0} vs Existing '
-                  'MDEventWorkspace = {1}.'.format(md_ws_name, peak_info.md_workspace))
+            if self._debugPrintMode:
+                print('[ERROR] PeakProcessRecord for Exp {0} Scan {1} shall not '
+                      'be created twice!'.format(exp_number, scan_number))
+                print('[CONTINUE] New PeaksWorkspace = {0} vs Existing '
+                      'PeaksWorkspace = {1}.'.format(peak_ws_name, peak_info.peaks_workspace))
+                print('[CONTINUE] New MDEventWorkspace = {0} vs Existing '
+                      'MDEventWorkspace = {1}.'.format(md_ws_name, peak_info.md_workspace))
             return False, peak_info
         # END-IF
 
@@ -2654,7 +2838,9 @@ class CWSCDReductionControl(object):
 
         # set the other information
         peak_info.set_data_ws_name(md_ws_name)
-        peak_info.calculate_peak_center()
+        err_msg = peak_info.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
 
@@ -2789,9 +2975,9 @@ class CWSCDReductionControl(object):
                 try:
                     mantidsimple.DownloadFile(Address=spice_file_url, Filename=spice_file_name)
                 except RuntimeError as download_error:
-                    print('[ERROR] Unable to download scan %d from %s due to %s.' % (scan_number,spice_file_url,
-                                                                                     str(download_error)))
-                    break
+                    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)
@@ -2816,6 +3002,8 @@ class CWSCDReductionControl(object):
                 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')
@@ -2830,7 +3018,10 @@ class CWSCDReductionControl(object):
                 two_theta = m1 = -1
 
                 for i_row in range(num_rows):
-                    det_count = spice_table_ws.cell(i_row, 5)
+                    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
@@ -2850,7 +3041,7 @@ class CWSCDReductionControl(object):
                 wavelength = get_hb3a_wavelength(m1)
                 if wavelength is None:
                     q_range = 0.
-                    print('[ERROR] Scan number {0} has invalid m1 for wavelength.'.format(scan_number))
+                    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
 
@@ -2889,9 +3080,11 @@ class CWSCDReductionControl(object):
         project.set('data dir', self._dataDir)
         project.set('gui parameters', ui_dict)
 
-        project.export(overwrite=False)
+        err_msg = project.save_to_disk(overwrite=False)
+        if len(err_msg) == 0:
+            err_msg = None
 
-        return
+        return err_msg
 
     def load_project(self, project_file_name):
         """
@@ -2907,7 +3100,9 @@ class CWSCDReductionControl(object):
 
         # instantiate a project manager instance and load the project
         saved_project = project_manager.ProjectManager(mode='import', project_file_path=project_file_name)
-        saved_project.load()
+        err_msg = saved_project.load_from_disk()
+        if len(err_msg) == 0:
+            err_msg = None
 
         # set current value
         try:
@@ -2920,7 +3115,7 @@ class CWSCDReductionControl(object):
         except KeyError:
             ui_dict = dict()
 
-        return ui_dict
+        return ui_dict, err_msg
 
 
 def convert_spice_ub_to_mantid(spice_ub):
diff --git a/scripts/HFIR_4Circle_Reduction/reduce4circleGUI.py b/scripts/HFIR_4Circle_Reduction/reduce4circleGUI.py
index da275cd257860a593464a12e63b11551fb3f0a1c..230e56b5261cd6808af6f3e4d2b0283816d89172 100644
--- a/scripts/HFIR_4Circle_Reduction/reduce4circleGUI.py
+++ b/scripts/HFIR_4Circle_Reduction/reduce4circleGUI.py
@@ -14,11 +14,12 @@ import time
 import datetime
 import random
 import numpy
+from HFIR_4Circle_Reduction import reduce4circleControl as r4c
 import HFIR_4Circle_Reduction.guiutility as gutil
 import HFIR_4Circle_Reduction.peakprocesshelper as peak_util
 import HFIR_4Circle_Reduction.fourcircle_utility as hb3a_util
 from HFIR_4Circle_Reduction import plot3dwindow
-from HFIR_4Circle_Reduction.multi_threads_helpers import *
+import HFIR_4Circle_Reduction.multi_threads_helpers as thread_pool
 import HFIR_4Circle_Reduction.optimizelatticewindow as ol_window
 from HFIR_4Circle_Reduction import viewspicedialog
 from HFIR_4Circle_Reduction import peak_integration_utility
@@ -26,6 +27,9 @@ from HFIR_4Circle_Reduction import FindUBUtility
 from HFIR_4Circle_Reduction import message_dialog
 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 line for the UI python class
 from HFIR_4Circle_Reduction.ui_MainWindow import Ui_MainWindow
@@ -161,9 +165,9 @@ class MainWindow(QtGui.QMainWindow):
                      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_addROI, QtCore.SIGNAL('clicked()'),
-                     self.do_add_roi)
-        self.connect(self.ui.pushButton_cancelROI, QtCore.SIGNAL('clicked()'),
+        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)
@@ -171,10 +175,10 @@ class MainWindow(QtGui.QMainWindow):
                      self.do_plot_prev_scan)
         self.connect(self.ui.pushButton_maskScanPt, QtCore.SIGNAL('clicked()'),
                      self.do_mask_pt_2d)
-        self.connect(self.ui.pushButton_saveMask, QtCore.SIGNAL('clicked()'),
-                     self.do_save_roi)
         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)
 
         # Tab 'calculate ub matrix'
         self.connect(self.ui.pushButton_addUBScans, QtCore.SIGNAL('clicked()'),
@@ -287,6 +291,9 @@ class MainWindow(QtGui.QMainWindow):
         self.connect(self.ui.pushButton_clearPeakIntFigure, QtCore.SIGNAL('clicked()'),
                      self.do_clear_peak_integration_canvas)
 
+        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)
@@ -299,6 +306,8 @@ class MainWindow(QtGui.QMainWindow):
                      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.connect(self.ui.actionSave_Project, QtCore.SIGNAL('triggered()'),
                      self.action_save_project)
@@ -333,6 +342,7 @@ class MainWindow(QtGui.QMainWindow):
         self._dataAccessMode = 'Download'
         self._surveyTableFlag = True
         self._ubPeakTableFlag = True
+        self._isROIApplied = False
 
         # set the detector geometry
         self.do_set_detector_size()
@@ -347,6 +357,9 @@ class MainWindow(QtGui.QMainWindow):
         # QSettings
         self.load_settings()
 
+        # mutex interlock
+        self._roiComboBoxMutex = False
+
         return
 
     @property
@@ -409,6 +422,8 @@ class MainWindow(QtGui.QMainWindow):
         self.ui.comboBox_ptCountType.addItem('Monitor')
         self.ui.comboBox_ptCountType.addItem('Absolute')
 
+        self.ui.comboBox_viewRawDataMasks.addItem('')
+
         # tab
         self.ui.tabWidget.setCurrentIndex(0)
 
@@ -430,13 +445,19 @@ class MainWindow(QtGui.QMainWindow):
 
         # check boxes
         self.ui.graphicsView_detector2dPlot.set_parent_window(self)
+        self.ui.checkBox_fpHighPrecision.setChecked(True)
 
         # background points
         self.ui.lineEdit_backgroundPts.setText('1, 1')
+        self.ui.lineEdit_scaleFactor.setText('1.')
 
         # about pre-processed data
         self.ui.checkBox_searchPreprocessedFirst.setChecked(True)
 
+        # hide and disable some push buttons for future implemetation
+        self.ui.pushButton_viewScan3D.hide()
+        self.ui.pushButton_plotSelectedData.hide()
+
         return
 
     def _build_peak_info_list(self, zero_hkl, is_spice=True):
@@ -450,7 +471,7 @@ class MainWindow(QtGui.QMainWindow):
             err_msg = 'At least 3 peaks must be selected to refine UB matrix.' \
                       'Now it is only %d selected.' % len(row_index_list)
             self.pop_one_button_dialog(err_msg)
-            return
+            return None
 
         # loop over all peaks for peak information
         peak_info_list = list()
@@ -471,7 +492,12 @@ class MainWindow(QtGui.QMainWindow):
                 spice_hkl = self.ui.tableWidget_peaksCalUB.get_hkl(i_row, True)
                 peak_info.set_hkl_np_array(numpy.array(spice_hkl))
             else:
-                calculated_hkl = self.ui.tableWidget_peaksCalUB.get_hkl(i_row, False)
+                try:
+                    calculated_hkl = self.ui.tableWidget_peaksCalUB.get_hkl(i_row, False)
+                except RuntimeError as run_err:
+                    errmsg = '[ERROR] Failed to get calculated HKL from UB calcualtion table due to {0}'.format(run_err)
+                    self.pop_one_button_dialog(errmsg)
+                    return None
                 peak_info.set_hkl_np_array(numpy.array(calculated_hkl))
             # END-IF-ELSE
 
@@ -511,6 +537,17 @@ class MainWindow(QtGui.QMainWindow):
 
         return
 
+    def _show_message(self, message):
+        """
+        show message in the message bar while clearing previous information
+        :param message:
+        :return:
+        """
+        if message is not None:
+            self.ui.lineEdit_message.setText(message)
+
+        return
+
     def action_save_project(self):
         """
         Save project
@@ -523,11 +560,12 @@ class MainWindow(QtGui.QMainWindow):
             yes = gutil.show_message(self, 'Project file %s does exist. This is supposed to be '
                                            'an incremental save.' % project_file_name)
             if yes:
-                print('[INFO] Save project in incremental way.')
+                message = 'Save project to {0} in incremental way.'.format(project_file_name)
             else:
-                print('[INFO] Saving activity is cancelled.')
+                message = 'Saving activity is cancelled.'
         else:
-            print('[INFO] Saving current project to %s.' % project_file_name)
+            message = 'Saving current project to {0}'.format(project_file_name)
+        self._show_message(message)
 
         # gather some useful information
         ui_dict = dict()
@@ -560,13 +598,14 @@ class MainWindow(QtGui.QMainWindow):
             self.ui.label_last1Path.setText(project_file_name)
         # END-IF
 
-        self._myControl.save_project(project_file_name, ui_dict)
+        err_msg = self._myControl.save_project(project_file_name, ui_dict)
+        if err_msg is not None:
+            self.pop_one_button_dialog(err_msg)
 
         # show user the message that the saving process is over
-        information = 'Project has been saved to {0}\n'.format(project_file_name),
+        information = 'Project has been saved to {0}\n'.format(project_file_name)
         information += 'Including dictionary keys: {0}'.format(ui_dict)
         self.pop_one_button_dialog(information)
-        print('[INFO]\n{0}'.format(information))
 
         return
 
@@ -600,21 +639,9 @@ class MainWindow(QtGui.QMainWindow):
         project_file_name = str(self.ui.label_last1Path.text())
         if os.path.exists(project_file_name) is False:
             self.pop_one_button_dialog('Last saved project %s cannot be located.' % project_file_name)
+            return
         else:
-            ui_dict = self._myControl.load_project(project_file_name)
-
-            # set the UI parameters to GUI
-            try:
-                self.ui.lineEdit_localSpiceDir.setText(ui_dict['local spice dir'])
-                self.ui.lineEdit_workDir.setText(ui_dict['work dir'])
-                self.ui.lineEdit_surveyStartPt.setText(ui_dict['survey start'])
-                self.ui.lineEdit_surveyEndPt.setText(ui_dict['survey stop'])
-
-                # now try to call some actions
-                self.do_apply_setup()
-                self.do_set_experiment()
-            except KeyError:
-                print('[Error] Some field cannot be found.')
+            self.load_project(project_file_name)
 
         return
 
@@ -668,7 +695,7 @@ class MainWindow(QtGui.QMainWindow):
 
         # prototype for a new thread
         self.ui.progressBar_add_ub_peaks.setRange(0, len(scan_number_list))
-        self._addUBPeaksThread = AddPeaksThread(self, exp_number, scan_number_list)
+        self._addUBPeaksThread = thread_pool.AddPeaksThread(self, exp_number, scan_number_list)
         self._addUBPeaksThread.start()
 
         # set the flag/notification where the indexing (HKL) from
@@ -677,8 +704,7 @@ class MainWindow(QtGui.QMainWindow):
         return
 
     def add_scans_ub_table(self, scan_list):
-        """
-
+        """ add scans to UB matrix construction table
         :param scan_list:
         :return:
         """
@@ -694,7 +720,7 @@ class MainWindow(QtGui.QMainWindow):
 
         # prototype for a new thread
         self.ui.progressBar_add_ub_peaks.setRange(0, len(scan_list))
-        self._addUBPeaksThread = AddPeaksThread(self, exp_number, scan_list)
+        self._addUBPeaksThread = thread_pool.AddPeaksThread(self, exp_number, scan_list)
         self._addUBPeaksThread.start()
 
         # set the flag/notification where the indexing (HKL) from
@@ -702,21 +728,21 @@ class MainWindow(QtGui.QMainWindow):
 
         return
 
-    def do_add_roi(self):
+    def do_switch_roi_mode(self):
         """ Add region of interest to 2D image
         :return:
         """
         # set the button to next mode
-        if str(self.ui.pushButton_addROI.text()) == 'Edit ROI':
+        if str(self.ui.pushButton_switchROIMode.text()) == 'Enter ROI-Edit Mode':
             # enter adding ROI mode
             self.ui.graphicsView_detector2dPlot.enter_roi_mode(state=True)
             # rename the button
-            self.ui.pushButton_addROI.setText('Quit ROI')
+            self.ui.pushButton_switchROIMode.setText('Quit ROI-Edit Mode')
         else:
             # quit editing ROI mode
             self.ui.graphicsView_detector2dPlot.enter_roi_mode(state=False)
             # rename the button
-            self.ui.pushButton_addROI.setText('Edit ROI')
+            self.ui.pushButton_switchROIMode.setText('Enter ROI-Edit Mode')
         # END-IF-ELSE
 
         return
@@ -847,27 +873,6 @@ class MainWindow(QtGui.QMainWindow):
 
         return
 
-    def do_apply_roi(self):
-        """ Save current selection of region of interest
-        :return:
-        """
-        lower_left_c, upper_right_c = self.ui.graphicsView_detector2dPlot.get_roi()
-        # at the very beginning, the lower left and upper right are same
-        if lower_left_c[0] == upper_right_c[0] or lower_left_c[1] == upper_right_c[1]:
-            return
-
-        status, par_val_list = gutil.parse_integers_editors([self.ui.lineEdit_exp, self.ui.lineEdit_run])
-        assert status, str(par_val_list)
-        exp_number = par_val_list[0]
-        scan_number = par_val_list[1]
-
-        try:
-            self._myControl.set_roi(exp_number, scan_number, lower_left_c, upper_right_c)
-        except AssertionError as ass_err:
-            print('[ERROR] Unable to set ROI due to {0}.'.format(ass_err))
-
-        return
-
     def do_apply_setup(self):
         """
         Purpose:
@@ -925,7 +930,8 @@ class MainWindow(QtGui.QMainWindow):
         # preprocess directory
         if len(pre_process_dir) == 0:
             # user does not specify
-            self._myControl.pre_processed_dir = None
+            pass
+        # It is not allowed to set pre-processed dir to None: self._myControl.pre_processed_dir = None
         elif os.path.exists(pre_process_dir):
             # user specifies a valid directory
             self._myControl.pre_processed_dir = pre_process_dir
@@ -1126,10 +1132,25 @@ class MainWindow(QtGui.QMainWindow):
         return
 
     def do_del_roi(self):
-        """ Delete ROI
+        """ Remove the current ROI
         :return:
         """
-        self.ui.graphicsView_detector2dPlot.remove_roi()
+        # check whether there is mask on detector view
+        if self.ui.graphicsView_detector2dPlot.is_roi_selection_drawn:
+            self.ui.graphicsView_detector2dPlot.remove_roi()
+
+        # need to draw again?
+        if self._isROIApplied:
+            re_plot = True
+        else:
+            re_plot = False
+
+        # need to reset combo box index
+        curr_index = self.ui.comboBox_viewRawDataMasks.currentIndex()
+        if curr_index != 0:
+            self.ui.comboBox_viewRawDataMasks.setCurrentIndex(0)
+        elif re_plot:
+            self.do_plot_pt_raw()
 
         return
 
@@ -1192,6 +1213,29 @@ class MainWindow(QtGui.QMainWindow):
 
         return hkl, vec_q
 
+    def do_export_mask(self):
+        """
+        export selected mask to file
+        :return:
+        """
+        # get selected mask name
+        mask_name = str(self.ui.comboBox_viewRawDataMasks.currentText())
+        if mask_name == 'No Mask':
+            self.pop_one_button_dialog('No mask is selected.  Saving is aborted.')
+            return
+
+        # get the output file name
+        roi_file_name = str(QtGui.QFileDialog.getSaveFileName(self, 'Output mask/ROI file name',
+                                                              self._myControl._workDir,
+                                                              'XML Files (*.xml);;All Files (*.*)'))
+        if len(roi_file_name) == 0:
+            return
+
+        # save file
+        self._myControl.save_roi_to_file(None, None, mask_name, roi_file_name)
+
+        return
+
     def do_export_selected_peaks_to_integrate(self):
         """
         export (to file or just print out) the scans that are selected for integration
@@ -1205,10 +1249,8 @@ class MainWindow(QtGui.QMainWindow):
             scan_number_list.append(tup[0])
         scan_number_list.sort()
 
-        info_str = '# Selected scans: \n'
-        info_str += '{0}'.format(scan_number_list)
-
-        print('[TEMP] Selected scans:\n{0}'.format(info_str))
+        info_str = '{0}'.format(scan_number_list)
+        self._show_message('Selected scans: {0}'.format(info_str))
 
         return
 
@@ -1241,7 +1283,8 @@ class MainWindow(QtGui.QMainWindow):
             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)
+                                                                      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
@@ -1384,7 +1427,6 @@ class MainWindow(QtGui.QMainWindow):
         # plot calculated motor position (or Pt.) - integrated intensity per Pts.
         motor_pos_vec = int_peak_dict['motor positions']
         pt_intensity_vec = int_peak_dict['pt intensities']
-        # print('[DB...BAT] motor position vector: {0} of type {1}'.format(motor_pos_vec, type(motor_pos_vec)))
         motor_std = motor_pos_vec.std()
         if motor_std > 0.005:
             self.ui.graphicsView_integratedPeakView.plot_raw_data(motor_pos_vec, pt_intensity_vec)
@@ -1429,7 +1471,7 @@ class MainWindow(QtGui.QMainWindow):
             raise RuntimeError('Peak integration result dictionary has keys {0}. Error is caused by {1}.'
                                ''.format(int_peak_dict.keys(), key_err))
         except ValueError as value_err:
-            print('[ERROR] Unable to fit by Gaussian due to {0}.'.format(value_err))
+            self._show_message('[ERROR] Unable to fit by Gaussian due to {0}.'.format(value_err))
         else:
             self.plot_model_data(motor_pos_vec, fit_gauss_dict)
 
@@ -1570,9 +1612,15 @@ class MainWindow(QtGui.QMainWindow):
         num_pt_bg_left = ret_obj[0]
         num_pt_bg_right = ret_obj[1]
 
-        self._myIntegratePeaksThread = IntegratePeaksThread(self, exp_number, scan_number_list,
-                                                            mask_det, selected_mask, norm_type,
-                                                            num_pt_bg_left, num_pt_bg_right)
+        # scale factor:
+        scale_factor = float(self.ui.lineEdit_scaleFactor.text())
+
+        # initialize a thread and start
+        self._myIntegratePeaksThread = \
+            thread_pool.IntegratePeaksThread(self, exp_number, scan_number_list,
+                                             mask_det, selected_mask, norm_type,
+                                             num_pt_bg_left, num_pt_bg_right,
+                                             scale_factor=scale_factor)
         self._myIntegratePeaksThread.start()
 
         return
@@ -1583,7 +1631,7 @@ class MainWindow(QtGui.QMainWindow):
         """
         # Get UB matrix
         ub_matrix = self.ui.tableWidget_ubMatrix.get_matrix()
-        print('[Info] Get UB matrix from table ', ub_matrix)
+        self._show_message('[Info] Get UB matrix from table: {0}'.format(ub_matrix))
 
         # Index all peaks
         num_peaks = self.ui.tableWidget_peaksCalUB.rowCount()
@@ -1674,20 +1722,17 @@ class MainWindow(QtGui.QMainWindow):
             self.pop_one_button_dialog(ret_obj)
             return
 
-        # Call to plot 2D
-        self._plot_raw_xml_2d(exp_no, scan_no, pt_no)
-
-        return
+        if self.ui.checkBox_autoMask.isChecked():
+            roi_index = self.ui.comboBox_viewRawDataMasks.currentIndex()
+            if roi_index == 0:
+                roi_name = None
+            else:
+                roi_name = str(self.ui.comboBox_viewRawDataMasks.currentText())
+        else:
+            # if auto-mask flag is off, then no need to mask data
+            roi_name = None
 
-    def evt_change_normalization(self):
-        """
-        Integrate Pt. vs integrated intensity of detectors of that Pt. if it is not calculated before
-        and then plot pt vs. integrated intensity on
-        :return:
-        """
-        # integrate any how
-        # self.do_integrate_per_pt()
-        self.do_integrate_single_scan()
+        self.load_plot_raw_data(exp_no, scan_no, pt_no, roi_name=roi_name)
 
         return
 
@@ -1699,8 +1744,6 @@ class MainWindow(QtGui.QMainWindow):
                                                         self.ui.lineEdit_run,
                                                         self.ui.lineEdit_rawDataPtNo])
         if status is True:
-            exp_no = ret_obj[0]
-            scan_no = ret_obj[1]
             pt_no = ret_obj[2]
         else:
             self.pop_one_button_dialog(ret_obj)
@@ -1715,7 +1758,7 @@ class MainWindow(QtGui.QMainWindow):
             self.ui.lineEdit_rawDataPtNo.setText('%d' % pt_no)
 
         # Plot
-        self._plot_raw_xml_2d(exp_no, scan_no, pt_no)
+        self.do_plot_pt_raw()
 
         return
 
@@ -1724,15 +1767,13 @@ class MainWindow(QtGui.QMainWindow):
         :return:
         """
         # get current exp number, scan number and pt number
-        status, ret_obj = gutil.parse_integers_editors([self.ui.lineEdit_exp,
-                                                        self.ui.lineEdit_run,
-                                                        self.ui.lineEdit_rawDataPtNo])
+        status, ret_obj = gutil.parse_integers_editors([self.ui.lineEdit_run])
         if status is False:
             error_msg = ret_obj
             self.pop_one_button_dialog(error_msg)
             return
 
-        exp_number, scan_number, pt_number = ret_obj
+        scan_number = ret_obj[0]
 
         # get next scan
         scan_number -= 1
@@ -1740,17 +1781,12 @@ class MainWindow(QtGui.QMainWindow):
             self.pop_one_button_dialog('Scan number cannot be negative!')
             return
 
-        # plot
-        try:
-            self._plot_raw_xml_2d(exp_number, scan_number, pt_number)
-        except RuntimeError as err:
-            error_msg = 'Unable to plot next scan %d due to %s.' % (scan_number, str(err))
-            self.pop_one_button_dialog(error_msg)
-            return
-
         # update line edits
         self.ui.lineEdit_run.setText(str(scan_number))
 
+        #
+        self.do_plot_pt_raw()
+
         return
 
     def do_plot_next_pt_raw(self):
@@ -1760,7 +1796,7 @@ class MainWindow(QtGui.QMainWindow):
         status, ret_obj = gutil.parse_integers_editors([self.ui.lineEdit_exp,
                                                         self.ui.lineEdit_run,
                                                         self.ui.lineEdit_rawDataPtNo])
-        if status is True:
+        if status:
             exp_no = ret_obj[0]
             scan_no = ret_obj[1]
             pt_no = ret_obj[2]
@@ -1783,7 +1819,9 @@ class MainWindow(QtGui.QMainWindow):
             self.ui.lineEdit_rawDataPtNo.setText('%d' % pt_no)
 
         # Plot
-        self._plot_raw_xml_2d(exp_no, scan_no, pt_no)
+        self.do_plot_pt_raw()
+
+        # self.load_plot_raw_data(exp_no, scan_no, pt_no)
 
         return
 
@@ -1792,26 +1830,26 @@ class MainWindow(QtGui.QMainWindow):
         :return:
         """
         # get current exp number, scan number and pt number
-        status, ret_obj = gutil.parse_integers_editors([self.ui.lineEdit_exp,
-                                                        self.ui.lineEdit_run,
-                                                        self.ui.lineEdit_rawDataPtNo])
+        status, ret_obj = gutil.parse_integers_editors([self.ui.lineEdit_run])
         if status is False:
             error_msg = ret_obj
             self.pop_one_button_dialog(error_msg)
             return
 
-        exp_number, scan_number, pt_number = ret_obj
+        scan_number = ret_obj[0]
 
         # get next scan
         scan_number += 1
 
+        self.do_plot_pt_raw()
+
         # plot
-        try:
-            self._plot_raw_xml_2d(exp_number, scan_number, pt_number)
-        except RuntimeError as err:
-            error_msg = 'Unable to plot next scan %d due to %s.' % (scan_number, str(err))
-            self.pop_one_button_dialog(error_msg)
-            return
+        # try:
+        #     self.load_plot_raw_data(exp_number, scan_number, pt_number)
+        # except RuntimeError as err:
+        #     error_msg = 'Unable to plot next scan %d due to %s.' % (scan_number, str(err))
+        #     self.pop_one_button_dialog(error_msg)
+        #     return
 
         # update line edits
         self.ui.lineEdit_run.setText(str(scan_number))
@@ -1839,9 +1877,42 @@ class MainWindow(QtGui.QMainWindow):
         return
 
     def do_mask_pt_2d(self):
-        """ Mask a Pt and re-plot
+        """ Save current in-edit ROI Mask a Pt and re-plot with current selected ROI or others
         :return:
         """
+        # # get the experiment and scan value
+        # status, par_val_list = gutil.parse_integers_editors([self.ui.lineEdit_exp, self.ui.lineEdit_run])
+        # if not status:
+        #     raise RuntimeError('Experiment number and Scan number must be given!')
+        # exp_number = par_val_list[0]
+        # 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:')
+
+        # 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
+
+        # get current ROI
+        ll_corner, ur_corner = self.ui.graphicsView_detector2dPlot.get_roi()
+
+        # set ROI
+        self._myControl.set_roi(roi_name, ll_corner, ur_corner)
+
+        # set it to combo-box
+        self._roiComboBoxMutex = True
+        self.ui.comboBox_maskNames1.addItem(roi_name)
+        self.ui.comboBox_maskNames2.addItem(roi_name)
+        self.ui.comboBox_maskNamesSurvey.addItem(roi_name)
+        self.ui.comboBox_viewRawDataMasks.addItem(roi_name)
+        self.ui.comboBox_viewRawDataMasks.setCurrentIndex(self.ui.comboBox_viewRawDataMasks.count()-1)
+        self._roiComboBoxMutex = False
+
         # get experiment, scan
         status, ret_obj = gutil.parse_integers_editors([self.ui.lineEdit_exp, self.ui.lineEdit_run,
                                                         self.ui.lineEdit_rawDataPtNo],
@@ -1852,27 +1923,21 @@ class MainWindow(QtGui.QMainWindow):
             self.pop_one_button_dialog(ret_obj)
             return
 
-        # get the mask
-        status, ret_obj = self._myControl.get_region_of_interest(exp, scan)
-        if status is False:
-            # unable to get region of interest
-            self.pop_one_button_dialog(ret_obj)
-            return
-        else:
-            corner1, corner2 = ret_obj
-
-        # create mask workspace
-        status, error = self._myControl.generate_mask_workspace(exp, scan, corner1, corner2)
-        if status is False:
-            self.pop_one_button_dialog(error)
-            return
-
-        # re-load data file and mask
-        self._myControl.load_spice_xml_file(exp, scan, pt)
-        self._myControl.apply_mask(exp, scan, pt)
+        # previously saved ROI
+        if self._myControl.has_roi_generated(roi_name) is False:
+            roi_start, roi_end = self._myControl.get_region_of_interest(roi_name)
+            status, mask_ws_name = self._myControl.generate_mask_workspace(exp, scan,
+                                                                           roi_start=roi_start, roi_end=roi_end,
+                                                                           mask_tag=roi_name)
+            if status:
+                self._myControl.set_roi_workspace(roi_name, mask_ws_name)
+        # END-IF
 
         # plot
-        self._plot_raw_xml_2d(exp, scan, pt)
+        self.load_plot_raw_data(exp, scan, pt, roi_name=roi_name)
+
+        # switch ROI edit mode
+        self.do_switch_roi_mode()
 
         return
 
@@ -2011,6 +2076,8 @@ class MainWindow(QtGui.QMainWindow):
         """
         # refine UB matrix by indexed peak
         peak_info_list = self._build_peak_info_list(zero_hkl=False)
+        if peak_info_list is None:
+            return
 
         # Refine UB matrix
         try:
@@ -2031,6 +2098,8 @@ class MainWindow(QtGui.QMainWindow):
         """
         # refine UB matrix by indexed peak
         peak_info_list = self._build_peak_info_list(zero_hkl=False, is_spice=False)
+        if peak_info_list is None:
+            return
 
         # Refine UB matrix
         try:
@@ -2069,7 +2138,7 @@ class MainWindow(QtGui.QMainWindow):
         assert os.path.exists(project_file_name), 'Project file "%s" cannot be found.' % project_file_name
 
         # load project
-        ui_dict = self._myControl.load_project(project_file_name)
+        ui_dict, err_msg = self._myControl.load_project(project_file_name)
 
         # get experiment number and IPTS number
         exp_number = int(ui_dict['exp number'])
@@ -2089,7 +2158,7 @@ class MainWindow(QtGui.QMainWindow):
             self.do_set_experiment()
 
         except KeyError:
-            print('[Error] Some field cannot be found.')
+            self._show_message('[Error] Some field cannot be found from project file {0}'.format(project_file_name))
 
         # set experiment configurations
         # set sample distance
@@ -2113,9 +2182,11 @@ class MainWindow(QtGui.QMainWindow):
             self._myControl.set_detector_center(exp_number, center_row, center_col)
 
         # pop out a dialog to notify the completion
+        if err_msg is not None:
+            self.pop_one_button_dialog('Encountered these errors from loading:\n{0}'.format(err_msg))
+
         message = 'Project from file {0} is loaded.'.format(project_file_name)
         self.pop_one_button_dialog(message)
-        print('[INFO] {0}'.format(message))
 
         return
 
@@ -2139,14 +2210,16 @@ class MainWindow(QtGui.QMainWindow):
 
         # get peak information list
         peak_info_list = self._build_peak_info_list(zero_hkl=False)
+        if peak_info_list is None:
+            return
 
         # get the UB matrix value
         ub_src_tab = self._refineConfigWindow.get_ub_source()
         if ub_src_tab == 3:
-            print('[INFO] UB matrix comes from tab "Calculate UB".')
+            self._show_message('UB matrix comes from tab "Calculate UB".')
             ub_matrix = self.ui.tableWidget_ubMatrix.get_matrix_str()
         elif ub_src_tab == 4:
-            print('[INFO] UB matrix comes from tab "UB Matrix".')
+            self._show_message('UB matrix comes from tab "UB Matrix".')
             ub_matrix = self.ui.tableWidget_ubInUse.get_matrix_str()
         else:
             self.pop_one_button_dialog('UB source tab %s is not supported.' % str(ub_src_tab))
@@ -2167,8 +2240,6 @@ class MainWindow(QtGui.QMainWindow):
         Refine UB matrix by calling FFT method
         :return:
         """
-        import refineubfftsetup
-
         dlg = refineubfftsetup.RefineUBFFTSetupDialog(self)
         if dlg.exec_():
             # Do stuff with values
@@ -2185,6 +2256,8 @@ class MainWindow(QtGui.QMainWindow):
 
         # get PeakInfo list and check
         peak_info_list = self._build_peak_info_list(zero_hkl=True)
+        if peak_info_list is None:
+            return
         assert isinstance(peak_info_list, list), \
             'PeakInfo list must be a list but not %s.' % str(type(peak_info_list))
         assert len(peak_info_list) >= 3, \
@@ -2262,37 +2335,6 @@ class MainWindow(QtGui.QMainWindow):
 
         return
 
-    def do_save_roi(self):
-        """
-        Save region of interest to a specific name and reflects in combo boxes for future use,
-        especially used as a general ROI for multiple scans
-        :return:
-        """
-        # get the experiment and scan value
-        status, par_val_list = gutil.parse_integers_editors([self.ui.lineEdit_exp, self.ui.lineEdit_run])
-        assert status
-        exp_number = par_val_list[0]
-        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:')
-
-        # return if cancelled
-        if not ok:
-            return
-
-        # get current ROI
-        status, roi = self._myControl.get_region_of_interest(exp_number=exp_number, scan_number=scan_number)
-        assert status, str(roi)
-        roi_name = str(roi_name)
-        self._myControl.save_roi(roi_name, roi)
-
-        # set it to combo-box
-        self.ui.comboBox_maskNames1.addItem(roi_name)
-        self.ui.comboBox_maskNames2.addItem(roi_name)
-
-        return
-
     def do_save_survey(self):
         """
         Save the survey to a file
@@ -2591,6 +2633,7 @@ class MainWindow(QtGui.QMainWindow):
         else:
             integrate_type = 'simple'
 
+        err_msg = ''
         for i_row in range(self.ui.tableWidget_mergeScans.rowCount()):
             scan_number = self.ui.tableWidget_mergeScans.get_scan_number(i_row)
             peak_info_obj = self._myControl.get_peak_info(exp_number, scan_number)
@@ -2599,10 +2642,12 @@ class MainWindow(QtGui.QMainWindow):
                 intensity2, error2 = peak_info_obj.get_intensity(integrate_type, True)
                 self.ui.tableWidget_mergeScans.set_peak_intensity(i_row, intensity1, intensity2, error2, integrate_type)
             except RuntimeError as run_err:
-                print('[ERROR] Unable to get peak intensity of scan'
-                      ' {0} due to {1}.'.format(self.ui.tableWidget_mergeScans.get_scan_number(i_row), run_err))
+                err_msg += '{0} due to {1};'.format(self.ui.tableWidget_mergeScans.get_scan_number(i_row), run_err)
         # END-FOR
 
+        if len(err_msg) > 0:
+            self._show_message('Unable to get peak intensity of scan: {0}'.format(err_msg))
+
         return
 
     def do_undo_ub_tab_hkl_to_integers(self):
@@ -2654,8 +2699,8 @@ class MainWindow(QtGui.QMainWindow):
                 try:
                     ub_matrix = self._myControl.get_ub_matrix(exp_number)
                 except KeyError as key_err:
-                    print('[Error] unable to get UB matrix: %s' % str(key_err))
-                    self.pop_one_button_dialog('Unable to get UB matrix.\nCheck whether UB matrix is set.')
+                    self.pop_one_button_dialog('Unable to get UB matrix due to {0}.\nCheck whether UB matrix is set.'
+                                               ''.format(key_err))
                     return
                 index_status, ret_tup = self._myControl.index_peak(ub_matrix, scan_i, allow_magnetic=True)
                 if index_status:
@@ -2821,8 +2866,6 @@ class MainWindow(QtGui.QMainWindow):
         show the details (in table) about the integration of scans
         :return:
         """
-        import PeaksIntegrationReport
-
         # check whether the integration information table
         if self._peakIntegrationInfoWindow is None:
             self._peakIntegrationInfoWindow = PeaksIntegrationReport.PeaksIntegrationReportDialog(self)
@@ -3074,15 +3117,19 @@ class MainWindow(QtGui.QMainWindow):
         :return:
         """
         # get experiment and scan number
-        scan_number = self.ui.tableWidget_peaksCalUB.get_selected_scans()
-        status, ret_obj = gutil.parse_integers_editors([self.ui.lineEdit_exp,
-                                                        self.ui.lineEdit_scanNumber])
+        status, ret_obj = gutil.parse_integers_editors([self.ui.lineEdit_exp])
         if status:
             exp_number = ret_obj[0]
-            scan_number = ret_obj[1]
         else:
             self.pop_one_button_dialog(ret_obj)
             return
+        scan_number_list = self.ui.tableWidget_peaksCalUB.get_selected_scans()
+        if len(scan_number_list) != 1:
+            self.pop_one_button_dialog('To view scan data in 3D, one and only one scan can be selected.'
+                                       'Now there are {0} scans that are selected.'.format(len(scan_number_list)))
+            return
+        else:
+            scan_number = scan_number_list[0]
 
         # Check
         if self._myControl.has_merged_data(exp_number, scan_number) is False:
@@ -3098,7 +3145,7 @@ class MainWindow(QtGui.QMainWindow):
         if self._my3DWindow is None:
             self._my3DWindow = plot3dwindow.Plot3DWindow(self)
 
-        print('[INFO] Write file to %s' % md_file_name)
+        # print('[INFO] Write file to %s' % md_file_name)
         self._my3DWindow.add_plot_by_file(md_file_name)
         self._my3DWindow.add_plot_by_array(weight_peak_centers, weight_peak_intensities)
         self._my3DWindow.add_plot_by_array(avg_peak_centre, avg_peak_intensity)
@@ -3199,6 +3246,88 @@ class MainWindow(QtGui.QMainWindow):
 
         return
 
+    def evt_change_norm_type(self):
+        """
+        handling the event that the detector counts normalization method is changed
+        :return:
+        """
+        # read the current normalization type
+        new_norm_type = str(self.ui.comboBox_mergePeakNormType.currentText()).lower()
+        print ('[DB...BAT] Change Norm Type is Triggered.  Type: {0}'.format(new_norm_type))
+
+        # set the scale factor according to new norm type
+        if new_norm_type.count('time') > 0:
+            scale_factor = 1.
+        elif new_norm_type.count('monitor') > 0:
+            scale_factor = 1000.
+        else:
+            scale_factor = 1.
+
+        self.ui.lineEdit_scaleFactor.setText('{0}'.format(scale_factor))
+
+        return
+
+    def evt_change_normalization(self):
+        """
+        Integrate Pt. vs integrated intensity of detectors of that Pt. if it is not calculated before
+        and then plot pt vs. integrated intensity on
+        :return:
+        """
+        # integrate any how
+        # self.do_integrate_per_pt()
+        self.do_integrate_single_scan()
+
+        return
+
+    def evt_change_roi(self):
+        """ handing event of ROI selected in combobox is changed
+        :return:
+        """
+        if self._roiComboBoxMutex:
+            return
+
+        # get target ROI name
+        curr_roi_name = str(self.ui.comboBox_viewRawDataMasks.currentText()).strip()
+
+        # set to 'no ROI', i.e., removing ROI and plot data without mask
+        self.ui.graphicsView_detector2dPlot.remove_roi()
+
+        if len(curr_roi_name) == 0:
+            # re-plot
+            self.do_plot_pt_raw()
+
+        else:
+            # set to another ROI
+            self.do_plot_pt_raw()
+
+            if self.ui.graphicsView_detector2dPlot.is_roi_selection_drawn is False:
+                # shall apply ROI/rectangular to 2D plot
+                lower_left, upper_right = self._myControl.get_region_of_interest(curr_roi_name)
+                self.ui.graphicsView_detector2dPlot.set_roi(lower_left, upper_right)
+                self.ui.graphicsView_detector2dPlot.plot_roi()
+            # END-IF
+
+        # END-IF-ELSE
+
+        return
+
+    def evt_new_roi(self, lower_left_x, lower_left_y, upper_right_x, upper_right_y):
+        """
+        handling event that a new ROI is defined
+        :param lower_left_x:
+        :param lower_left_y:
+        :param upper_right_x:
+        :param upper_right_y:
+        :return:
+        """
+        # a new ROI is defined so combo box is set to item 0
+        self.ui.comboBox_viewRawDataMasks.setCurrentIndex(0)
+
+        self.ui.lineEdit_message.setText('New selected ROI: ({0}, {1}), ({2}, {3})'
+                                         ''.format(lower_left_x, lower_left_y, upper_right_x, upper_right_y))
+
+        return
+
     def evt_show_survey(self):
         """
         Show survey result
@@ -3233,13 +3362,11 @@ class MainWindow(QtGui.QMainWindow):
 
         # collection all the information
         report_dict = dict()
-        print('[DB] Selected rows: {0}'.format(row_number_list))
         for row_number in row_number_list:
             scan_number = self.ui.tableWidget_mergeScans.get_scan_number(row_number)
             peak_info = self._myControl.get_peak_info(exp_number, scan_number)
             peak_integrate_dict = peak_info.generate_integration_report()
             report_dict[scan_number] = peak_integrate_dict
-            print('[DB] Report Scan {0}. Keys: {1}'.format(scan_number, peak_integrate_dict.keys()))
         # END-FOR
 
         return report_dict
@@ -3312,9 +3439,8 @@ class MainWindow(QtGui.QMainWindow):
         # set the experiment
         self._myControl.set_local_data_dir(str(self.ui.lineEdit_localSpiceDir.text()))
         self._myControl.set_working_directory(str(self.ui.lineEdit_workDir.text()))
-        self._myControl.set_server_url(str(self.ui.lineEdit_url.text()))
 
-        print('[INFO] Session {0} has been loaded.'.format(filename))
+        self._show_message('Session {0} has been loaded.'.format(filename))
 
         return
 
@@ -3350,7 +3476,6 @@ class MainWindow(QtGui.QMainWindow):
 
         # Setup
         save_dict['lineEdit_localSpiceDir'] = str(self.ui.lineEdit_localSpiceDir.text())
-        save_dict['lineEdit_url'] = str(self.ui.lineEdit_url.text())
         save_dict['lineEdit_workDir'] = str(self.ui.lineEdit_workDir.text())
 
         # Experiment
@@ -3401,6 +3526,37 @@ class MainWindow(QtGui.QMainWindow):
 
         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',
+                                                               self._myControl.get_working_directory(),
+                                                               file_filter))
+
+        # generate a mask name and load by calling controller to load mask XML
+        roi_name = os.path.basename(mask_file_name).split('.')[0]
+        lower_left_corner, upper_right_corner = self._myControl.load_mask_file(mask_file_name, roi_name)
+
+        # set UI
+        self.ui.comboBox_maskNames1.addItem(roi_name)
+        self.ui.comboBox_maskNames2.addItem(roi_name)
+        self.ui.comboBox_maskNamesSurvey.addItem(roi_name)
+        self.ui.comboBox_maskNamesSurvey.setCurrentIndex(self.ui.comboBox_maskNames1.count() - 1)
+        self.ui.comboBox_viewRawDataMasks.addItem(roi_name)
+        self.ui.comboBox_viewRawDataMasks.setCurrentIndex(self.ui.comboBox_viewRawDataMasks.count() - 1)
+
+        # set ROI to controller
+        self._myControl.set_roi(roi_name, lower_left_corner, upper_right_corner)
+
+        # plot ROI on 2D plot
+        self.ui.graphicsView_detector2dPlot.remove_roi()
+        self.ui.graphicsView_detector2dPlot.set_roi(lower_left_corner, upper_right_corner)
+
+        return
+
     def menu_quit(self):
         """
 
@@ -3571,6 +3727,12 @@ class MainWindow(QtGui.QMainWindow):
         last_1_project_path = str(self.ui.label_last1Path.text())
         settings.setValue('last1path', last_1_project_path)
 
+        # survey
+        survey_start = str(self.ui.lineEdit_surveyStartPt.text())
+        survey_stop = str(self.ui.lineEdit_surveyEndPt.text())
+        settings.setValue('survey_start_scan', survey_start)
+        settings.setValue('survey_stop_scan', survey_stop)
+
         return
 
     def load_settings(self):
@@ -3622,6 +3784,12 @@ class MainWindow(QtGui.QMainWindow):
             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)
+            survey_stop = str(settings.value('survey_stop_scan'))
+            self.ui.lineEdit_surveyEndPt.setText(survey_stop)
+
         except TypeError as err:
             self.pop_one_button_dialog(str(err))
             return
@@ -3649,16 +3817,31 @@ class MainWindow(QtGui.QMainWindow):
 
         return True, (a, b, c, alpha, beta, gamma)
 
-    def _plot_raw_xml_2d(self, exp_no, scan_no, pt_no):
-        """ Plot raw workspace from XML file for a measurement/pt.
+    def load_spice_file(self, exp_number, scan_number, overwrite):
         """
+        load spice file
+        :param exp_number:
+        :param scan_number:
+        :param overwrite:
+        :return:
+        """
+        # check inputs
+        assert isinstance(exp_number, int), 'Exp number {0} must be an integer but not of type {1}' \
+                                            ''.format(exp_number, type(exp_number))
+        assert isinstance(scan_number, int), 'Scan number {0} must be an integer but not of type {1}' \
+                                             ''.format(scan_number, type(scan_number))
+
         # Check and load SPICE table file
-        does_exist = self._myControl.does_spice_loaded(exp_no, scan_no)
-        if does_exist is False:
+        load_spice = False
+        if not overwrite:
+            load_spice = not self._myControl.does_spice_loaded(exp_number, scan_number)
+
+        # load if necessary
+        if load_spice:
             # Download data
-            status, error_message = self._myControl.download_spice_file(exp_no, scan_no, over_write=False)
-            if status is True:
-                status, error_message = self._myControl.load_spice_scan_file(exp_no, scan_no)
+            status, error_message = self._myControl.download_spice_file(exp_number, scan_number, over_write=False)
+            if status:
+                status, error_message = self._myControl.load_spice_scan_file(exp_number, scan_number)
                 if status is False and self._allowDownload is False:
                     self.pop_one_button_dialog(error_message)
                     return
@@ -3667,44 +3850,40 @@ class MainWindow(QtGui.QMainWindow):
                 return
         # END-IF(does_exist)
 
-        # Load Data for Pt's xml file
-        does_exist = self._myControl.does_raw_loaded(exp_no, scan_no, pt_no)
+        return
 
-        if does_exist is False:
-            # Check whether needs to download
-            status, error_message = self._myControl.download_spice_xml_file(scan_no, pt_no, exp_no=exp_no)
-            if status is False:
-                self.pop_one_button_dialog(error_message)
-                return
-            # Load SPICE xml file
-            status, error_message = self._myControl.load_spice_xml_file(exp_no, scan_no, pt_no)
-            if status is False:
-                self.pop_one_button_dialog(error_message)
-                return
+    def load_plot_raw_data(self, exp_no, scan_no, pt_no, roi_name=None):
+        """
+        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)
+        :return:
+        """
+        # check inputs
+        assert isinstance(exp_no, int), 'Exp number {0} must be an integer but not of type {1}' \
+                                        ''.format(exp_no, type(exp_no))
+        assert isinstance(scan_no, int), 'Scan number {0} must be an integer but not of type {1}' \
+                                         ''.format(scan_no, type(scan_no))
+        assert isinstance(pt_no, int), 'Pt number {0} must be an integer but not of type {1}' \
+                                       ''.format(pt_no, type(pt_no))
+
+        # check data loaded with mask information
+        does_loaded = self._myControl.does_raw_loaded(exp_no, scan_no, pt_no, roi_name)
+        if not does_loaded:
+            # check and load SPICE file if necessary
+            self.load_spice_file(exp_no, scan_no, overwrite=False)
+            # load Pt xml
+            self._myControl.load_spice_xml_file(exp_no, scan_no, pt_no)
+            # mask detector if required
+            if roi_name is not None:
+                self._myControl.apply_mask(exp_no, scan_no, pt_no, roi_name=roi_name)
+        # END-IF
 
-        # Convert a list of vector to 2D numpy array for imshow()
         # Get data and plot
         raw_det_data = self._myControl.get_raw_detector_counts(exp_no, scan_no, pt_no)
-        # raw_det_data = numpy.rot90(raw_det_data, 1)
-        self.ui.graphicsView_detector2dPlot.clear_canvas()
-        # get the configuration of detector from GUI
-        #  FIXME/TODO/ISSUE/NOW/TODAY - use the detector size wrong!
-        if 0:
-            ret_obj = gutil.parse_integer_list(str(self.ui.lineEdit_detectorGeometry.text()), expected_size=2)
-            x_max, y_max = ret_obj
-        else:
-            x_max, y_max = 256, 256
-
-        self.ui.graphicsView_detector2dPlot.add_plot_2d(raw_det_data, x_min=0, x_max=x_max, y_min=0, y_max=y_max,
-                                                        hold_prev_image=False)
-        status, roi = self._myControl.get_region_of_interest(exp_no, scan_number=None)
-        if status:
-            self.ui.graphicsView_detector2dPlot.add_roi(roi[0], roi[1])
-        else:
-            error_msg = roi
-            # self.pop_one_button_dialog(error_msg)
-            print('[Error] %s' % error_msg)
-        # END-IF
+        self.ui.graphicsView_detector2dPlot.plot_detector_counts(raw_det_data)
 
         # Information
         info = '%-10s: %d\n%-10s: %d\n%-10s: %d\n' % ('Exp', exp_no,
diff --git a/scripts/HFIR_4Circle_Reduction/refineubfftsetup.py b/scripts/HFIR_4Circle_Reduction/refineubfftsetup.py
index fa72b99cbbe6b62e83bbd33ebe7ea517d21277d5..0c5ef026dcc16d6b844bd7a144de06f00412842b 100644
--- a/scripts/HFIR_4Circle_Reduction/refineubfftsetup.py
+++ b/scripts/HFIR_4Circle_Reduction/refineubfftsetup.py
@@ -1,5 +1,5 @@
 from __future__ import (absolute_import, division, print_function)
-import ui_RefineUbFftDialog
+import HFIR_4Circle_Reduction.ui_RefineUbFftDialog as ui_RefineUbFftDialog
 
 from PyQt4 import QtCore
 from PyQt4 import QtGui
diff --git a/scripts/Inelastic/CrystalField/CrystalFieldMultiSite.py b/scripts/Inelastic/CrystalField/CrystalFieldMultiSite.py
index 5c693ea580b2229c067402a52b5db65e63ba5541..2eb89e593e877772a820f123ec393f6244c3a1d2 100644
--- a/scripts/Inelastic/CrystalField/CrystalFieldMultiSite.py
+++ b/scripts/Inelastic/CrystalField/CrystalFieldMultiSite.py
@@ -1,6 +1,7 @@
 import numpy as np
 
 from CrystalField import CrystalField, Function
+from .fitting import islistlike
 
 
 def makeWorkspace(xArray, yArray, child=True, ws_name='dummy'):
@@ -66,9 +67,13 @@ class CrystalFieldMultiSite(object):
         self.Symmetries = Symmetries
         self._plot_window = {}
         self.chi2 = None
+        self._resolutionModel = None
 
         parameter_dict = kwargs.pop('parameters', None)
         attribute_dict = kwargs.pop('attributes', None)
+        ties_dict = kwargs.pop('ties', None)
+        constraints_list = kwargs.pop('constraints', None)
+        fix_list = kwargs.pop('fixedParameters', None)
 
         kwargs = self._setMandatoryArguments(kwargs)
 
@@ -78,22 +83,32 @@ class CrystalFieldMultiSite(object):
 
         self._setRemainingArguments(kwargs)
 
+        self.default_spectrum_size = 200
+
         if attribute_dict is not None:
             for name, value in attribute_dict.items():
                 self.function.setAttributeValue(name, value)
         if parameter_dict is not None:
             for name, value in parameter_dict.items():
                 self.function.setParameter(name, value)
+        if ties_dict:
+            for name, value in parameter_dict.items():
+                self.function.tie(name, value)
+        if constraints_list:
+            self.function.addConstraints(','.join(constraints_list))
+        if fix_list:
+            for param in fix_list:
+                self.function.fixParameter(param)
 
     def _setMandatoryArguments(self, kwargs):
-        if 'Temperatures' in kwargs:
-            self.Temperatures = kwargs.pop('Temperatures')
-            if 'FWHMs' in kwargs:
-                self.FWHMs = kwargs.pop('FWHMs')
+        if 'Temperatures' in kwargs or 'Temperature' in kwargs:
+            self.Temperatures = kwargs.pop('Temperatures') if 'Temperatures' in kwargs else kwargs.pop('Temperature')
+            if 'FWHM' in kwargs or 'FWHMs' in kwargs:
+                self.FWHM = kwargs.pop('FWHMs') if 'FWHMs' in kwargs else kwargs.pop('FWHM')
             elif 'ResolutionModel' in kwargs:
                 self.ResolutionModel = kwargs.pop('ResolutionModel')
             else:
-                raise RuntimeError("If temperatures are set, must also set FWHMs or ResolutionModel")
+                raise RuntimeError("If temperatures are set, must also set FWHM or ResolutionModel")
         return kwargs
 
     def _setRemainingArguments(self, kwargs):
@@ -137,6 +152,16 @@ class CrystalFieldMultiSite(object):
 
         return self._calcSpectrum(i, ws, ws_index)
 
+    def calc_xmin_xmax(self, i=0):
+        peaks = np.array([])
+        for idx in range(len(self.Ions)):
+            blm = {}
+            for bparam in CrystalField.field_parameter_names:
+                blm[bparam] = self.function.getParameterValue('ion{}.'.format(idx) + bparam)
+            _cft = CrystalField(self.Ions[idx], 'C1', Temperature=self.Temperatures[i], **blm)
+            peaks = np.append(peaks, _cft.getPeakList()[0])
+        return np.min(peaks), np.max(peaks)
+
     def getSpectrum(self, *args):
         """
         Get a specified spectrum calculated with the current field and peak parameters.
@@ -144,14 +169,16 @@ class CrystalFieldMultiSite(object):
         Alternatively can be called getSpectrum(workspace, ws_index). Spectrum index is assumed zero.
 
         Examples:
+            cf.getSpectrum()         # Calculate the first spectrum using automatically generated x-values
+            cf.getSpectrum(1)        # Calculate the second spectrum using automatically generated x-values
             cf.getSpectrum(1, ws, 5) # Calculate the second spectrum using the x-values from the 6th spectrum
                                      # in workspace ws.
-            cf.getSpectrum(ws) # Calculate the first spectrum using the x-values from the 1st spectrum
-                               # in workspace ws.
-            cf.getSpectrum(ws, 3) # Calculate the first spectrum using the x-values from the 4th spectrum
-                                  # in workspace ws.
-            cf.getSpectrum(3, ws) # Calculate the third spectrum using the x-values from the 1st spectrum
-                                  # in workspace ws.
+            cf.getSpectrum(ws)       # Calculate the first spectrum using the x-values from the 1st spectrum
+                                     # in workspace ws.
+            cf.getSpectrum(ws, 3)    # Calculate the first spectrum using the x-values from the 4th spectrum
+                                     # in workspace ws.
+            cf.getSpectrum(2, ws)    # Calculate the third spectrum using the x-values from the 1st spectrum
+                                     # in workspace ws.
 
         @return: A tuple of (x, y) arrays
         """
@@ -160,11 +187,18 @@ class CrystalFieldMultiSite(object):
                 raise RuntimeError('You must first define a temperature for the spectrum')
             return self._calcSpectrum(args[0], args[1], args[2])
         elif len(args) == 1:
-            return self._calcSpectrum(0, args[0], 0)
+            if isinstance(args[0], int):
+                x_min, x_max = self.calc_xmin_xmax(args[0])
+                xArray = np.linspace(x_min, x_max, self.default_spectrum_size)
+                return self._calcSpectrum(args[0], xArray, 0)
+            else:
+                return self._calcSpectrum(0, args[0], 0)
         elif len(args) == 2:
             return self._getSpectrumTwoArgs(*args)
         else:
-            raise RuntimeError('getSpectrum expected 1-3 arguments, got {}s'.format(len(args)))
+            x_min, x_max = self.calc_xmin_xmax()
+            xArray = np.linspace(x_min, x_max, self.default_spectrum_size)
+            return self._calcSpectrum(0, xArray, 0)
 
     def _convertToWS(self, wksp_list):
         """
@@ -350,7 +384,7 @@ class CrystalFieldMultiSite(object):
         params = get_parameters_for_add_from_multisite(self, 0)
         params.update(get_parameters_for_add_from_multisite(other, len(self.Ions)))
         new_cf = CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=self.Temperatures,
-                                       FWHMs=self.FWHMs, parameters=params, abundances=abundances)
+                                       FWHM=self.FWHM, parameters=params, abundances=abundances)
         return new_cf
 
     def __getitem__(self, item):
@@ -378,7 +412,7 @@ class CrystalFieldMultiSite(object):
         params = get_parameters_for_add_from_multisite(self, 0)
         params.update(get_parameters_for_add(other, len(self.Ions)))
         new_cf = CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=self.Temperatures,
-                                       FWHMs=self.FWHMs, parameters=params, abundances=abundances)
+                                       FWHM=self.FWHM, parameters=params, abundances=abundances)
         return new_cf
 
     def __radd__(self, other):
@@ -395,13 +429,31 @@ class CrystalFieldMultiSite(object):
         params = get_parameters_for_add(other, 0)
         params.update(get_parameters_for_add_from_multisite(self, 1))
         new_cf = CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=self.Temperatures,
-                                       FWHMs=self.FWHMs, parameters=params, abundances=abundances)
+                                       FWHM=self.FWHM, parameters=params, abundances=abundances)
         return new_cf
 
     @property
     def background(self):
         return self._background
 
+    @background.setter
+    def background(self, value):
+        if hasattr(value, 'peak') and hasattr(value, 'background'):
+            # Input is a CrystalField.Background object
+            if value.peak and value.background:
+                self._setBackground(peak=str(value.peak.function), background=str(value.background.function))
+            elif value.peak:
+                self._setBackground(peak=str(value.peak.function))
+            else:
+                self._setBackground(background=str(value.background.function))
+        elif hasattr(value, 'function'):
+            self._setBackground(background=str(value.function))
+        else:
+            self._setBackground(background=value)
+        # Need this for a weird python bug: "IndexError: Function index (2) out of range (2)"
+        # if user calls print(self.function) after setting background
+        _ = self.function.getTies() # noqa: F841
+
     @property
     def Ions(self):
         string_ions = self.function.getAttributeValue('Ions')
@@ -456,6 +508,14 @@ class CrystalFieldMultiSite(object):
     def Temperatures(self, value):
         self.function.setAttributeValue('Temperatures', value)
 
+    @property
+    def Temperature(self):
+        return list(self.function.getAttributeValue("Temperatures"))
+
+    @Temperature.setter
+    def Temperatures(self, value):
+        self.function.setAttributeValue('Temperatures', value)
+
     @property
     def FWHMs(self):
         fwhm = self.function.getAttributeValue('FWHMs')
@@ -466,10 +526,46 @@ class CrystalFieldMultiSite(object):
 
     @FWHMs.setter
     def FWHMs(self, value):
-        if len(value) == 1:
-            value = value[0]
+        if islistlike(value):
+            if len(value) != len(self.Temperatures):
+                value = [value[0]] * len(self.Temperatures)
+        else:
             value = [value] * len(self.Temperatures)
         self.function.setAttributeValue('FWHMs', value)
+        self._resolutionModel = None
+
+    @property
+    def FWHM(self):
+        return self.FWHMs
+
+    @FWHM.setter
+    def FWHM(self, value):
+        self.FWHMs = value
+
+    @property
+    def ResolutionModel(self):
+        return self._resolutionModel
+
+    @ResolutionModel.setter
+    def ResolutionModel(self, value):
+        from .function import ResolutionModel
+        if hasattr(value, 'model'):
+            self._resolutionModel = value
+        else:
+            self._resolutionModel = ResolutionModel(value)
+        nSpec = len(self.Temperatures)
+        if nSpec > 1:
+            if not self._resolutionModel.multi or self._resolutionModel.NumberOfSpectra != nSpec:
+                raise RuntimeError('Resolution model is expected to have %s functions, found %s' %
+                                   (nSpec, self._resolutionModel.NumberOfSpectra))
+            for i in range(nSpec):
+                model = self._resolutionModel.model[i]
+                self.function.setAttributeValue('sp%i.FWHMX' % i, model[0])
+                self.function.setAttributeValue('sp%i.FWHMY' % i, model[1])
+        else:
+            model = self._resolutionModel.model
+            self.function.setAttributeValue('FWHMX', model[0])
+            self.function.setAttributeValue('FWHMY', model[1])
 
     @property
     def FWHMVariation(self):
diff --git a/scripts/Inelastic/CrystalField/__init__.py b/scripts/Inelastic/CrystalField/__init__.py
index 17cf614835b81aeb3b6b1c7d4b8bafe7bc7a5449..20fd13f37bb8abddefd4308b4d230dd7d7264fc4 100644
--- a/scripts/Inelastic/CrystalField/__init__.py
+++ b/scripts/Inelastic/CrystalField/__init__.py
@@ -1,6 +1,7 @@
 from __future__ import (absolute_import, division, print_function)
-from .fitting import CrystalField, CrystalFieldFit, CrystalFieldMulti
+from .fitting import CrystalField, CrystalFieldFit
 from .function import PeaksFunction, Background, Function, ResolutionModel, PhysicalProperties
 from .pointcharge import PointCharge
-__all__ = ['CrystalField', 'CrystalFieldFit', 'CrystalFieldMulti', 'PeaksFunction',
+from .CrystalFieldMultiSite import CrystalFieldMultiSite
+__all__ = ['CrystalField', 'CrystalFieldFit', 'CrystalFieldMultiSite', 'PeaksFunction',
            'Background', 'Function', 'ResolutionModel', 'PhysicalProperties', 'PointCharge']
diff --git a/scripts/Inelastic/CrystalField/fitting.py b/scripts/Inelastic/CrystalField/fitting.py
index aab99ebca585ddecb1347b76fc8a463a861b26af..f07526d4cfd5b9ca3a1a6fed0a54e474269757c4 100644
--- a/scripts/Inelastic/CrystalField/fitting.py
+++ b/scripts/Inelastic/CrystalField/fitting.py
@@ -2,7 +2,7 @@ from __future__ import (absolute_import, division, print_function)
 import numpy as np
 import re
 import warnings
-from six import string_types
+from six import string_types, iteritems
 
 
 # RegEx pattern matching a composite function parameter name, eg f2.Sigma.
@@ -26,20 +26,68 @@ def makeWorkspace(xArray, yArray):
 
 
 def islistlike(arg):
-    return (not hasattr(arg, "strip")) and (hasattr(arg, "__getitem__") or hasattr(arg, "__iter__"))
+    return (not hasattr(arg, "strip")) and (hasattr(arg, "__getitem__") or hasattr(arg, "__iter__")) and hasattr(arg, "__len__")
+
+
+def ionname2Nre(ionname):
+    ion_nre_map = {'Ce': 1, 'Pr': 2, 'Nd': 3, 'Pm': 4, 'Sm': 5, 'Eu': 6, 'Gd': 7,
+                   'Tb': 8, 'Dy': 9, 'Ho': 10, 'Er': 11, 'Tm': 12, 'Yb': 13}
+    if ionname not in ion_nre_map.keys():
+        msg = 'Value %s is not allowed for attribute Ion.\nList of allowed values: %s' % \
+              (ionname, ', '.join(list(ion_nre_map.keys())))
+        arbitraryJ = re.match('[SJsj]([0-9\.]+)', ionname)
+        if arbitraryJ and (float(arbitraryJ.group(1)) % 0.5) == 0:
+            nre = int(-float(arbitraryJ.group(1)) * 2.)
+            if nre < -99:
+                raise RuntimeError('J value ' + str(-nre / 2) + ' is too large.')
+        else:
+            raise RuntimeError(msg+', S<n>, J<n>')
+    else:
+        nre = ion_nre_map[ionname]
+    return nre
+
+
+def cfpstrmaker(x, pref='B'):
+    return [pref+str(k)+str(x) for k in [2, 4, 6] if x <= k]
+
+
+def getSymmAllowedParam(sym_str):
+    if 'T' in sym_str or 'O' in sym_str:
+        return ['B40', 'B44', 'B60', 'B64']
+    if any([sym_str == val for val in ['C1', 'Ci']]):
+        return sum([cfpstrmaker(i) for i in range(7)] +
+                   [cfpstrmaker(i, 'IB') for i in range(1, 7)],[])
+    retval = cfpstrmaker(0)
+    if '6' in sym_str or '3' in sym_str:
+        retval += cfpstrmaker(6)
+        if any([sym_str == val for val in ['C6', 'C3h', 'C6h']]):
+            retval += cfpstrmaker(6, 'IB')
+    if ('3' in sym_str and '3h' not in sym_str) or 'S6' in sym_str:
+        retval += cfpstrmaker(3)
+        if any([sym_str == val for val in ['C3', 'S6']]):
+            retval += cfpstrmaker(3, 'IB') + cfpstrmaker(6, 'IB')
+    if '4' in sym_str or '2' in sym_str:
+        retval += cfpstrmaker(4)
+        if any([sym_str == val for val in ['C4', 'S4', 'C4h']]):
+            retval += cfpstrmaker(4, 'IB')
+    if ('2' in sym_str and '2d' not in sym_str) or 'Cs' in sym_str:
+        retval += cfpstrmaker(2)
+        if any([sym_str == val for val in ['C2', 'Cs', 'C2h']]):
+            retval += cfpstrmaker(2, 'IB') + cfpstrmaker(4, 'IB')
+    return retval
 
 
 #pylint: disable=too-many-instance-attributes,too-many-public-methods
 class CrystalField(object):
     """Calculates the crystal fields for one ion"""
 
-    ion_nre_map = {'Ce': 1, 'Pr': 2, 'Nd': 3, 'Pm': 4, 'Sm': 5, 'Eu': 6, 'Gd': 7,
-                   'Tb': 8, 'Dy': 9, 'Ho': 10, 'Er': 11, 'Tm': 12, 'Yb': 13}
-
     allowed_symmetries = ['C1', 'Ci', 'C2', 'Cs', 'C2h', 'C2v', 'D2', 'D2h', 'C4', 'S4', 'C4h',
                           'D4', 'C4v', 'D2d', 'D4h', 'C3', 'S6', 'D3', 'C3v', 'D3d', 'C6', 'C3h',
                           'C6h', 'D6', 'C6v', 'D3h', 'D6h', 'T', 'Td', 'Th', 'O', 'Oh']
 
+    lande_g = [6.0 / 7., 4.0 / 5., 8.0 / 11., 3.0 / 5., 2.0 / 7., 0.0, 2.0,
+               3.0 / 2., 4.0 / 3., 5.0 / 4.,  6.0 / 5., 7.0 / 6., 8.0 / 7.]
+
     default_peakShape = 'Gaussian'
     default_background = 'FlatBackground'
     default_spectrum_size = 200
@@ -132,6 +180,12 @@ class CrystalField(object):
 
         free_parameters = {key: kwargs[key] for key in kwargs if key in CrystalField.field_parameter_names}
 
+        if 'ResolutionModel' in kwargs and 'FWHM' in kwargs:
+            msg = 'Both ''ResolutionModel'' and ''FWHM'' specified but can only accept one width option.'
+            msg += ' Prefering to use ResolutionModel, and ignoring FWHM.'
+            kwargs.pop('FWHM')
+            warnings.warn(msg, SyntaxWarning)
+
         for key in kwargs:
             if key == 'ToleranceEnergy':
                 self.ToleranceEnergy = kwargs[key]
@@ -157,7 +211,7 @@ class CrystalField(object):
         for param in CrystalField.field_parameter_names:
             if param in free_parameters:
                 self.function.setParameter(param, free_parameters[param])
-            else:
+            elif param not in getSymmAllowedParam(self.Symmetry):
                 self.function.fixParameter(param)
 
         self._setPeaks()
@@ -173,8 +227,6 @@ class CrystalField(object):
         self._peakList = None
 
         # Spectra
-        self._dirty_spectra = True
-        self._spectra = {}
         self._plot_window = {}
 
         # self._setDefaultTies()
@@ -248,6 +300,7 @@ class CrystalField(object):
         """
         if hasattr(i, 'toString'):
             out = i.toString()
+            ppobj = i
         else:
             if self._physprop is None:
                 raise RuntimeError('Physical properties environment not defined.')
@@ -261,6 +314,11 @@ class CrystalField(object):
         if len(fieldParams) > 0:
             out += ',%s' % ','.join(['%s=%s' % item for item in fieldParams.items()])
         ties = self._getFieldTies()
+        from .function import PhysicalProperties
+        if ppobj.TypeID == PhysicalProperties.SUSCEPTIBILITY:
+            ties += ',' if ties else ''
+            ties += 'Lambda=0' if ppobj.Lambda == 0. else ''
+            ties += ',Chi0=0' if ppobj.Chi0 == 0. else ''
         if len(ties) > 0:
             out += ',ties=(%s)' % ties
         constraints = self._getFieldConstraints()
@@ -269,7 +327,6 @@ class CrystalField(object):
         return out
 
     def makeMultiSpectrumFunction(self):
-        import re
         return re.sub(r'FWHM[X|Y]\d+=\(\),', '', str(self.function))
 
     @property
@@ -290,14 +347,10 @@ class CrystalField(object):
         ...
         cf.Ion = 'Pr'
         """
-        if value not in self.ion_nre_map.keys():
-            msg = 'Value %s is not allowed for attribute Ion.\nList of allowed values: %s' % \
-                  (value, ', '.join(list(self.ion_nre_map.keys())))
-            raise RuntimeError(msg)
+        self._nre = ionname2Nre(value)
         self.crystalFieldFunction.setAttributeValue('Ion', value)
         self._dirty_eigensystem = True
         self._dirty_peaks = True
-        self._dirty_spectra = True
 
     @property
     def Symmetry(self):
@@ -324,7 +377,6 @@ class CrystalField(object):
         self.crystalFieldFunction.setAttributeValue('Symmetry', value)
         self._dirty_eigensystem = True
         self._dirty_peaks = True
-        self._dirty_spectra = True
 
     @property
     def ToleranceEnergy(self):
@@ -336,7 +388,6 @@ class CrystalField(object):
         """Set energy tolerance"""
         self.crystalFieldFunction.setAttributeValue('ToleranceEnergy', float(value))
         self._dirty_peaks = True
-        self._dirty_spectra = True
 
     @property
     def ToleranceIntensity(self):
@@ -348,7 +399,6 @@ class CrystalField(object):
         """Set intensity tolerance"""
         self.crystalFieldFunction.setAttributeValue('ToleranceIntensity', float(value))
         self._dirty_peaks = True
-        self._dirty_spectra = True
 
     @property
     def IntensityScaling(self):
@@ -378,7 +428,6 @@ class CrystalField(object):
                 self.crystalFieldFunction.setParameter(paramName, value[i])
 
         self._dirty_peaks = True
-        self._dirty_spectra = True
 
     @property
     def Temperature(self):
@@ -401,7 +450,6 @@ class CrystalField(object):
                 return
             self.crystalFieldFunction.setAttributeValue('Temperature', float(value))
         self._dirty_peaks = True
-        self._dirty_spectra = True
 
     @property
     def FWHM(self):
@@ -420,12 +468,30 @@ class CrystalField(object):
         if self._isMultiSpectrum:
             if not islistlike(value):
                 value = [value] * self.NumberOfSpectra
+            if len(value) != len(self.Temperature):
+                if self.PhysicalProperty is not None and len(value) == len(self.Temperature) - len(self.PhysicalProperty):
+                    value = value + [0] * len(self.PhysicalProperty)
+                else:
+                    raise RuntimeError('Vector of FWHMs must either have same size as '
+                                       'Temperatures (%i) or have size 1.' % (len(self.Temperature)))
             self.crystalFieldFunction.setAttributeValue('FWHMs', value)
         else:
             if islistlike(value):
                 raise ValueError('FWHM is expected to be a single floating point value')
             self.crystalFieldFunction.setAttributeValue('FWHM', float(value))
-        self._dirty_spectra = True
+        # If both FWHM and ResolutionModel is set, may cause runtime errors
+        self._resolutionModel = None
+        if self._isMultiSpectrum:
+            for i in range(self.NumberOfSpectra):
+                if self.crystalFieldFunction.getAttributeValue('FWHMX%s' % i):
+                    self.crystalFieldFunction.setAttributeValue('FWHMX%s' % i, [])
+                if self.crystalFieldFunction.getAttributeValue('FWHMY%s' % i):
+                    self.crystalFieldFunction.setAttributeValue('FWHMY%s' % i, [])
+        else:
+            if self.crystalFieldFunction.getAttributeValue('FWHMX'):
+                self.crystalFieldFunction.setAttributeValue('FWHMX', [])
+            if self.crystalFieldFunction.getAttributeValue('FWHMY'):
+                self.crystalFieldFunction.setAttributeValue('FWHMY', [])
 
     @property
     def FWHMVariation(self):
@@ -434,13 +500,11 @@ class CrystalField(object):
     @FWHMVariation.setter
     def FWHMVariation(self, value):
         self.crystalFieldFunction.setAttributeValue('FWHMVariation', float(value))
-        self._dirty_spectra = True
 
     def __getitem__(self, item):
         return self.crystalFieldFunction.getParameterValue(item)
 
     def __setitem__(self, key, value):
-        self._dirty_spectra = True
         self.crystalFieldFunction.setParameter(key, value)
 
     @property
@@ -450,7 +514,7 @@ class CrystalField(object):
     @ResolutionModel.setter
     def ResolutionModel(self, value):
         from .function import ResolutionModel
-        if isinstance(value, ResolutionModel):
+        if hasattr(value, 'model'):
             self._resolutionModel = value
         else:
             self._resolutionModel = ResolutionModel(value)
@@ -466,6 +530,11 @@ class CrystalField(object):
             model = self._resolutionModel.model
             self.crystalFieldFunction.setAttributeValue('FWHMX', model[0])
             self.crystalFieldFunction.setAttributeValue('FWHMY', model[1])
+        # If FWHM is set, it overrides resolution model, so unset it
+        if self._isMultiSpectrum and any(self.crystalFieldFunction.getAttributeValue('FWHMs')):
+            self.crystalFieldFunction.setAttributeValue('FWHMs', [0.] * self.NumberOfSpectra)
+        elif not self._isMultiSpectrum and self.crystalFieldFunction.getAttributeValue('FWHM'):
+            self.crystalFieldFunction.setAttributeValue('FWHM', 0.)
 
     @property
     def FixAllPeaks(self):
@@ -511,11 +580,10 @@ class CrystalField(object):
             value: an instance of function.Background class or a list of instances
                 in a multi-spectrum case
         """
-        from .function import Background
         from mantid.simpleapi import FunctionFactory
         if self._background is not None:
             raise ValueError('Background has been set already')
-        if not isinstance(value, Background):
+        if not hasattr(value, 'toString'):
             raise TypeError('Expected a Background object, found %s' % str(value))
         if not self._isMultiSpectrum:
             fun_str = value.toString() + ';' + str(self.function)
@@ -548,9 +616,8 @@ class CrystalField(object):
 
     @PhysicalProperty.setter
     def PhysicalProperty(self, value):
-        from .function import PhysicalProperties
         vlist = value if islistlike(value) else [value]
-        if all([isinstance(pp, PhysicalProperties) for pp in vlist]):
+        if all([hasattr(pp, 'TypeID') for pp in vlist]):
             nOldPP = len(self._physprop) if islistlike(self._physprop) else (0 if self._physprop is None else 1)
             self._physprop = value
         else:
@@ -576,7 +643,14 @@ class CrystalField(object):
             self.function.setAttributeValue('PhysicalProperties', [0]*len(tt)+ppids)
             for attribs in [pp.getAttributes(i+len(tt)) for i, pp in enumerate(vlist)]:
                 for item in attribs.items():
-                    self.function.setAttributeValue(item[0], item[1])
+                    if 'Lambda' in item[0] or 'Chi0' in item[0]:
+                        self.function.setParameter(item[0], item[1])
+                        if item[1] == 0.:
+                            self.function.tie(item[0], '0.')
+                        else:
+                            self.function.removeTie(item[0])
+                    else:
+                        self.function.setAttributeValue(item[0], item[1])
 
     @property
     def isPhysicalPropertyOnly(self):
@@ -642,10 +716,6 @@ class CrystalField(object):
         @param ws_index:  An index of a spectrum from workspace to use.
         @return: A tuple of (x, y) arrays
         """
-        if self._dirty_spectra:
-            self._spectra = {}
-            self._dirty_spectra = False
-
         wksp = workspace
         # Allow to call getSpectrum with a workspace as the first argument.
         if not isinstance(i, int):
@@ -668,16 +738,12 @@ class CrystalField(object):
             return self._calcSpectrum(i, wksp, ws_index)
 
         if xArray is None:
-            if i in self._spectra:
-                return self._spectra[i]
-            else:
-                x_min, x_max = self.calc_xmin_xmax(i)
-                xArray = np.linspace(x_min, x_max, self.default_spectrum_size)
+            x_min, x_max = self.calc_xmin_xmax(i)
+            xArray = np.linspace(x_min, x_max, self.default_spectrum_size)
 
         yArray = np.zeros_like(xArray)
         wksp = makeWorkspace(xArray, yArray)
-        self._spectra[i] = self._calcSpectrum(i, wksp, 0)
-        return self._spectra[i]
+        return self._calcSpectrum(i, wksp, 0)
 
     def getHeatCapacity(self, workspace=None, ws_index=0):
         """
@@ -822,6 +888,22 @@ class CrystalField(object):
 
         return self._getPhysProp(PhysicalProperties(pptype, *args, **kwargs), workspace, ws_index)
 
+    def getDipoleMatrix(self):
+        """Returns the dipole transition matrix as a numpy array"""
+        from scipy.constants import physical_constants
+        from CrystalField.energies import energies
+        self._calcEigensystem()
+        _, _, hx = energies(self._nre, BextX=1.0)
+        _, _, hy = energies(self._nre, BextY=1.0)
+        _, _, hz = energies(self._nre, BextZ=1.0)
+        ix = np.dot(np.conj(np.transpose(self._eigenvectors)), np.dot(hx, self._eigenvectors))
+        iy = np.dot(np.conj(np.transpose(self._eigenvectors)), np.dot(hy, self._eigenvectors))
+        iz = np.dot(np.conj(np.transpose(self._eigenvectors)), np.dot(hz, self._eigenvectors))
+        gj = 2. if (self._nre < 1) else self.lande_g[self._nre - 1]
+        gJuB = gj * physical_constants['Bohr magneton in eV/T'][0] * 1000.
+        trans = np.multiply(ix, np.conj(ix)) + np.multiply(iy, np.conj(iy)) + np.multiply(iz, np.conj(iz))
+        return trans / (gJuB ** 2)
+
     def plot(self, i=0, workspace=None, ws_index=0, name=None):
         """Plot a spectrum. Parameters are the same as in getSpectrum(...)"""
         from mantidplot import plotSpectrum
@@ -898,12 +980,32 @@ class CrystalField(object):
             symmetries = [self.Symmetry, other.Symmetry]
             params = {}
             temperatures = [self._getTemperature(x) for x in range(self.NumberOfSpectra)]
-            fwhms = [self._getFWHM(x) for x in range(self.NumberOfSpectra)]
             for bparam in CrystalField.field_parameter_names:
                 params['ion0.' + bparam] = self[bparam]
                 params['ion1.' + bparam] = other[bparam]
-            return CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=temperatures,
-                                         FWHMs = fwhms, parameters=params, abundances=[1.0, 1.0])
+            ties = {}
+            fixes = []
+            for prefix, obj in iteritems({'ion0.':self, 'ion1.':other}):
+                tiestr = obj.function.getTies()
+                if tiestr:
+                    for tiepair in [tie.split('=') for tie in tiestr.split(',')]:
+                        ties[prefix + tiepair[0]] = tiepair[1]
+                for par_id in [id for id in range(obj.function.nParams()) if obj.function.isFixed(id)]:
+                    parName = obj.function.getParamName(par_id)
+                    if obj.background is not None:
+                        parName = parName.split('.')[-1]
+                    if parName not in self.field_parameter_names:
+                        continue
+                    fixes.append(prefix + parName)
+            if self._resolutionModel is None:
+                fwhms = [self._getFWHM(x) for x in range(self.NumberOfSpectra)]
+                return CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=temperatures,
+                                             FWHM = fwhms, parameters=params, abundances=[1.0, 1.0],
+                                             ties = ties, fixedParameters = fixes)
+            else:
+                return CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=temperatures,
+                                             ResolutionModel=self._resolutionModel, parameters=params,
+                                             abundances=[1.0, 1.0], ties = ties, fixedParameters = fixes)
 
     def __mul__(self, factor):
         ffactor = float(factor)
@@ -970,13 +1072,11 @@ class CrystalField(object):
         return params
 
     def _getFieldTies(self):
-        import re
-        ties = re.match('ties=\((.*?)\)', str(self.crystalFieldFunction))
+        ties = re.search('ties=\((.*?)\)', str(self.crystalFieldFunction))
         return ties.group(1) if ties else ''
 
     def _getFieldConstraints(self):
-        import re
-        constraints = re.match('constraints=\((.*?)\)', str(self.crystalFieldFunction))
+        constraints = re.search('constraints=\((.*?)\)', str(self.crystalFieldFunction))
         return constraints.group(1) if constraints else ''
 
     def _getPhysProp(self, ppobj, workspace, ws_index):
@@ -1012,9 +1112,8 @@ class CrystalField(object):
         """
         if self._dirty_eigensystem:
             import CrystalField.energies as energies
-            nre = self.ion_nre_map[self.Ion]
             self._eigenvalues, self._eigenvectors, self._hamiltonian = \
-                energies.energies(nre, **self._getFieldParameters())
+                energies.energies(self._nre, **self._getFieldParameters())
             self._dirty_eigensystem = False
 
     def _calcPeaksList(self, i):
@@ -1066,6 +1165,7 @@ class CrystalFieldSite(object):
 
     def __add__(self, other):
         from CrystalField.CrystalFieldMultiSite import CrystalFieldMultiSite
+        from CrystalField import CrystalField
         if isinstance(other, CrystalField):
             abundances = [self.abundance, 1.0]
         elif isinstance(other, CrystalFieldSite):
@@ -1079,233 +1179,18 @@ class CrystalFieldSite(object):
         ions = [self.crystalField.Ion, other.Ion]
         symmetries = [self.crystalField.Symmetry, other.Symmetry]
         temperatures = [self.crystalField._getTemperature(x) for x in range(self.crystalField.NumberOfSpectra)]
-        FWHMs = [self.crystalField._getFWHM(x) for x in range(self.crystalField.NumberOfSpectra)]
         params = {}
         for bparam in CrystalField.field_parameter_names:
             params['ion0.' + bparam] = self.crystalField[bparam]
             params['ion1.' + bparam] = other[bparam]
-        return CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=temperatures, FWHMs = FWHMs,
-                                     abundances=abundances, parameters=params)
-
-
-class CrystalFieldMulti(object):
-    """CrystalFieldMulti represents crystal field of multiple ions."""
-
-    def __init__(self, *args):
-        self.sites = []
-        self.abundances = []
-        for arg in args:
-            if isinstance(arg, CrystalField):
-                self.sites.append(arg)
-                self.abundances.append(1.0)
-            elif isinstance(arg, CrystalFieldSite):
-                self.sites.append(arg.crystalField)
-                self.abundances.append(arg.abundance)
-            else:
-                raise RuntimeError('Cannot include an object of type %s into a CrystalFieldMulti' % type(arg))
-        self._ties = {}
-
-    def makeSpectrumFunction(self):
-        fun = ';'.join([a.makeSpectrumFunction() for a in self.sites])
-        fun += self._makeIntensityScalingTies()
-        ties = self.getTies()
-        if len(ties) > 0:
-            fun += ';ties=(%s)' % ties
-        return 'composite=CompositeFunction,NumDeriv=1;' + fun
-
-    def makePhysicalPropertiesFunction(self):
-        # Handles relative intensities. Scaling factors here a fixed attributes not
-        # variable parameters and we require the sum to be unity.
-        factors = np.array(self.abundances)
-        sum_factors = np.sum(factors)
-        factstr = [',ScaleFactor=%s' % (str(factors[i] / sum_factors)) for i in range(len(self.sites))]
-        fun = ';'.join([a.makePhysicalPropertiesFunction()+factstr[i] for a,i in enumerate(self.sites)])
-        ties = self.getTies()
-        if len(ties) > 0:
-            fun += ';ties=(%s)' % ties
-        return fun
-
-    def makeMultiSpectrumFunction(self):
-        fun = ';'.join([a.makeMultiSpectrumFunction() for a in self.sites])
-        fun += self._makeIntensityScalingTiesMulti()
-        ties = self.getTies()
-        if len(ties) > 0:
-            fun += ';ties=(%s)' % ties
-        return 'composite=CompositeFunction,NumDeriv=1;' + fun
-
-    def ties(self, **kwargs):
-        """Set ties on the parameters."""
-        for tie in kwargs:
-            self._ties[tie] = kwargs[tie]
-
-    def getTies(self):
-        ties = ['%s=%s' % item for item in self._ties.items()]
-        return ','.join(ties)
-
-    def getSpectrum(self, i=0, workspace=None, ws_index=0):
-        tt = []
-        for site in self.sites:
-            tt = tt + (list(site.Temperature) if islistlike(site.Temperature) else [site.Temperature])
-        if any([val < 0 for val in tt]):
-            raise RuntimeError('You must first define a temperature for the spectrum')
-        largest_abundance= max(self.abundances)
-        if workspace is not None:
-            xArray, yArray = self.sites[0].getSpectrum(i, workspace, ws_index)
-            yArray *= self.abundances[0] / largest_abundance
-            ia = 1
-            for arg in self.sites[1:]:
-                _, yyArray = arg.getSpectrum(i, workspace, ws_index)
-                yArray += yyArray * self.abundances[ia] / largest_abundance
-                ia += 1
-            return xArray, yArray
-        x_min = 0.0
-        x_max = 0.0
-        for arg in self.sites:
-            xmin, xmax = arg.calc_xmin_xmax(i)
-            if xmin < x_min:
-                x_min = xmin
-            if xmax > x_max:
-                x_max = xmax
-        xArray = np.linspace(x_min, x_max, CrystalField.default_spectrum_size)
-        _, yArray = self.sites[0].getSpectrum(i, xArray, ws_index)
-        yArray *= self.abundances[0] / largest_abundance
-        ia = 1
-        for arg in self.sites[1:]:
-            _, yyArray = arg.getSpectrum(i, xArray, ws_index)
-            yArray += yyArray * self.abundances[ia] / largest_abundance
-            ia += 1
-        return xArray, yArray
-
-    def update(self, func):
-        nFunc = func.nFunctions()
-        assert nFunc == len(self.sites)
-        for i in range(nFunc):
-            self.sites[i].update(func[i])
-
-    def update_multi(self, func):
-        nFunc = func.nFunctions()
-        assert nFunc == len(self.sites)
-        for i in range(nFunc):
-            self.sites[i].update_multi(func[i])
-
-    def _makeIntensityScalingTies(self):
-        """
-        Make a tie string that ties IntensityScaling's of the sites according to their abundances.
-        """
-        n_sites = len(self.sites)
-        if n_sites < 2:
-            return ''
-        factors = np.array(self.abundances)
-        i_largest = np.argmax(factors)
-        largest_factor = factors[i_largest]
-        tie_template = 'f%s.IntensityScaling=%s*' + 'f%s.IntensityScaling' % i_largest
-        ties = []
-        for i in range(n_sites):
-            if i != i_largest:
-                ties.append(tie_template % (i, factors[i] / largest_factor))
-        s = ';ties=(%s)' % ','.join(ties)
-        return s
-
-    def _makeIntensityScalingTiesMulti(self):
-        """
-        Make a tie string that ties IntensityScaling's of the sites according to their abundances.
-        """
-        n_sites = len(self.sites)
-        if n_sites < 2:
-            return ''
-        factors = np.array(self.abundances)
-        i_largest = np.argmax(factors)
-        largest_factor = factors[i_largest]
-        tie_template = 'f{1}.IntensityScaling{0}={2}*f%s.IntensityScaling{0}' % i_largest
-        ties = []
-        n_spectra = self.sites[0].NumberOfSpectra
-        for spec in range(n_spectra):
-            for i in range(n_sites):
-                if i != i_largest:
-                    ties.append(tie_template.format(spec, i, factors[i] / largest_factor))
-        s = ';ties=(%s)' % ','.join(ties)
-        return s
-
-    @property
-    def isPhysicalPropertyOnly(self):
-        return all([a.isPhysicalPropertyOnly for a in self.sites])
-
-    @property
-    def PhysicalProperty(self):
-        return [a.PhysicalProperty for a in self.sites]
-
-    @PhysicalProperty.setter
-    def PhysicalProperty(self, value):
-        for a in self.sites:
-            a.PhysicalProperty = value
-
-    @property
-    def NumberOfSpectra(self):
-        """ Returns the number of expected workspaces """
-        num_spec = []
-        for site in self.sites:
-            num_spec.append(site.NumberOfSpectra)
-        if len(set(num_spec)) > 1:
-            raise ValueError('Number of spectra for each site not consistent with each other')
-        return num_spec[0]
-
-    @property
-    def Temperature(self):
-        tt = []
-        for site in self.sites:
-            tt.append([val for val in (site.Temperature if islistlike(site.Temperature) else [site.Temperature])])
-        if len(set([tuple(val) for val in tt])) > 1:
-            raise ValueError('Temperatures of spectra for each site not consistent with each other')
-        return tt[0]
-
-    @Temperature.setter
-    def Temperature(self, value):
-        for site in self.sites:
-            site.Temperature = value
-
-    def __add__(self, other):
-        if isinstance(other, CrystalFieldMulti):
-            cfm = CrystalFieldMulti()
-            cfm.sites += self.sites + other.sites
-            cfm.abundances += self.abundances + other.abundances
-            return cfm
-        elif isinstance(other, CrystalField):
-            cfm = CrystalFieldMulti()
-            cfm.sites += self.sites + [other]
-            cfm.abundances += self.abundances + [1]
-            return cfm
-        elif isinstance(other, CrystalFieldSite):
-            cfm = CrystalFieldMulti()
-            cfm.sites += self.sites + [other.crystalField]
-            cfm.abundances += self.abundances + [other.abundance]
-            return cfm
+        if self.crystalField.ResolutionModel is None:
+            FWHM = [self.crystalField._getFWHM(x) for x in range(self.crystalField.NumberOfSpectra)]
+            return CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=temperatures, FWHM = FWHM,
+                                         abundances=abundances, parameters=params)
         else:
-            raise TypeError('Cannot add %s to CrystalFieldMulti' % other.__class__.__name__)
-
-    def __radd__(self, other):
-        if isinstance(other, CrystalFieldMulti):
-            cfm = CrystalFieldMulti()
-            cfm.sites += other.sites + self.sites
-            cfm.abundances += other.abundances + self.abundances
-            return cfm
-        elif isinstance(other, CrystalField):
-            cfm = CrystalFieldMulti()
-            cfm.sites += [other] + self.sites
-            cfm.abundances += [1] + self.abundances
-            return cfm
-        elif isinstance(other, CrystalFieldSite):
-            cfm = CrystalFieldMulti()
-            cfm.sites += [other.crystalField] + self.sites
-            cfm.abundances += [other.abundance] + self.abundances
-            return cfm
-        else:
-            raise TypeError('Cannot add %s to CrystalFieldMulti' % other.__class__.__name__)
-
-    def __len__(self):
-        return len(self.sites)
-
-    def __getitem__(self, item):
-        return self.sites[item]
+            return CrystalFieldMultiSite(Ions=ions, Symmetries=symmetries, Temperatures=temperatures,
+                                         ResolutionModel=self.crystalField.ResolutionModel,
+                                         abundances=abundances, parameters=params)
 
 
 #pylint: disable=too-few-public-methods
@@ -1314,12 +1199,15 @@ class CrystalFieldFit(object):
     Object that controls fitting.
     """
 
-    def __init__(self, Model=None, Temperature=None, FWHM=None, InputWorkspace=None, **kwargs):
+    def __init__(self, Model=None, Temperature=None, FWHM=None, InputWorkspace=None,
+                 ResolutionModel=None, **kwargs):
         self.model = Model
         if Temperature is not None:
             self.model.Temperature = Temperature
         if FWHM is not None:
             self.model.FWHM = FWHM
+        if ResolutionModel is not None:
+            self.model.ResolutionModel = ResolutionModel
         self._input_workspace = InputWorkspace
         self._output_workspace_base_name = 'fit'
         self._fit_properties = kwargs
@@ -1347,11 +1235,22 @@ class CrystalFieldFit(object):
 
     def estimate_parameters(self, EnergySplitting, Parameters, **kwargs):
         from CrystalField.normalisation import split2range
+        from CrystalField.CrystalFieldMultiSite import CrystalFieldMultiSite
         from mantid.api import mtd
         self.check_consistency()
-        ranges = split2range(Ion=self.model.Ion, EnergySplitting=EnergySplitting,
-                             Parameters=Parameters)
-        constraints = [('%s<%s<%s' % (-bound, parName, bound)) for parName, bound in ranges.items()]
+        if isinstance(self.model, CrystalFieldMultiSite):
+            constraints = []
+            for ni in range(len(self.model.Ions)):
+                pars = Parameters[ni] if islistlike(Parameters[ni]) else Parameters
+                ion = self.model.Ions[ni]
+                ranges = split2range(Ion=ion, EnergySplitting=EnergySplitting, Parameters=pars)
+                constraints += [('%s<ion%d.%s<%s' % (-bound, ni, parName, bound))
+                                for parName, bound in ranges.items()]
+        else:
+            ranges = split2range(Ion=self.model.Ion, EnergySplitting=EnergySplitting,
+                                 Parameters=Parameters)
+            constraints = [('%s<%s<%s' % (-bound, parName, bound))
+                           for parName, bound in ranges.items()]
         self.model.constraints(*constraints)
         if 'Type' not in kwargs or kwargs['Type'] == 'Monte Carlo':
             if 'OutputWorkspace' in kwargs and kwargs['OutputWorkspace'].strip() != '':
@@ -1378,12 +1277,14 @@ class CrystalFieldFit(object):
         ne = self.get_number_estimates()
         if ne == 0:
             raise RuntimeError('There are no estimated parameters.')
-        if index >= ne:
+        if index > ne:
             raise RuntimeError('There are only %s sets of estimated parameters, requested set #%s' % (ne, index))
+        from CrystalField.CrystalFieldMultiSite import CrystalFieldMultiSite
         for row in range(self._estimated_parameters.rowCount()):
             name = self._estimated_parameters.cell(row, 0)
             value = self._estimated_parameters.cell(row, index)
-            self.model[name] = value
+            model_pname = name if isinstance(self.model, CrystalFieldMultiSite) else name.split('.')[-1]
+            self.model[model_pname] = value
             if self._function is not None:
                 self._function.setParameter(name, value)
 
diff --git a/scripts/Inelastic/CrystalField/function.py b/scripts/Inelastic/CrystalField/function.py
index 5d6f24e20778cd609b540287efacb76ca709709d..c92547e806ad2d4cbd3b5a842cb8fe38368c2121 100644
--- a/scripts/Inelastic/CrystalField/function.py
+++ b/scripts/Inelastic/CrystalField/function.py
@@ -433,13 +433,18 @@ class ResolutionModel:
             x = self._mergeArrays(x, xx)
             y = self._mergeArrays(y, yy)
             n = len(x)
-        return x, y
+        return list(x), list(y)
 
 
 class PhysicalProperties(object):
     """
     Contains information about measurement conditions of physical properties
     """
+    HEATCAPACITY = 1
+    SUSCEPTIBILITY = 2
+    MAGNETISATION = 3
+    MAGNETICMOMENT = 4
+
     def _str2id(self, typeid):
         mappings = [['cp', 'cv', 'heatcap'], ['chi', 'susc'], ['mag', 'm(h)'], ['mom', 'm(t)']]
         for id in range(4):
@@ -461,19 +466,21 @@ class PhysicalProperties(object):
         :param temperature: the temperature in Kelvin of measurements of M(H)
         :param inverse: a boolean indicating whether susceptibility is chi or 1/chi or M(T) or 1/M(T)
         :param unit: the unit the data was measured in. Either: 'bohr', 'SI' or 'cgs'.
+        :param lambda: (susceptibility only) the value of the exchange constant in inverse susc units
+        :param chi0: (susceptibility only) the value of the residual (background) susceptibility
 
         typeid is required in all cases, and all other parameters may be specified as keyword arguments.
         otherwise the syntax is:
 
         PhysicalProperties('Cp')  # No further parameters required for heat capacity
-        PhysicalProperties('chi', hdir, inverse, unit)
+        PhysicalProperties('chi', hdir, inverse, unit, lambda, chi0)
         PhysicalProperties('chi', unit)
         PhysicalProperties('mag', hdir, temp, unit)
         PhysicalProperties('mag', unit)
         PhysicalProperties('M(T)', hmag, hdir, inverse, unit)
         PhysicalProperties('M(T)', unit)
 
-        Defaults are: hdir=[0, 0, 1]; hmag=1; temp=1; inverse=False; unit='cgs'.
+        Defaults are: hdir=[0, 0, 1]; hmag=1; temp=1; inverse=False; unit='cgs'; lambda=chi0=0.
         """
         self._physpropUnit = 'cgs'
         self._suscInverseFlag = False
@@ -481,6 +488,7 @@ class PhysicalProperties(object):
         self._hmag = 1.
         self._physpropTemperature = 1.
         self._lambda = 0.    # Exchange parameter (for susceptibility only)
+        self._chi0 = 0.      # Residual/background susceptibility (for susceptibility only)
         self._typeid = self._str2id(typeid) if isinstance(typeid, string_types) else int(typeid)
         try:
             initialiser = getattr(self, 'init' + str(self._typeid))
@@ -532,11 +540,11 @@ class PhysicalProperties(object):
 
     @property
     def Inverse(self):
-        return self._suscInverseFlag if (self._typeid == 2 or self._typeid == 4) else None
+        return self._suscInverseFlag if (self._typeid == self.SUSCEPTIBILITY or self._typeid == self.MAGNETICMOMENT) else None
 
     @Inverse.setter
     def Inverse(self, value):
-        if (self._typeid == 2 or self._typeid == 4):
+        if (self._typeid == self.SUSCEPTIBILITY or self._typeid == self.MAGNETICMOMENT):
             if isinstance(value, string_types):
                 self._suscInverseFlag = value.lower() in ['true', 't', '1', 'yes', 'y']
             else:
@@ -546,40 +554,49 @@ class PhysicalProperties(object):
 
     @property
     def Hdir(self):
-        return self._hdir if (self._typeid > 1) else None
+        return self._hdir if (self._typeid != self.HEATCAPACITY) else None
 
     @Hdir.setter
     def Hdir(self, value):
-        if (self._typeid > 1):
+        if (self._typeid != self.HEATCAPACITY):
             self._hdir = self._checkhdir(value)
 
     @property
     def Hmag(self):
-        return self._hmag if (self._typeid == 4) else None
+        return self._hmag if (self._typeid == self.MAGNETICMOMENT) else None
 
     @Hmag.setter
     def Hmag(self, value):
-        if (self._typeid == 4):
+        if (self._typeid == self.MAGNETICMOMENT):
             self._hmag = float(value)
 
     @property
     def Temperature(self):
-        return self._physpropTemperature if (self._typeid == 3) else None
+        return self._physpropTemperature if (self._typeid == self.MAGNETISATION) else None
 
     @Temperature.setter
     def Temperature(self, value):
-        if (self._typeid == 3):
+        if (self._typeid == self.MAGNETISATION):
             self._physpropTemperature = float(value)
 
     @property
     def Lambda(self):
-        return self._lambda if (self._typeid == 2) else None
+        return self._lambda if (self._typeid == self.SUSCEPTIBILITY) else None
 
     @Lambda.setter
     def Lambda(self, value):
-        if (self._typeid == 2):
+        if (self._typeid == self.SUSCEPTIBILITY):
             self._lambda = float(value)
 
+    @property
+    def Chi0(self):
+        return self._chi0 if (self._typeid == self.SUSCEPTIBILITY) else None
+
+    @Chi0.setter
+    def Chi0(self, value):
+        if (self._typeid == self.SUSCEPTIBILITY):
+            self._chi0 = float(value)
+
     def init1(self, *args, **kwargs):
         """ Initialises environment for heat capacity data """
         if len(args) > 0:
@@ -602,7 +619,7 @@ class PhysicalProperties(object):
 
     def init2(self, *args, **kwargs):
         """ Initialises environment for susceptibility data """
-        mapping = ['Hdir', 'Inverse', 'Unit', 'Lambda']
+        mapping = ['Hdir', 'Inverse', 'Unit', 'Lambda', 'Chi0']
         self._parseargs(mapping, *args, **kwargs)
 
     def init3(self, *args, **kwargs):
@@ -620,35 +637,38 @@ class PhysicalProperties(object):
         types = ['CrystalFieldHeatCapacity', 'CrystalFieldSusceptibility',
                  'CrystalFieldMagnetisation', 'CrystalFieldMoment']
         out = 'name=%s' % (types[self._typeid - 1])
-        if self._typeid > 1:
+        if self._typeid != self.HEATCAPACITY:
             out += ',Unit=%s' % (self._physpropUnit)
             if 'powder' in self._hdir:
                 out += ',powder=1'
             else:
                 out += ',Hdir=(%s)' % (','.join([str(hh) for hh in self._hdir]))
-            if self._typeid == 3:  # magnetisation M(H)
+            if self._typeid == self.MAGNETISATION:
                 out += ',Temperature=%s' % (self._physpropTemperature)
             else:            # either susceptibility or M(T)
                 out += ',inverse=%s' % (1 if self._suscInverseFlag else 0)
-                out += (',Hmag=%s' % (self._hmag)) if self._typeid==3 else ''
-                if self._typeid == 2 and self._lambda != 0:
+                out += (',Hmag=%s' % (self._hmag)) if self._typeid == self.MAGNETISATION else ''
+                if self._typeid == self.SUSCEPTIBILITY and self._lambda != 0:
                     out += ',Lambda=%s' % (self._lambda)
+                if self._typeid == self.SUSCEPTIBILITY and self._chi0 != 0:
+                    out += ',Chi0=%s' % (self._chi0)
         return out
 
     def getAttributes(self, dataset=None):
         """Returns a dictionary of PhysicalProperties attributes for use with IFunction"""
         dataset = '' if dataset is None else str(dataset)
         out = {}
-        if self._typeid > 1:
+        if self._typeid != self.HEATCAPACITY:
             out['Unit%s' % (dataset)] = self._physpropUnit
             if 'powder' in self._hdir:
                 out['powder%s' % (dataset)] = 1
             else:
                 out['Hdir%s' % (dataset)] = [float(hh) for hh in self._hdir] # needs to be list
-            if self._typeid != 3:  # either susceptibility or M(T)
+            if self._typeid != self.MAGNETISATION:  # either susceptibility or M(T)
                 out['inverse%s' % (dataset)] = 1 if self._suscInverseFlag else 0
-                if self._typeid==3:
+                if self._typeid == self.MAGNETICMOMENT:
                     out['Hmag%s' % (dataset)] = self._hmag
-                if self._typeid == 2 and self._lambda != 0:
-                    out['Lambda%s=' % (dataset)] = self._lambda
+                if self._typeid == self.SUSCEPTIBILITY:
+                    out['Lambda%s' % (dataset)] = self._lambda
+                    out['Chi0%s' % (dataset)] = self._chi0
         return out
diff --git a/scripts/Inelastic/CrystalField/normalisation.py b/scripts/Inelastic/CrystalField/normalisation.py
index cd2f9178697fbb906f32440c10419028d70c6ef1..f1df0597b84e9211e7e6ebd6792d23513d495548 100644
--- a/scripts/Inelastic/CrystalField/normalisation.py
+++ b/scripts/Inelastic/CrystalField/normalisation.py
@@ -1,6 +1,7 @@
 from __future__ import (absolute_import, division, print_function)
 import numpy as np
 from CrystalField.energies import energies as CFEnergy
+from CrystalField.fitting import ionname2Nre
 from six import iteritems
 from six import string_types
 
@@ -9,13 +10,14 @@ def _get_normalisation(nre, bnames):
     """ Helper function to calculate the normalisation factor.
         Defined as: ||Blm|| = sum_{Jz,Jz'} |<Jz|Blm|Jz'>|^2 / (2J+1)
     """
-    J = [0, 5./2, 4, 9./2, 4, 5./2, 0, 7./2, 6, 15./2, 8, 15./2, 6, 7./2]
+    Jvals = [0, 5./2, 4, 9./2, 4, 5./2, 0, 7./2, 6, 15./2, 8, 15./2, 6, 7./2]
+    J = (-nre / 2.) if (nre < 0) else Jvals[nre]
     retval = {}
     for bname in bnames:
         bdict = {bname: 1}
         ee, vv, ham = CFEnergy(nre, **bdict)
         Omat = np.mat(ham)
-        norm = np.trace(np.real(Omat * np.conj(Omat))) / (2*J[nre]+1)
+        norm = np.trace(np.real(Omat * np.conj(Omat))) / (2*J+1)
         retval[bname] = np.sqrt(np.abs(norm)) * np.sign(norm)
     return retval
 
@@ -25,12 +27,11 @@ def _parse_args(**kwargs):
     """
     # Some definitions
     Blms = ['B20', 'B21', 'B22', 'B40', 'B41', 'B42', 'B43', 'B44', 'B60', 'B61', 'B62', 'B63', 'B64', 'B65', 'B66']
-    Ions = ['Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb']
     # Some Error checking
     if 'Ion' not in kwargs.keys() and 'IonNum' not in kwargs.keys():
         raise NameError('You must specify the ion using either the ''Ion'', ''IonNum'' keywords')
     if 'Ion' in kwargs.keys():
-        nre = [id for id, val in enumerate(Ions) if val == kwargs['Ion']][0] + 1
+        nre = ionname2Nre(kwargs['Ion'])
     else:
         nre = kwargs['IonNum']
     # Now parses the list of input crystal field parameters
@@ -184,9 +185,8 @@ def split2range(*args, **kwargs):
     # Error checking
     if 'Ion' not in argin.keys() and 'IonNum' not in argin.keys():
         raise NameError('You must specify the ion using either the ''Ion'', ''IonNum'' keywords')
-    Ions = ['Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb']
     if 'Ion' in argin.keys():
-        nre = [ id for id,val in enumerate(Ions) if val==kwargs['Ion'] ][0] + 1
+        nre = ionname2Nre(kwargs['Ion'])
     else:
         nre = argin['IonNum']
     if 'EnergySplitting' not in argin.keys():
diff --git a/scripts/Interface/ui/sans_isis/beam_centre.py b/scripts/Interface/ui/sans_isis/beam_centre.py
index e59f2dfdd455bf0371458ff40c2c92b2145ae27a..55053098c22597caeaf863e3134b813bcb95616d 100644
--- a/scripts/Interface/ui/sans_isis/beam_centre.py
+++ b/scripts/Interface/ui/sans_isis/beam_centre.py
@@ -39,7 +39,7 @@ class BeamCentre(QtGui.QWidget, ui_beam_centre.Ui_BeamCentre):
         self.log_widget = MantidQt.MantidWidgets.MessageDisplay(self.groupBox_2)
         self.log_widget.setMinimumSize(QtCore.QSize(491, 371))
         self.log_widget.setObjectName(_fromUtf8("log_widget"))
-        self.gridLayout_4.addWidget(self.log_widget, 0, 1, 4, 1)
+        self.gridLayout.addWidget(self.log_widget, 0, 1, 4, 1)
         self.log_widget.setSource("CentreFinder")
         self.log_widget.attachLoggingChannel()
 
@@ -77,6 +77,9 @@ class BeamCentre(QtGui.QWidget, ui_beam_centre.Ui_BeamCentre):
         self.r_min_line_edit.setValidator(positive_double_validator)
         self.r_max_line_edit.setValidator(positive_double_validator)
 
+        self.q_min_line_edit.setValidator(positive_double_validator)
+        self.q_max_line_edit.setValidator(positive_double_validator)
+
         self.max_iterations_line_edit.setValidator(positive_integer_validator)
         self.tolerance_line_edit.setValidator(positive_double_validator)
 
@@ -90,6 +93,10 @@ class BeamCentre(QtGui.QWidget, ui_beam_centre.Ui_BeamCentre):
         self.tolerance = options.tolerance
         self.left_right = options.left_right
         self.up_down = options.up_down
+        self.verbose = options.verbose
+        self.COM = options.COM
+        self.q_min = options.q_min
+        self.q_max = options.q_max
 
     def update_simple_line_edit_field(self, line_edit, value):
         gui_element = getattr(self, line_edit)
@@ -127,6 +134,22 @@ class BeamCentre(QtGui.QWidget, ui_beam_centre.Ui_BeamCentre):
     def r_max(self, value):
         self.update_simple_line_edit_field(line_edit="r_max_line_edit", value=value)
 
+    @property
+    def q_max(self):
+        return self.get_simple_line_edit_field(line_edit="q_max_line_edit", expected_type=float)
+
+    @q_max.setter
+    def q_max(self, value):
+        self.update_simple_line_edit_field(line_edit="q_max_line_edit", value=value)
+
+    @property
+    def q_min(self):
+        return self.get_simple_line_edit_field(line_edit="q_min_line_edit", expected_type=float)
+
+    @q_min.setter
+    def q_min(self, value):
+        self.update_simple_line_edit_field(line_edit="q_min_line_edit", value=value)
+
     @property
     def max_iterations(self):
         return self.get_simple_line_edit_field(line_edit="max_iterations_line_edit", expected_type=int)
@@ -159,6 +182,22 @@ class BeamCentre(QtGui.QWidget, ui_beam_centre.Ui_BeamCentre):
     def up_down(self, value):
         self.up_down_check_box.setChecked(value)
 
+    @property
+    def verbose(self):
+        return self.verbose_check_box.isChecked()
+
+    @verbose.setter
+    def verbose(self, value):
+        self.verbose_check_box.setChecked(value)
+
+    @property
+    def COM(self):
+        return self.COM_check_box.isChecked()
+
+    @COM.setter
+    def COM(self, value):
+        self.COM_check_box.setChecked(value)
+
     @property
     def lab_pos_1(self):
         return self.get_simple_line_edit_field(line_edit="lab_pos_1_line_edit", expected_type=float)
diff --git a/scripts/Interface/ui/sans_isis/beam_centre.ui b/scripts/Interface/ui/sans_isis/beam_centre.ui
index 4cd96068f38d2638d186e68517e4445c44378c49..f81c12ed85011f5ee209da8850130eab0f9e8579 100644
--- a/scripts/Interface/ui/sans_isis/beam_centre.ui
+++ b/scripts/Interface/ui/sans_isis/beam_centre.ui
@@ -7,55 +7,58 @@
     <x>0</x>
     <y>0</y>
     <width>1015</width>
-    <height>418</height>
+    <height>527</height>
    </rect>
   </property>
   <property name="windowTitle">
    <string>Form</string>
   </property>
-  <layout class="QVBoxLayout" name="verticalLayout_3">
+  <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QGroupBox" name="groupBox_2">
      <property name="title">
       <string>Beam Centre Finder</string>
      </property>
-     <layout class="QGridLayout" name="gridLayout_4">
+     <layout class="QGridLayout" name="gridLayout">
       <item row="0" column="0">
        <widget class="QGroupBox" name="groupBox_3">
+        <property name="toolTip">
+         <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The centre positions of both the high angle and low angle detector banks. The values here if they exist will be used in any reduction initiated from the runs tab.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+        </property>
         <property name="title">
          <string>Centre Position</string>
         </property>
-        <layout class="QGridLayout" name="gridLayout">
-         <item row="3" column="0" colspan="4">
-          <widget class="QLineEdit" name="hab_pos_1_line_edit"/>
-         </item>
-         <item row="1" column="0" colspan="4">
-          <widget class="QLineEdit" name="lab_pos_1_line_edit"/>
-         </item>
-         <item row="0" column="0" colspan="2">
+        <layout class="QGridLayout" name="gridLayout_4">
+         <item row="0" column="0">
           <widget class="QLabel" name="lab_centre_label">
            <property name="text">
             <string>Centre Position LAB</string>
            </property>
           </widget>
          </item>
-         <item row="1" column="4">
-          <widget class="QLineEdit" name="lab_pos_2_line_edit"/>
+         <item row="1" column="0">
+          <widget class="QLineEdit" name="lab_pos_1_line_edit"/>
          </item>
-         <item row="3" column="4">
-          <widget class="QLineEdit" name="hab_pos_2_line_edit"/>
+         <item row="1" column="1">
+          <widget class="QLineEdit" name="lab_pos_2_line_edit"/>
          </item>
-         <item row="2" column="0" colspan="2">
+         <item row="2" column="0">
           <widget class="QLabel" name="hab_centre_label">
            <property name="text">
             <string>Centre Position HAB</string>
            </property>
           </widget>
          </item>
+         <item row="3" column="0">
+          <widget class="QLineEdit" name="hab_pos_1_line_edit"/>
+         </item>
+         <item row="3" column="1">
+          <widget class="QLineEdit" name="hab_pos_2_line_edit"/>
+         </item>
         </layout>
        </widget>
       </item>
-      <item row="0" column="1" rowspan="4">
+      <item row="0" column="1" rowspan="3">
        <widget class="QWidget" name="log_widget" native="true">
         <property name="minimumSize">
          <size>
@@ -70,73 +73,154 @@
         <property name="title">
          <string>Options</string>
         </property>
-        <layout class="QGridLayout" name="gridLayout_2">
-         <item row="0" column="1">
-          <widget class="QLabel" name="label_8">
+        <layout class="QGridLayout" name="gridLayout_3">
+         <item row="7" column="0" colspan="2">
+          <widget class="QLabel" name="label_10">
            <property name="text">
-            <string>Radius Limits(mm)</string>
+            <string>Max iterations</string>
            </property>
           </widget>
          </item>
-         <item row="1" column="0">
-          <widget class="QLabel" name="label_7">
+         <item row="8" column="2">
+          <widget class="QLineEdit" name="tolerance_line_edit">
+           <property name="toolTip">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This is the minimum step size below which the algorithm will decide it has converged.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+         <item row="7" column="2">
+          <widget class="QLineEdit" name="max_iterations_line_edit">
+           <property name="toolTip">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The maximum number of iterations the algorithm will perform.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+         <item row="5" column="4">
+          <widget class="QLineEdit" name="q_max_line_edit"/>
+         </item>
+         <item row="8" column="0">
+          <widget class="QLabel" name="label_11">
            <property name="text">
-            <string>from</string>
+            <string>Tolerance</string>
            </property>
           </widget>
          </item>
-         <item row="1" column="1">
-          <widget class="QLineEdit" name="r_min_line_edit"/>
+         <item row="5" column="3">
+          <widget class="QLabel" name="label">
+           <property name="text">
+            <string>to</string>
+           </property>
+          </widget>
          </item>
-         <item row="1" column="2">
+         <item row="2" column="3">
           <widget class="QLabel" name="label_9">
            <property name="text">
             <string>to</string>
            </property>
           </widget>
          </item>
-         <item row="1" column="3">
-          <widget class="QLineEdit" name="r_max_line_edit"/>
-         </item>
          <item row="2" column="0">
-          <widget class="QLabel" name="label_10">
+          <widget class="QLabel" name="label_7">
            <property name="text">
-            <string>Max iterations</string>
+            <string>from</string>
            </property>
           </widget>
          </item>
-         <item row="2" column="1">
-          <widget class="QLineEdit" name="max_iterations_line_edit"/>
-         </item>
-         <item row="3" column="0">
-          <widget class="QLabel" name="label_11">
+         <item row="5" column="0">
+          <widget class="QLabel" name="label_3">
            <property name="text">
-            <string>Tolerance</string>
+            <string>from</string>
            </property>
           </widget>
          </item>
-         <item row="3" column="1">
-          <widget class="QLineEdit" name="tolerance_line_edit"/>
+         <item row="2" column="4">
+          <widget class="QLineEdit" name="r_max_line_edit"/>
          </item>
-         <item row="4" column="0">
-          <widget class="QLabel" name="label_6">
+         <item row="3" column="2">
+          <widget class="QLabel" name="label_2">
+           <property name="toolTip">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The Q range which will be considered when finding the beam centre.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
            <property name="text">
-            <string>Find Beam Centre for:</string>
+            <string>Q Limits</string>
            </property>
           </widget>
          </item>
-         <item row="4" column="1">
-          <widget class="QCheckBox" name="left_right_check_box">
+         <item row="1" column="2">
+          <widget class="QLabel" name="label_8">
+           <property name="toolTip">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The radius limits which will be considered when finding the beam centre.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
            <property name="text">
-            <string>Left/Right</string>
+            <string>Radius Limits(mm)</string>
            </property>
           </widget>
          </item>
-         <item row="4" column="3">
-          <widget class="QCheckBox" name="up_down_check_box">
-           <property name="text">
-            <string>Up/Down</string>
+         <item row="5" column="2">
+          <widget class="QLineEdit" name="q_min_line_edit"/>
+         </item>
+         <item row="11" column="0" colspan="5">
+          <widget class="QGroupBox" name="groupBox_6">
+           <property name="title">
+            <string>General Options</string>
+           </property>
+           <layout class="QHBoxLayout" name="horizontalLayout">
+            <item>
+             <widget class="QCheckBox" name="verbose_check_box">
+              <property name="toolTip">
+               <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If true the reduced quartile workspaces from each iteration will be output to the ADS.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+              </property>
+              <property name="text">
+               <string>Verbose</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="COM_check_box">
+              <property name="toolTip">
+               <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Option which controls whether the initial position is taken as the current centre position or determined from a centre of mass analysis of the scatter workspace.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+              </property>
+              <property name="text">
+               <string>Initial COM</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+         </item>
+         <item row="2" column="2">
+          <widget class="QLineEdit" name="r_min_line_edit"/>
+         </item>
+         <item row="13" column="0" colspan="5">
+          <widget class="QGroupBox" name="groupBox">
+           <property name="toolTip">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Controls in which directions the algorithm will search for the beam centre.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+           <property name="title">
+            <string>Direction</string>
            </property>
+           <layout class="QHBoxLayout" name="horizontalLayout_2">
+            <item>
+             <widget class="QCheckBox" name="left_right_check_box">
+              <property name="toolTip">
+               <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+              </property>
+              <property name="text">
+               <string>Left/Right</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QCheckBox" name="up_down_check_box">
+              <property name="toolTip">
+               <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+              </property>
+              <property name="text">
+               <string>Up/Down</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
           </widget>
          </item>
         </layout>
@@ -147,9 +231,9 @@
         <property name="title">
          <string>Run</string>
         </property>
-        <layout class="QGridLayout" name="gridLayout_3">
-         <item row="0" column="2">
-          <spacer name="horizontalSpacer_3">
+        <layout class="QGridLayout" name="gridLayout_5">
+         <item row="0" column="0">
+          <spacer name="horizontalSpacer">
            <property name="orientation">
             <enum>Qt::Horizontal</enum>
            </property>
@@ -168,8 +252,8 @@
            </property>
           </widget>
          </item>
-         <item row="0" column="0">
-          <spacer name="horizontalSpacer">
+         <item row="0" column="2">
+          <spacer name="horizontalSpacer_3">
            <property name="orientation">
             <enum>Qt::Horizontal</enum>
            </property>
diff --git a/scripts/Muon/MaxentTools/__init__.py b/scripts/Muon/MaxentTools/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/scripts/Muon/MaxentTools/back.py b/scripts/Muon/MaxentTools/back.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ab153a904a146fc7adc1c3968fccc0e7c68b7e6
--- /dev/null
+++ b/scripts/Muon/MaxentTools/back.py
@@ -0,0 +1,43 @@
+
+from __future__ import (absolute_import, division, print_function)
+import numpy as np
+
+"""
+         ngroups, npts are array sizes
+         hists(ngroups): integer flag to say if used or not? R
+         P (integer) - not used?
+         Datum (npts,ngroups) = counts, R (and occasionally W)
+         Sigma (npts,ngroups) = error values R
+         corr(npts,ngroups) - not used?
+"""
+
+
+def BACK(hists, datum, sigma, DETECT_e, filePHASE, mylog):
+
+    (npts, ngroups) = datum.shape
+    DETECT_d = np.zeros([ngroups])
+    for j in range(ngroups):
+        if(hists[j] > 0):
+            Ax = np.sum(DETECT_e * datum[:, j] / sigma[:, j]**2)
+            Bx = np.sum(DETECT_e**2 / sigma[:, j]**2)
+            scale = Ax / Bx
+        DETECT_d[j] = scale
+        datum[:, j] = np.where(
+            sigma[:, j] > 1.e6, datum[:, j], datum[:, j] - scale * DETECT_e)
+    ggro = 0.0
+    sum = 0.0
+    for j in range(ngroups):
+        if(hists[j] > 0):
+            ggro = ggro + 1.0
+            sum = sum + DETECT_d[j]
+    sum = sum / ggro
+    amp = np.where(hists > 0, DETECT_d / sum, 0.0)
+    # read PHASE.DAT, was in degrees, one number per line. Present filePHASE
+    # is in radians
+    FASE_phase = filePHASE
+    DETECT_a = amp * np.cos(FASE_phase)
+    DETECT_b = amp * np.sin(FASE_phase)
+    # convert to self.log().information() if self is passed in
+    mylog.debug("phases as loaded" + str(filePHASE))
+    mylog.debug("exponentials as fitted" + str(DETECT_d))
+    return datum, DETECT_a, DETECT_b, DETECT_d, FASE_phase
diff --git a/scripts/Muon/MaxentTools/chinow.py b/scripts/Muon/MaxentTools/chinow.py
new file mode 100644
index 0000000000000000000000000000000000000000..88c41d4ee7750b7cc9b19a11ed74acb613a9c90b
--- /dev/null
+++ b/scripts/Muon/MaxentTools/chinow.py
@@ -0,0 +1,21 @@
+
+from __future__ import (absolute_import, division, print_function)
+import numpy as np
+from Muon.MaxentTools.chosol import  CHOSOL
+
+# translation of chinow.for
+"""
+
+      END
+"""
+# JSL: ASSUME all of arrays SPACE_c1 etc are used, i.e. m=3 and size=3
+
+
+def CHINOW(ax, SPACE_c1, SPACE_c2, SPACE_s1, SPACE_s2, mylog):
+    bx = 1. - ax
+    a = bx * SPACE_c2 - ax * SPACE_s2
+    b = -(bx * SPACE_c1 - ax * SPACE_s1)
+    SPACE_beta = CHOSOL(a, b, mylog)
+    z = np.dot(SPACE_c2, SPACE_beta)
+    w = np.sum(SPACE_beta * (SPACE_c1 + 0.5 * z))
+    return 1. + w, SPACE_beta
diff --git a/scripts/Muon/MaxentTools/chosol.py b/scripts/Muon/MaxentTools/chosol.py
new file mode 100644
index 0000000000000000000000000000000000000000..fdf926926ece3f206d9719a1794a983b956cd7d6
--- /dev/null
+++ b/scripts/Muon/MaxentTools/chosol.py
@@ -0,0 +1,77 @@
+import numpy as np
+import math
+
+"""
+   a,b: arrays
+   n : size (to use)
+   returns x (array) (was parameter by reference, modified in place)
+"""
+
+
+def CHOSOL_old(a, b, n):
+
+    assert(n == 3)
+    assert(a.shape == (3, 3))
+    assert(b.shape == (3,))
+    L = np.zeros([n, n])
+    L[0, 0] = math.sqrt(a[0, 0])
+    for i in range(1, n):
+        L[i, 0] = a[i, 0] / L[0, 0]
+        for j in range(1, i + 1):
+            z = a[i, j] - np.dot(L[i, :j], L[j, :j])
+            if(z < 0):
+                raise UserWarning(
+                    "trapped a negative square root in CHOSOL_old")
+                z = 1.E-10
+            if(j == i):
+                L[i, j] = math.sqrt(z)
+            else:
+                L[i, j] = z / L[j, j]
+
+    bl = np.zeros([n])
+    bl[0] = b[0] / L[0, 0]
+    for i in range(1, n):
+        z = np.dot(L[i, :i], bl[:i])
+        bl[i] = (b[i] - z) / L[i, i]
+    x = np.zeros([n])
+    x[n - 1] = bl[n - 1] / L[n - 1, n - 1]
+    for i in range(n - 2, -1, -1):
+        z = np.dot(L[i + 1:n, i], x[i + 1:n])
+        x[i] = (bl[i] - z) / L[i, i]
+    return x
+
+# solves ax=b by Cholesky decomposition
+# only uses lower half of array a
+# equivalent to np.linalg.solve(a,b) IF a is actually symmetric (Hermitian)
+# exact equivalent code follows:
+
+
+def CHOSOL(a, b, mylog):
+    n = a.shape[0]
+    try:
+        L = np.linalg.cholesky(a)
+    except:
+        mylog.warning("np.linalg.cholesky failed, trying backup")
+        mylog.debug("array a is " + str(a))
+        L = np.zeros([n, n])
+        L[0, 0] = math.sqrt(a[0, 0])
+        for i in range(1, n):
+            L[i, 0] = a[i, 0] / L[0, 0]
+            for j in range(1, i + 1):
+                z = a[i, j] - np.dot(L[i, :j], L[j, :j])
+                if(z < 0):
+                    mylog.warning(
+                        "trapped a negative square root in CHOSOL backup code")
+                    z = 1.E-10
+                if(j == i):
+                    L[i, j] = math.sqrt(z)
+                else:
+                    L[i, j] = z / L[j, j]
+
+    bl = np.zeros([n])
+    for i in range(n):
+        bl[i] = (b[i] - np.dot(L[i, :], bl)) / L[i, i]
+    x = np.zeros([n])
+    for i in range(n - 1, -1, -1):
+        x[i] = (bl[i] - np.dot(L[:, i], x)) / L[i, i]
+    return x
diff --git a/scripts/Muon/MaxentTools/deadfit.py b/scripts/Muon/MaxentTools/deadfit.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ca49add33517ad2773ccc141e9cca6bdd91eccb
--- /dev/null
+++ b/scripts/Muon/MaxentTools/deadfit.py
@@ -0,0 +1,43 @@
+
+from __future__ import (absolute_import, division, print_function)
+import numpy as np
+from Muon.MaxentTools.zft import ZFT
+
+"""
+     Works with the dead time data
+"""
+
+
+def DEADFIT(
+            datum, sigma, datt, DETECT_a, DETECT_b, DETECT_d, DETECT_e, RUNDATA_res, RUNDATA_frames, RUNDATA_fnorm, RUNDATA_hists,
+            MAXPAGE_n, MAXPAGE_f, PULSESHAPE_convol, SAVETIME_I2, mylog):
+    (npts, ngroups) = datt.shape
+
+    zr, zi = ZFT(MAXPAGE_f, PULSESHAPE_convol, DETECT_e, SAVETIME_I2)
+    # new numpy array code
+    isig = sigma**-2
+    wiggle = np.outer(zr, DETECT_a) + np.outer(zi, DETECT_b)
+    diff = datt - wiggle
+    Ax = np.dot(DETECT_e**2, isig)
+    Bx = np.dot(DETECT_e, isig * diff)
+    Cx = np.dot(DETECT_e, isig * datt**2)
+    Dx = np.sum(datt**4 * isig, axis=0)
+    Ex = np.sum(diff * datt**2 * isig, axis=0)
+    tau = (Bx * Cx - Ax * Ex) / (Ax * Dx - Cx * Cx)
+    scale = (Bx / Ax) + tau * (Cx / Ax)
+    SENSE_taud = tau * RUNDATA_res * \
+        RUNDATA_hists * RUNDATA_frames * RUNDATA_fnorm
+    DETECT_c = scale - DETECT_d
+    DETECT_d = scale
+    corr = datt**2 * tau[np.newaxis, :]
+    datum = np.where(
+        sigma <= 1.E3,
+        datt +
+        corr -
+        np.outer(DETECT_e, DETECT_d),
+        datum)
+
+    mylog.debug("amplitudes of exponentials:" + str(DETECT_d))
+    mylog.debug("changes this cycle:" + str(DETECT_c))
+    mylog.debug("DEADTIMES:" + str(SENSE_taud))
+    return datum, corr, DETECT_c, DETECT_d, SENSE_taud
diff --git a/scripts/Muon/MaxentTools/dist.py b/scripts/Muon/MaxentTools/dist.py
new file mode 100644
index 0000000000000000000000000000000000000000..b3fffab3a3056e7361b1525bdb2e816d50177b7e
--- /dev/null
+++ b/scripts/Muon/MaxentTools/dist.py
@@ -0,0 +1,9 @@
+
+from __future__ import (absolute_import, division, print_function)
+import numpy as np
+
+
+# inner loop Z=sum_L(S2(K,L)*beta_L)
+# outer loop W=sum(beta(k)*Z(k))
+def DIST(SPACE_beta, SPACE_s2):
+    return -np.dot(SPACE_beta, np.dot(SPACE_s2, SPACE_beta))
diff --git a/scripts/Muon/MaxentTools/input.py b/scripts/Muon/MaxentTools/input.py
new file mode 100644
index 0000000000000000000000000000000000000000..f97604ee9dfb16e708fd66fc58aa587c10ddea5f
--- /dev/null
+++ b/scripts/Muon/MaxentTools/input.py
@@ -0,0 +1,84 @@
+
+from __future__ import (absolute_import, division, print_function)
+import numpy as np
+import math
+"""
+    raw data was in rdata[hist,bin]
+    work in progress depending on who calls INPUT and why...
+    transfer data from rdata[hists, raw bins] to datum,datt[groups, bins starting t0 and length npts]
+    rdata[itzero]=t0 (might be off the start of the array), rdata[i1stgood]=first good rdata[itotal]=last good
+   eliminate intermediate array "coldstore". Even "rdata" is somewhat obsolete...
+"""
+
+# parameter p not used!
+
+
+def INPUT(
+          nhists,
+          ngroups,
+          npts,
+          CHANNELS_itzero,
+          CHANNELS_i1stgood,
+          CHANNELS_itotal,
+          RUNDATA_res,
+          RUNDATA_frames,
+          GROUPING_group,
+          DATALL_rdata,
+          FAC_factor,
+          SENSE_taud,
+          mylog):
+
+    datum = np.zeros([npts, ngroups])
+    sigma = np.zeros([npts, ngroups])
+    RUNDATA_hists = np.zeros([ngroups])
+    if(CHANNELS_i1stgood < CHANNELS_itzero):
+        CHANNELS_i1stgood = CHANNELS_itzero
+    i1 = CHANNELS_i1stgood - CHANNELS_itzero
+    i2 = CHANNELS_itotal - CHANNELS_itzero
+    mylog.debug("rdata has shape [{0}]".format(DATALL_rdata.shape))
+    mylog.debug("datum has shape [{0}]".format(datum.shape))
+    for j in range(nhists):
+        idest = GROUPING_group[j]
+        if(idest >= 0):
+            mylog.debug("adding rdata[{0},{1}:{2}] to datum[{3}:{4},{5}]".format(
+                j,
+                CHANNELS_i1stgood,
+                CHANNELS_itotal,
+                i1,
+                i2,
+                idest))
+            datum[i1:i2,idest] += DATALL_rdata[j,CHANNELS_i1stgood:CHANNELS_itotal]
+            RUNDATA_hists[idest] += 1
+    totfcounts = np.sum(datum)
+    MISSCHANNELS_mm = np.count_nonzero(RUNDATA_hists == 0)
+    gg = np.nonzero(RUNDATA_hists)[
+                    0]  # indices of "good" groups with at least one data set
+    mylog.notice(
+             'Good muons (used for normn. to 10 Mev)={0}'.format(totfcounts))
+    mylog.debug("group, no. of hists")
+    for i in range(ngroups):
+        mylog.debug("{0} {1}".format(i, RUNDATA_hists[i]))
+    # normalise to 10 Mevents
+    RUNDATA_fnorm = 1.E7 / totfcounts
+    sigma[:] = 1.E15  # default for "no data here"
+    sigma[i1:i2, gg] = np.sqrt(datum[i1:i2, gg] + 2.)*RUNDATA_fnorm
+    datum[i1:i2, gg] *= RUNDATA_fnorm
+    datt = np.array(datum)
+    tau = SENSE_taud / (RUNDATA_res*RUNDATA_hists*RUNDATA_frames*RUNDATA_fnorm)
+    overloads = np.argwhere(datum*tau[np.newaxis,:] > 1.0)
+    for (i, j) in overloads:
+        mylog.warning(
+            "overload [{0},{1}]: count={2} taud={3}".format(i, j, datum[i, j], tau[j]))
+    corr = tau[np.newaxis,:] * datum**2
+    sigma = np.where(datum > 0.5, sigma*(1.+0.5*corr/datum), sigma)
+    datum = datum + corr
+
+    FAC_ratio = math.sqrt(
+            float((i2-i1)*len(gg)) / float(npts*(ngroups-MISSCHANNELS_mm)) )
+    FAC_facfake = FAC_factor * FAC_ratio
+    sigma = sigma * FAC_facfake
+    zerosigs = np.argwhere(sigma == 0.0)
+    for (i, j) in zerosigs:
+        mylog.warning("ZERO SIG!!! [{0},{1}]".format(i, j))
+
+    return (datum, sigma, corr, datt, MISSCHANNELS_mm, RUNDATA_fnorm, RUNDATA_hists, FAC_facfake, FAC_ratio)
diff --git a/scripts/Muon/MaxentTools/maxent.py b/scripts/Muon/MaxentTools/maxent.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b8c25388860238b230f568c9f9805b1bbbb8ba9
--- /dev/null
+++ b/scripts/Muon/MaxentTools/maxent.py
@@ -0,0 +1,136 @@
+import numpy as np
+import math
+from Muon.MaxentTools.opus import OPUS
+from Muon.MaxentTools.tropus import TROPUS
+from Muon.MaxentTools.project import PROJECT
+from Muon.MaxentTools.move import MOVE
+
+# translated from MAXENT.for
+"""
+
+"""
+# common /SPACE/ is used only here and below
+# max changed to itermax to avoid overwriting built in function
+
+
+def warningMsg(values,name,mylog):
+    if(not np.all(np.isfinite(values))):
+        mylog.warning(name+" has some NaNs or Infs")
+
+
+def MAXENT(datum, sigma, flat, base, itermax, sumfix, SAVETIME_ngo, MAXPAGE_n, MAXPAGE_f, PULSESHAPE_convol,
+           DETECT_a, DETECT_b, DETECT_e, FAC_factor, FAC_facfake, SAVETIME_i2, mylog, prog):
+    npts, ngroups = datum.shape
+    p = npts*ngroups
+    xi = np.zeros([MAXPAGE_n, 3])
+    eta = np.zeros([npts, ngroups, 3])
+    #
+    SPACE_blank = flat
+    if(SPACE_blank != 0):
+        base = np.zeros([MAXPAGE_n])
+        base[:] = SPACE_blank
+    else:
+
+        SPACE_blank = np.mean(base)
+    SPACE_chizer = float(p)
+    SPACE_chtarg = SPACE_chizer
+    HERITAGE_iter = 0
+    m = 3
+    if(SAVETIME_ngo > 0):
+        HERITAGE_iter = 1
+    else:
+        MAXPAGE_f = np.array(base)
+    test = 99. # temporary for 1st test
+    SPACE_chisq = SPACE_chizer*2. # temporary for 1st test
+    mylog.debug("entering loop with spectrum from {0} to {1}".format(np.amin(MAXPAGE_f), np.amax(MAXPAGE_f)))
+    while( HERITAGE_iter <= itermax and (HERITAGE_iter <= 1 or not (test < 0.02 and abs(SPACE_chisq/SPACE_chizer-1) < 0.01) ) ): # label 6
+        mylog.debug("start loop, iter={} ngo={} test={} chisq={}".format(HERITAGE_iter, SAVETIME_ngo, test, SPACE_chisq/SPACE_chizer))
+        mylog.debug("entering loop with spectrum from {0} to {1}".format(np.amin(MAXPAGE_f), np.amax(MAXPAGE_f)))
+        ox = OPUS(MAXPAGE_f, SAVETIME_i2, PULSESHAPE_convol, DETECT_a, DETECT_b, DETECT_e)
+        warningMsg(ox,'ox',mylog)
+        mylog.debug("ox from {0} to {1}".format(np.amin(ox), np.amax(ox)))
+        a = ox-datum
+        SPACE_chisq = np.sum(a**2/sigma**2)
+        ox = 2*a/(sigma**2)
+        cgrad = TROPUS(ox, SAVETIME_i2, PULSESHAPE_convol, DETECT_a, DETECT_b, DETECT_e)
+        warningMsg(cgrad,'cgrad',mylog)
+        mylog.debug("cgrad from {0} to {1}".format(np.amin(cgrad), np.amax(cgrad)))
+        SPACE_xsum = np.sum(MAXPAGE_f)
+        sgrad = -np.log(MAXPAGE_f/base)/SPACE_blank
+        warningMsg(sgrad,'sgrad',mylog)
+        mylog.debug("sgrad from {0} to {1}".format(np.amin(sgrad), np.amax(sgrad)))
+        snorm = math.sqrt(np.sum(sgrad**2*MAXPAGE_f))
+        cnorm = math.sqrt(np.sum(cgrad**2*MAXPAGE_f))
+        tnorm = np.sum(sgrad*cgrad*MAXPAGE_f)
+        mylog.debug("snorm={} cnorm={} tnorm={}".format(snorm, cnorm, tnorm))
+        a = 1.
+        b = 1./cnorm
+        c = 1./cnorm
+        if(HERITAGE_iter != 0):
+            test = math.sqrt(0.5*abs(1.-tnorm/(snorm*cnorm)))
+            # also eliminate NaNs!
+            if(test < 1.E-7 or not np.isfinite(test)):
+                test = 1.E-7
+            a = 1./(snorm*2.*test)
+            b = 1./(cnorm*2.*test)
+        else:
+            test = 0.
+        if( math.isnan(a) or math.isnan(b) or math.isnan(c)):
+            mylog.debug("aa={} b={} c={}".format(a, b, c))
+            raise ValueError("invalid value: a={} b={} c={}".format(a, b, c))
+        mylog.debug("a={} b={} c={}".format(a, b, c))
+        xi[:, 0] = MAXPAGE_f*c*cgrad
+        xi[:, 1] = MAXPAGE_f*(a*sgrad-b*cgrad)
+        warningMsg(xi[:,0],'xi[,0]',mylog)
+        warningMsg(xi[:,1],'xi[,1]',mylog)
+        if(sumfix):
+            PROJECT(0, MAXPAGE_n, xi)
+            PROJECT(1, MAXPAGE_n, xi)
+        eta[:,:, 0] = OPUS(xi[:, 0], SAVETIME_i2, PULSESHAPE_convol, DETECT_a, DETECT_b, DETECT_e)
+        eta[:,:, 1] = OPUS(xi[:, 1], SAVETIME_i2, PULSESHAPE_convol, DETECT_a, DETECT_b, DETECT_e)
+        warningMsg(eta[:,:, 0],"eta[,,0]",mylog)
+        warningMsg(eta[:,:, 1],"eta[,,1]",mylog)
+        ox = eta[:,:, 1]/(sigma**2)
+        xi[:, 2] = TROPUS(ox, SAVETIME_i2, PULSESHAPE_convol, DETECT_a, DETECT_b, DETECT_e)
+        warningMsg(xi[:, 2],"xi[,2]", mylog)
+        a = 1./math.sqrt(np.sum(xi[:, 2]**2*MAXPAGE_f))
+        xi[:, 2] = xi[:, 2]*MAXPAGE_f*a
+        if(sumfix):
+            PROJECT(2, MAXPAGE_n, xi)
+        eta[:,:, 2] = OPUS(xi[:, 2], SAVETIME_i2, PULSESHAPE_convol, DETECT_a, DETECT_b, DETECT_e)
+        warningMsg(eta[:,:, 2],"eta[,,2]",mylog)
+        # loop DO 17, DO 18
+        SPACE_s1 = np.dot(sgrad, xi)
+        SPACE_c1 = np.dot(cgrad, xi)/SPACE_chisq
+        # loops DO 19,DO 20, DO 21 harder to completely remove
+        SPACE_s2 = np.zeros([3, 3])
+        SPACE_c2 = np.zeros([3, 3])
+        for L in range(m):
+            for k in range(m):
+                if(L <= k):
+                    SPACE_s2[k, L] = -np.sum(xi[:, k]*xi[:, L]/MAXPAGE_f)/SPACE_blank
+                    SPACE_c2[k, L] = np.sum(eta[:,:, k]*eta[:,:, L]/sigma**2)*2./SPACE_chisq
+                else: # make symmetric
+                    SPACE_s2[k, L] = SPACE_s2[L, k]
+                    SPACE_c2[k, L] = SPACE_c2[L, k]
+        s = -np.sum(MAXPAGE_f*np.log(MAXPAGE_f/(base*math.e)))/(SPACE_blank*math.e) # spotted missing minus sign!
+        a = s*SPACE_blank*math.e/SPACE_xsum
+        mylog.notice("{:3}    {:10.4}  {:10.4}  {:10.4}  {:10.4}  {:10.4}".format(HERITAGE_iter,
+                     test, s, SPACE_chtarg, SPACE_chisq, SPACE_xsum))
+        SPACE_beta = np.array([-0.5*SPACE_c1[0]/SPACE_c2[0, 0], 0.0, 0.0])
+        warningMsg(SPACE_beta,"SPACE_beta", mylog)
+        if(HERITAGE_iter != 0):
+            (sigma, SPACE_chtarg, SPACE_beta, FAC_factor, FAC_facfake) = MOVE(
+                    sigma, SPACE_chisq, SPACE_chizer, SPACE_xsum, SPACE_c1, SPACE_c2, SPACE_s1,
+                    SPACE_s2, SPACE_blank, FAC_factor, FAC_facfake, mylog)
+        # do 23,24
+        MAXPAGE_f += np.dot(xi, SPACE_beta)
+        MAXPAGE_f = np.where(MAXPAGE_f < 0, 1.E-3*SPACE_blank, MAXPAGE_f)
+        a = np.sum(MAXPAGE_f)
+        if(sumfix):
+            MAXPAGE_f /= a
+        # 50
+        HERITAGE_iter += 1
+        prog.report("chisq="+str(SPACE_chisq))
+
+    return (sigma, base, HERITAGE_iter, MAXPAGE_f, FAC_factor, FAC_facfake)
diff --git a/scripts/Muon/MaxentTools/modab.py b/scripts/Muon/MaxentTools/modab.py
new file mode 100644
index 0000000000000000000000000000000000000000..09b03fbbc8ae838a5336dd161a5ee2a5a945c288
--- /dev/null
+++ b/scripts/Muon/MaxentTools/modab.py
@@ -0,0 +1,46 @@
+
+from __future__ import (absolute_import, division, print_function)
+import numpy as np
+from Muon.MaxentTools.zft import ZFT
+
+# translation of modab.for
+# P unused apart from passing to ZFT which doesn't use it
+# params ngroups, npts inferred from size of datum
+
+
+def MODAB(
+     hists,
+     datum,
+     sigma,
+     MISSCHANNELS_mm,
+     MAXPAGE_f,
+     PULSESHAPE_convol,
+     DETECT_e,
+     SAVETIME_I2,
+     mylog):
+    npts, ngroups = datum.shape
+    zr, zi = ZFT(MAXPAGE_f, PULSESHAPE_convol, DETECT_e, SAVETIME_I2)
+    DETECT_a = np.zeros([ngroups])
+    DETECT_b = np.zeros([ngroups])
+    # SENSE_phi=np.zeros([ngroups])
+    # AMPS_amp=np.zeros([ngroups])
+    for j in range(ngroups):
+        if(hists[j] != 0):
+            x = zr / sigma[:, j]
+            y = zi / sigma[:, j]
+            s11 = np.sum(x**2)
+            s22 = np.sum(y**2)
+            s12 = np.sum(x * y)
+            sa = np.sum(datum[:, j] * x / sigma[:, j])
+            sb = np.sum(datum[:, j] * y / sigma[:, j])
+            DETECT_a[j] = (sa * s22 - sb * s12) / (s11 * s22 - s12 * s12)
+            DETECT_b[j] = (sb * s11 - sa * s12) / (s11 * s22 - s12 * s12)
+    AMPS_amp = np.sqrt(DETECT_a**2 + DETECT_b**2)
+    SENSE_phi = np.arctan2(DETECT_b, DETECT_a)  # *180.0/math.pi
+    s = np.sum(AMPS_amp) / float(ngroups - MISSCHANNELS_mm)
+    mylog.notice("AMPLITUDES" + str(AMPS_amp / s))
+    mylog.notice("PHASES =" + str(SENSE_phi))
+    AMPS_amp = AMPS_amp / s
+    DETECT_a = DETECT_a / s
+    DETECT_b = DETECT_b / s
+    return (SENSE_phi, DETECT_a, DETECT_b, AMPS_amp)
diff --git a/scripts/Muon/MaxentTools/modamp.py b/scripts/Muon/MaxentTools/modamp.py
new file mode 100644
index 0000000000000000000000000000000000000000..e60b27d6fdddab6262b838bf603c5fb27b1bd66a
--- /dev/null
+++ b/scripts/Muon/MaxentTools/modamp.py
@@ -0,0 +1,39 @@
+
+from __future__ import (absolute_import, division, print_function)
+import numpy as np
+from Muon.MaxentTools.zft import ZFT
+
+""
+#
+
+
+def MODAMP(
+     hists,
+     datum,
+     sigma,
+     MISSCHANNELS_MM,
+     FASE_phase,
+     MAXPAGE_f,
+     PULSESHAPE_convol,
+     DETECT_e,
+     SAVETIME_I2,
+     mylog):
+    npts, ngroups = datum.shape
+    zr, zi = ZFT(MAXPAGE_f, PULSESHAPE_convol, DETECT_e, SAVETIME_I2)
+    cs = np.cos(FASE_phase)
+    sn = np.sin(FASE_phase)
+    AMPS_amp = np.zeros([ngroups])
+    for i in range(ngroups):
+        if(hists[i] != 0):
+            hij = cs[i] * zr + sn[i] * zi
+            s = np.sum(datum[:, i] * hij / (sigma[:, i]**2))
+            t = np.sum(hij**2 / (sigma[:, i]**2))
+            AMPS_amp[i] = s / t
+    v = np.sum(AMPS_amp**2) / float(ngroups - MISSCHANNELS_MM)
+    AMPS_amp = AMPS_amp / v
+    DETECT_a = AMPS_amp * cs
+    DETECT_b = AMPS_amp * sn
+    SENSE_phi = np.array(FASE_phase)  # *180.0/np.pi
+    mylog.debug("amplitudes" + str(AMPS_amp))
+    mylog.debug("fixed phases" + str(SENSE_phi))
+    return SENSE_phi, DETECT_a, DETECT_b, AMPS_amp
diff --git a/scripts/Muon/MaxentTools/modbak.py b/scripts/Muon/MaxentTools/modbak.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc8a055e8c6a81c3f84eb9a9f93ac31f3153dd8c
--- /dev/null
+++ b/scripts/Muon/MaxentTools/modbak.py
@@ -0,0 +1,35 @@
+
+from __future__ import (absolute_import, division, print_function)
+import numpy as np
+from Muon.MaxentTools.zft import ZFT
+
+
+def MODBAK(
+     hists,
+     datum,
+     sigma,
+     DETECT_a,
+     DETECT_b,
+     DETECT_e,
+     DETECT_d,
+     MAXPAGE_f,
+     PULSESHAPE_convol,
+     SAVETIME_I2,
+     mylog):
+    npts, ngroups = datum.shape
+    DETECT_c = np.zeros([ngroups])
+    zr, zi = ZFT(MAXPAGE_f, PULSESHAPE_convol, DETECT_e, SAVETIME_I2)
+    for j in range(ngroups):
+        if(hists[j] != 0):
+            isigsq = np.where(sigma[:, j] > 1.E3, 0.0, sigma[:, j]**-2)
+            diff = datum[:, j] - (DETECT_a[j] * zr + DETECT_b[j] * zi)
+            s = np.sum(diff * DETECT_e * isigsq)
+            t = np.sum(isigsq * DETECT_e)
+            scale = s / t
+            DETECT_c[j] = scale
+            DETECT_d[j] = DETECT_d[j] + scale
+            datum[:, j] = datum[:, j] - scale * \
+                np.where(sigma[:, j] > 1.E3, 0.0, DETECT_e)
+    mylog.debug("exponentials:" + str(DETECT_d))
+    mylog.debug("changes this cycle:" + str(DETECT_c))
+    return DETECT_c, DETECT_d
diff --git a/scripts/Muon/MaxentTools/move.py b/scripts/Muon/MaxentTools/move.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5d78a246f2b45484f813d908c52f123763a71a5
--- /dev/null
+++ b/scripts/Muon/MaxentTools/move.py
@@ -0,0 +1,66 @@
+
+from __future__ import (absolute_import, division, print_function)
+import math
+
+from Muon.MaxentTools.chinow import CHINOW
+from Muon.MaxentTools.dist import DIST
+
+
+# variable p is used? Size of sigma
+
+def MOVE(
+     sigma,
+     SPACE_chisq,
+     SPACE_chizer,
+     SPACE_xsum,
+     SPACE_c1,
+     SPACE_c2,
+     SPACE_s1,
+     SPACE_s2,
+     SPACE_blank,
+     FAC_factor,
+     FAC_facfake,
+     mylog):
+    (cmin, SPACE_beta) = CHINOW(0., SPACE_c1,
+                                SPACE_c2, SPACE_s1, SPACE_s2, mylog)
+    if(cmin * SPACE_chisq > SPACE_chizer):
+        ctarg = 0.5 * (1. + cmin)
+    else:
+        ctarg = SPACE_chizer / SPACE_chisq
+    a1 = 0.
+    a2 = 1.
+    jtest = 0.
+    f1 = cmin - ctarg
+    (f2, SPACE_beta) = CHINOW(1., SPACE_c1,
+                              SPACE_c2, SPACE_s1, SPACE_s2, mylog)
+    f2 = f2 - ctarg
+    while(True):
+        anew = 0.5 * (a1 + a2)
+        (fx, SPACE_beta) = CHINOW(anew, SPACE_c1,
+                                  SPACE_c2, SPACE_s1, SPACE_s2, mylog)
+        fx = fx - ctarg
+        if(f1 * fx > 0):
+            a1 = anew
+            f1 = fx
+        if(f2 * fx > 0):
+            a2 = anew
+            f2 = fx
+        if(abs(fx) >= 1.E-3):
+            jtest = jtest + 1
+            if(jtest > 10000):
+                mylog.notice(' stuck in MOVE : chi**2 not tight enough')
+                sigma = sigma * 0.99
+                FAC_factor = FAC_factor * 0.99
+                # FAC_facdef=FAC_factor
+                FAC_facfake = FAC_facfake * 0.99
+                mylog.notice(
+                    ' tightening looseness factor by 1 % to: {0}'.format(FAC_factor))
+                break
+        else:
+            break
+    w = DIST(SPACE_beta, SPACE_s2)
+    if(w > 0.1 * SPACE_xsum / SPACE_blank):
+        SPACE_beta = SPACE_beta * \
+            math.sqrt(0.1 * SPACE_xsum / (SPACE_blank * w))
+    SPACE_chtarg = ctarg * SPACE_chisq
+    return sigma, SPACE_chtarg, SPACE_beta, FAC_factor, FAC_facfake
diff --git a/scripts/Muon/MaxentTools/multimaxalpha.py b/scripts/Muon/MaxentTools/multimaxalpha.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e9d196caba0c47789e5fe08fabe35b419ea052d
--- /dev/null
+++ b/scripts/Muon/MaxentTools/multimaxalpha.py
@@ -0,0 +1,72 @@
+
+from __future__ import (absolute_import, division, print_function)
+import numpy as np
+
+from Muon.MaxentTools.input import INPUT
+from Muon.MaxentTools.start import START
+from Muon.MaxentTools.back import BACK
+from Muon.MaxentTools.maxent import MAXENT
+from Muon.MaxentTools.deadfit import DEADFIT
+from Muon.MaxentTools.modbak import MODBAK
+from Muon.MaxentTools.modamp import MODAMP
+from Muon.MaxentTools.modab import MODAB
+from Muon.MaxentTools.outspec import OUTSPEC
+
+
+def MULTIMAX(
+      POINTS_nhists, POINTS_ngroups, POINTS_npts, CHANNELS_itzero, CHANNELS_i1stgood, CHANNELS_itotal, RUNDATA_res, RUNDATA_frames,
+      GROUPING_group, DATALL_rdata, FAC_factor, SENSE_taud, MAXPAGE_n, filePHASE,
+      PULSES_def, PULSES_npulse, FLAGS_fitdead, FLAGS_fixphase, SAVETIME_i2,
+      OuterIter, InnerIter, mylog, prog, phaseconvWS, TZERO_fine):
+    #
+    base = np.zeros([MAXPAGE_n])
+    (datum, sigma, corr, datt, MISSCHANNELS_mm, RUNDATA_fnorm, RUNDATA_hists, FAC_facfake, FAC_ratio) = INPUT(
+        POINTS_nhists, POINTS_ngroups, POINTS_npts, CHANNELS_itzero, CHANNELS_i1stgood, CHANNELS_itotal,
+        RUNDATA_res, RUNDATA_frames, GROUPING_group, DATALL_rdata, FAC_factor, SENSE_taud, mylog)
+
+    (DETECT_e, PULSESHAPE_convol) = START(
+        POINTS_npts, PULSES_npulse, RUNDATA_res, MAXPAGE_n, TZERO_fine, mylog)
+
+    (datum, DETECT_a, DETECT_b, DETECT_d, FASE_phase) = BACK(
+        RUNDATA_hists, datum, sigma, DETECT_e, filePHASE, mylog)
+    SAVETIME_ngo = -1
+    MAXPAGE_f = None
+    for j in range(OuterIter):  # outer "alpha chop" iterations?
+        SAVETIME_ngo = SAVETIME_ngo + 1
+        mylog.information("CYCLE NUMBER=" + str(SAVETIME_ngo))
+        (sigma, base, HERITAGE_iter, MAXPAGE_f, FAC_factor, FAC_facfake) = MAXENT(
+            datum, sigma, PULSES_def, base, InnerIter, False,
+            SAVETIME_ngo, MAXPAGE_n, MAXPAGE_f, PULSESHAPE_convol, DETECT_a,
+            DETECT_b, DETECT_e, FAC_factor, FAC_facfake, SAVETIME_i2, mylog, prog)
+
+        if(FLAGS_fitdead):
+            (datum, corr, DETECT_c, DETECT_d, SENSE_taud) = DEADFIT(
+                datum, sigma, datt, DETECT_a, DETECT_b, DETECT_d, DETECT_e, RUNDATA_res, RUNDATA_frames, RUNDATA_fnorm, RUNDATA_hists,
+                MAXPAGE_n, MAXPAGE_f, PULSESHAPE_convol, SAVETIME_i2, mylog)
+        else:
+            (DETECT_c, DETECT_d) = MODBAK(RUNDATA_hists, datum, sigma, DETECT_a, DETECT_b,
+                                          DETECT_e, DETECT_d, MAXPAGE_f, PULSESHAPE_convol, SAVETIME_i2, mylog)
+
+        if(FLAGS_fixphase):
+            (SENSE_phi, DETECT_a, DETECT_b, AMPS_amp) = MODAMP(RUNDATA_hists, datum, sigma,
+                                                               MISSCHANNELS_mm, FASE_phase, MAXPAGE_f, PULSESHAPE_convol, DETECT_e,
+                                                               SAVETIME_i2, mylog)
+        else:
+            (SENSE_phi, DETECT_a, DETECT_b, AMPS_amp) = MODAB(RUNDATA_hists, datum, sigma,
+                                                              MISSCHANNELS_mm, MAXPAGE_f, PULSESHAPE_convol, DETECT_e, SAVETIME_i2, mylog)
+        # outout per-iteration debug info
+        if(phaseconvWS):
+            for k in range(POINTS_ngroups):
+                phaseconvWS.dataX(k)[j + 1] = (j + 1) * 1.0
+                phaseconvWS.dataY(k)[j + 1] = SENSE_phi[k]
+        prog.report((j + 1) * InnerIter, "")
+                    # finished outer loop, jump progress bar
+
+    (OUTSPEC_test, OUTSPEC_guess) = OUTSPEC(datum, MAXPAGE_f, sigma, datt, CHANNELS_itzero, CHANNELS_itotal,
+                                            PULSESHAPE_convol, FAC_ratio, DETECT_a, DETECT_b, DETECT_d, DETECT_e, SAVETIME_i2,
+                                            RUNDATA_fnorm, mylog)
+
+    return (
+                            MISSCHANNELS_mm, RUNDATA_fnorm, RUNDATA_hists, MAXPAGE_f, FAC_factor, FAC_facfake, FAC_ratio,
+                            DETECT_a, DETECT_b, DETECT_c, DETECT_d, DETECT_e, PULSESHAPE_convol, SENSE_taud, FASE_phase, SAVETIME_ngo,
+                            AMPS_amp, SENSE_phi, OUTSPEC_test, OUTSPEC_guess)
diff --git a/scripts/Muon/MaxentTools/opus.py b/scripts/Muon/MaxentTools/opus.py
new file mode 100644
index 0000000000000000000000000000000000000000..53f496203335f04d3b8bc4bd59e52f50d5084252
--- /dev/null
+++ b/scripts/Muon/MaxentTools/opus.py
@@ -0,0 +1,14 @@
+
+from __future__ import (absolute_import, division, print_function)
+import numpy as np
+
+
+def OPUS(x, SAVETIME_i2, PULSESHAPE_convol, DETECT_a, DETECT_b, DETECT_e):
+    npts = DETECT_e.shape[0]
+    n = x.shape[0]
+    y = np.zeros([SAVETIME_i2], dtype=np.complex_)
+    y[:n] = x * PULSESHAPE_convol
+    y2 = np.fft.ifft(y) * SAVETIME_i2  # SN=+1, inverse FFT without the 1/N
+    ox = (np.outer(np.real(y2[:npts]), DETECT_a) + np.outer(
+        np.imag(y2[:npts]), DETECT_b)) * DETECT_e[:, np.newaxis]
+    return ox
diff --git a/scripts/Muon/MaxentTools/outspec.py b/scripts/Muon/MaxentTools/outspec.py
new file mode 100644
index 0000000000000000000000000000000000000000..5714cd38eb62e283a51be7b38c3bd321a3032ef6
--- /dev/null
+++ b/scripts/Muon/MaxentTools/outspec.py
@@ -0,0 +1,56 @@
+
+from __future__ import (absolute_import, division, print_function)
+import numpy as np
+from Muon.MaxentTools.zft import ZFT
+
+
+def OUTSPEC(
+     datum,
+     f,
+     sigma,
+     datt,
+     CHANNELS_itzero,
+     CHANNELS_itotal,
+     PULSESHAPE_convol,
+     FAC_ratio,
+     DETECT_a,
+     DETECT_b,
+     DETECT_d,
+     DETECT_e,
+     SAVETIME_I2,
+     RUNDATA_fnorm,
+     mylog):
+
+    npts, ngroups = datum.shape
+    guess = np.zeros([npts, ngroups])
+    chi = np.zeros([ngroups])
+    zr, zi = ZFT(f, PULSESHAPE_convol, DETECT_e, SAVETIME_I2)
+    guess = np.outer(zr, DETECT_a) + np.outer(zi, DETECT_b)
+    test0 = (datum - guess) / sigma
+    test = test0 * FAC_ratio
+    chi = np.sum(test0, axis=0)
+    ibads9 = np.greater_equal(test, 5.0)
+    chi = chi / float(npts)
+    for j in range(ngroups):
+        ibads8 = np.nonzero(ibads9[:, j])[0]
+        for i in ibads8[0:10]:
+            mylog.warning(
+                "devn .gt. 5 std devs on point {:4d} of group {:2d}".format(i, j))
+        if(len(ibads8) > 10):
+            mylog.warning("... lots of baddies in group {:2d}".format(j))
+    mylog.debug("contribs to chi**2 from each group:" + str(chi))
+    nphalf =int( npts / 2)
+    tmp1 = np.where(np.less_equal(sigma[nphalf:, ], 1.E3), guess[nphalf:,:]/sigma[nphalf:,:]**2, 0.0)
+    sum = np.sum(tmp1)
+    nsum = np.count_nonzero(tmp1)
+    if(nsum == 0):
+        mylog.debug(' no data in last half, so last half sum = 0')
+    else:
+        bum = sum / nsum
+        mylog.debug("Points:{0}".format(npts))
+        mylog.debug("Last half sum:{0}".format(bum))
+    # add in backgrounds (no dead time adjustment yet)
+    guess = ((guess + np.outer(DETECT_e, DETECT_d)) /
+             RUNDATA_fnorm)  # convert from 10Mevent normalisation to original stats
+
+    return test, guess
diff --git a/scripts/Muon/MaxentTools/project.py b/scripts/Muon/MaxentTools/project.py
new file mode 100644
index 0000000000000000000000000000000000000000..7d28a711e979bbae25e89dc4a8d97314f154005d
--- /dev/null
+++ b/scripts/Muon/MaxentTools/project.py
@@ -0,0 +1,9 @@
+
+from __future__ import (absolute_import, division, print_function)
+import numpy as np
+
+
+def PROJECT(k, n, xi):  # xi modified in place
+    a = np.sum(xi[:n, k])
+    a = a / n
+    xi[:n, k] -= a
diff --git a/scripts/Muon/MaxentTools/start.py b/scripts/Muon/MaxentTools/start.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb78cc88db921817df8b6abe5a76b7b169b726d1
--- /dev/null
+++ b/scripts/Muon/MaxentTools/start.py
@@ -0,0 +1,47 @@
+
+from __future__ import (absolute_import, division, print_function)
+import numpy as np
+import math
+
+
+def START(npts, PULSES_npulse, RUNDATA_res, MAXPAGE_n, TZERO_fine, mylog):
+    Tmuon = 2.19704
+    Tpion = 0.026
+    TAUlife = Tmuon / RUNDATA_res
+    DETECT_e = np.exp(-np.arange(npts) / TAUlife)
+    # create convolution factor for finite pulse width and 2 pulse
+    # WARNING, ISIS specific constants here!
+    hpulsew = .05  # subject to accelerator tune, pulse slicers, etc
+    pulse2t = .324  # at 800MeV, different at 700MeV
+    if(PULSES_npulse == 1):
+        pulse2t = 0.0
+    fperchan = 1. / (RUNDATA_res * float(npts) * 2.)
+    ww = np.arange(MAXPAGE_n) * fperchan * 2. * math.pi
+    # GW(I) is parabolic proton pulse ft GW(0)=1
+    gw = (
+        3 / hpulsew) * (
+        np.sin(
+            ww * hpulsew) / (
+                hpulsew**2 * ww**3) - np.cos(
+                    ww * hpulsew) / (
+                        hpulsew * ww**2))  # may throw warning about div/0 at i=0
+    gw[0] = 1.0
+    aa = gw / (1 + (ww * Tpion)**2)
+    PULSESHAPE_convolr = aa * \
+        (np.cos(ww * pulse2t / 2) - np.tanh(pulse2t / (2 * Tmuon))
+         * np.sin(ww * pulse2t / 2) * ww * Tpion)
+    PULSESHAPE_convoli = -aa * \
+        (np.tanh(pulse2t / (2 * Tmuon)) * np.sin(ww * pulse2t / 2)
+         + np.cos(pulse2t * ww / 2) * ww * Tpion)
+
+    PULSESHAPE_convol = PULSESHAPE_convolr + 1.j * PULSESHAPE_convoli
+    mylog.notice("convol before time shift:" + str(PULSESHAPE_convol[0:3]))
+    # adjust for T0 not being an exact bin boundary (JSL)
+    # adjust such that time zero is mean muon arrival as calculated by
+    # frequency scan, NOT centre of proton pulse parabola (single pulse case
+    # only for now)
+    PULSESHAPE_convol = PULSESHAPE_convol * \
+        np.exp(1.j * (TZERO_fine + Tpion) * ww)
+    mylog.notice("convol after time shift:" + str(PULSESHAPE_convol[0:3]))
+
+    return (DETECT_e, PULSESHAPE_convol)
diff --git a/scripts/Muon/MaxentTools/tropus.py b/scripts/Muon/MaxentTools/tropus.py
new file mode 100644
index 0000000000000000000000000000000000000000..2c1593944a10947492fac55e64a38de81e4cc90e
--- /dev/null
+++ b/scripts/Muon/MaxentTools/tropus.py
@@ -0,0 +1,18 @@
+
+from __future__ import (absolute_import, division, print_function)
+import numpy as np
+
+
+def TROPUS(ox, SAVETIME_i2, PULSESHAPE_convol, DETECT_a, DETECT_b, DETECT_e):
+    npts, ngroups = ox.shape
+    n = PULSESHAPE_convol.shape[0]
+    y = np.zeros([SAVETIME_i2], dtype=np.complex_)
+    y[:npts] = np.dot(
+        ox,
+        DETECT_a) * DETECT_e + 1.j * np.dot(ox,
+                                            DETECT_b) * DETECT_e
+    y2 = np.fft.fft(y)  # SN=-1 meaning forward fft, scale is OK
+    x = np.real(y2)[:n] * np.real(PULSESHAPE_convol) + \
+        np.imag(y2)[:n] * np.imag(PULSESHAPE_convol)
+
+    return x
diff --git a/scripts/Muon/MaxentTools/zft.py b/scripts/Muon/MaxentTools/zft.py
new file mode 100644
index 0000000000000000000000000000000000000000..663b7c2a2e19a1cada9356c92b83ee12aa5ed321
--- /dev/null
+++ b/scripts/Muon/MaxentTools/zft.py
@@ -0,0 +1,19 @@
+
+from __future__ import (absolute_import, division, print_function)
+import numpy as np
+
+# param P not used!
+# N is number of frequency points provided
+# 2**I2PWR is length of FFT (zero pad spectrum). Replace with I2
+# npts is number of time bins (truncate result)
+# PULSESHAPE_convol is complex (convolR + i*convolI)
+
+
+def ZFT(f, PULSESHAPE_convol, DETECT_e, SAVETIME_i2):
+    n = f.shape[0]
+    npts = DETECT_e.shape[0]
+    y = np.zeros([SAVETIME_i2], dtype=np.complex_)
+    y[:n] = f * PULSESHAPE_convol
+    y2 = np.fft.ifft(y) * \
+        SAVETIME_i2  # SN=+1 meaning inverse FFT without the 1/N scale factor
+    return np.real(y2[:npts]) * DETECT_e, np.imag(y2[:npts]) * DETECT_e
diff --git a/scripts/SANS/ISISCommandInterface.py b/scripts/SANS/ISISCommandInterface.py
index 401b1a18311ae9edc3440470193cc84f201b9251..8470b2bcbbd7268cd26326d813ccb5fb2b0df1d4 100644
--- a/scripts/SANS/ISISCommandInterface.py
+++ b/scripts/SANS/ISISCommandInterface.py
@@ -460,7 +460,7 @@ def WavRangeReduction(wav_start=None, wav_end=None, full_trans_wav=None, name_su
     # do reduce rear bank data
     if reduce_rear_flag:
         ReductionSingleton().instrument.setDetector('rear')
-        retWSname_rear = _WavRangeReduction(name_suffix)
+        retWSname_rear, rear_slices = _WavRangeReduction(name_suffix)
         retWSname = retWSname_rear
 
     # do reduce front bank
@@ -494,107 +494,28 @@ def WavRangeReduction(wav_start=None, wav_end=None, full_trans_wav=None, name_su
 
         ReductionSingleton().instrument.setDetector('front')
 
-        retWSname_front = _WavRangeReduction(name_suffix)
+        retWSname_front, front_slices = _WavRangeReduction(name_suffix)
         retWSname = retWSname_front
 
     # This section provides a the REAR -- FRONT fitting and a stitched workspace.
     # If merge_flag is selected we use SANSStitch and get the fitting for free
     # If fitRequired is selected, then we explicity call the SANSFitScale algorithm
     if merge_flag:
-        # Prepare the Norm and Count workspaces for the FRONT and the REAR detectors
-        retWSname_merged = retWSname_rear
-        if retWSname_merged.count('rear') == 1:
-            retWSname_merged = retWSname_merged.replace('rear', 'merged')
+        if ReductionSingleton().getNumSlices() > 1:
+            slices = []
+            for index in range(ReductionSingleton().getNumSlices()):
+                merge_workspace = _merge_workspaces(front_slices[index], rear_slices[index], rAnds)
+                slices.append(merge_workspace)
+            ReductionSingleton().setSliceIndex(0)
+            group_name = _common_substring(slices[0], slices[1])
+            if group_name[-2] == "_":
+                group_name = group_name[:-2]
+            _group_workspaces(slices, group_name)
+            retWSname_merged = group_name
         else:
-            retWSname_merged = retWSname_merged + "_merged"
-
-        Nf = mtd[retWSname_front + "_sumOfNormFactors"]
-        Nr = mtd[retWSname_rear + "_sumOfNormFactors"]
-        Cf = mtd[retWSname_front + "_sumOfCounts"]
-        Cr = mtd[retWSname_rear + "_sumOfCounts"]
-        consider_can = True
-        try:
-            Nf_can = mtd[retWSname_front + "_can_tmp_sumOfNormFactors"]
-            Nr_can = mtd[retWSname_rear + "_can_tmp_sumOfNormFactors"]
-            Cf_can = mtd[retWSname_front + "_can_tmp_sumOfCounts"]
-            Cr_can = mtd[retWSname_rear + "_can_tmp_sumOfCounts"]
-            if Cr_can is None:
-                consider_can = False
-        except KeyError:
-            # The CAN was not specified
-            consider_can = False
-        # Get fit paramters
-        scale_factor, shift_factor, fit_mode, fit_min, fit_max = su.extract_fit_parameters(rAnds)
-        merge_range = ReductionSingleton().instrument.getDetector('FRONT').mergeRange
-
-        kwargs_stitch = {"HABCountsSample": Cf,
-                         "HABNormSample": Nf,
-                         "LABCountsSample": Cr,
-                         "LABNormSample": Nr,
-                         "ProcessCan": False,
-                         "Mode": fit_mode,
-                         "ScaleFactor": scale_factor,
-                         "ShiftFactor": shift_factor,
-                         "OutputWorkspace": retWSname_merged,
-                         "MergeMask": merge_range.q_merge_range}
-        if consider_can:
-            kwargs_can = {"HABCountsCan": Cf_can,
-                          "HABNormCan": Nf_can,
-                          "LABCountsCan": Cr_can,
-                          "LABNormCan": Nr_can,
-                          "ProcessCan": True}
-            kwargs_stitch.update(kwargs_can)
-
-        if rAnds.qRangeUserSelected:
-            q_range_stitch = {"FitMin": fit_min,
-                              "FitMax": fit_max}
-            kwargs_stitch.update(q_range_stitch)
-
-        if merge_range.q_merge_range:
-            if merge_range.q_min:
-                q_range_options = {"MergeMin": merge_range.q_min}
-                kwargs_stitch.update(q_range_options)
-            if merge_range.q_max:
-                q_range_options = {"MergeMax": merge_range.q_max}
-                kwargs_stitch.update(q_range_options)
-
-        alg_stitch = su.createUnmanagedAlgorithm("SANSStitch", **kwargs_stitch)
-        alg_stitch.execute()
-
-        # Get the fit values
-        shift_from_alg = alg_stitch.getProperty("OutShiftFactor").value
-        scale_from_alg = alg_stitch.getProperty("OutScaleFactor").value
-        ReductionSingleton().instrument.getDetector('FRONT').rescaleAndShift.shift = shift_from_alg
-        ReductionSingleton().instrument.getDetector('FRONT').rescaleAndShift.scale = scale_from_alg
-
-        # Get the merged workspace
-        mergedQ = alg_stitch.getProperty("OutputWorkspace").value
-        # Add the ouput to the Analysis Data Service
-        AnalysisDataService.addOrReplace(retWSname_merged, mergedQ)
-
-        # save the properties Transmission and TransmissionCan inside the merged workspace
-        # get these values from the rear_workspace because they are the same value as the front one.
-        # ticket #6929
-        rear_ws = mtd[retWSname_rear]
-        for prop in ['Transmission', 'TransmissionCan']:
-            if rear_ws.getRun().hasProperty(prop):
-                ws_name = rear_ws.getRun().getLogData(prop).value
-                if mtd.doesExist(ws_name):  # ensure the workspace has not been deleted
-                    AddSampleLog(Workspace=retWSname_merged, LogName=prop, LogText=ws_name)
+            retWSname_merged = _merge_workspaces(retWSname_front, retWSname_rear, rAnds)
 
         retWSname = retWSname_merged
-
-        # Remove the partial workspaces, this needs to be done for when we merge and/or fit
-        delete_workspaces(retWSname_rear + "_sumOfCounts")
-        delete_workspaces(retWSname_rear + "_sumOfNormFactors")
-        delete_workspaces(retWSname_front + "_sumOfCounts")
-        delete_workspaces(retWSname_front + "_sumOfNormFactors")
-        if consider_can:
-            delete_workspaces(retWSname_front + "_can_tmp_sumOfNormFactors")
-            delete_workspaces(retWSname_rear + "_can_tmp_sumOfNormFactors")
-            delete_workspaces(retWSname_front + "_can_tmp_sumOfCounts")
-            delete_workspaces(retWSname_rear + "_can_tmp_sumOfCounts")
-
     elif fitRequired:
         # Get fit paramters
         scale_factor, shift_factor, fit_mode = su.extract_fit_parameters(rAnds)
@@ -619,11 +540,8 @@ def WavRangeReduction(wav_start=None, wav_end=None, full_trans_wav=None, name_su
 
     # applying scale and shift on the front detector reduced data
     if reduce_front_flag:
-        frontWS = mtd[retWSname_front]
-        buffer = Scale(InputWorkspace=frontWS, Operation="Add", Factor=shift)
-        frontWS = Scale(InputWorkspace=buffer, Operation="Multiply", Factor=scale)
-        RenameWorkspace(InputWorkspace=frontWS, OutputWorkspace=retWSname_front)
-        DeleteWorkspace(buffer)
+        Scale(InputWorkspace=retWSname_front, OutputWorkspace=retWSname_front, Operation="Add", Factor=shift)
+        Scale(InputWorkspace=retWSname_front, OutputWorkspace=retWSname_front, Operation="Multiply", Factor=scale)
 
     # finished calculating cross section so can restore these value
     ReductionSingleton().to_Q.outputParts = toRestoreOutputParts
@@ -653,6 +571,119 @@ def WavRangeReduction(wav_start=None, wav_end=None, full_trans_wav=None, name_su
     return retWSname
 
 
+def _merge_workspaces(retWSname_front, retWSname_rear, rAnds):
+    # Prepare the Norm and Count workspaces for the FRONT and the REAR detectors
+    retWSname_merged = retWSname_rear
+    if retWSname_merged.count('rear') == 1:
+        retWSname_merged = retWSname_merged.replace('rear', 'merged')
+    else:
+        retWSname_merged = retWSname_merged + "_merged"
+
+    Nf = mtd[retWSname_front + "_sumOfNormFactors"]
+    Nr = mtd[retWSname_rear + "_sumOfNormFactors"]
+    Cf = mtd[retWSname_front + "_sumOfCounts"]
+    Cr = mtd[retWSname_rear + "_sumOfCounts"]
+    consider_can = True
+    try:
+        Nf_can = mtd[retWSname_front + "_can_tmp_sumOfNormFactors"]
+        Nr_can = mtd[retWSname_rear + "_can_tmp_sumOfNormFactors"]
+        Cf_can = mtd[retWSname_front + "_can_tmp_sumOfCounts"]
+        Cr_can = mtd[retWSname_rear + "_can_tmp_sumOfCounts"]
+        if Cr_can is None:
+            consider_can = False
+    except KeyError:
+        # The CAN was not specified
+        consider_can = False
+
+    # Get fit paramters
+    scale_factor, shift_factor, fit_mode, fit_min, fit_max = su.extract_fit_parameters(rAnds)
+    merge_range = ReductionSingleton().instrument.getDetector('FRONT').mergeRange
+
+    kwargs_stitch = {"HABCountsSample": Cf,
+                     "HABNormSample": Nf,
+                     "LABCountsSample": Cr,
+                     "LABNormSample": Nr,
+                     "ProcessCan": False,
+                     "Mode": fit_mode,
+                     "ScaleFactor": scale_factor,
+                     "ShiftFactor": shift_factor,
+                     "OutputWorkspace": retWSname_merged,
+                     "MergeMask": merge_range.q_merge_range}
+    if consider_can:
+        kwargs_can = {"HABCountsCan": Cf_can,
+                      "HABNormCan": Nf_can,
+                      "LABCountsCan": Cr_can,
+                      "LABNormCan": Nr_can,
+                      "ProcessCan": True}
+        kwargs_stitch.update(kwargs_can)
+
+    if rAnds.qRangeUserSelected:
+        q_range_stitch = {"FitMin": fit_min,
+                          "FitMax": fit_max}
+        kwargs_stitch.update(q_range_stitch)
+
+    if merge_range.q_merge_range:
+        if merge_range.q_min:
+            q_range_options = {"MergeMin": merge_range.q_min}
+            kwargs_stitch.update(q_range_options)
+        if merge_range.q_max:
+            q_range_options = {"MergeMax": merge_range.q_max}
+            kwargs_stitch.update(q_range_options)
+
+    alg_stitch = su.createUnmanagedAlgorithm("SANSStitch", **kwargs_stitch)
+    alg_stitch.execute()
+
+    # Get the fit values
+    shift_from_alg = alg_stitch.getProperty("OutShiftFactor").value
+    scale_from_alg = alg_stitch.getProperty("OutScaleFactor").value
+    ReductionSingleton().instrument.getDetector('FRONT').rescaleAndShift.shift = shift_from_alg
+    ReductionSingleton().instrument.getDetector('FRONT').rescaleAndShift.scale = scale_from_alg
+
+    # Get the merged workspace
+    mergedQ = alg_stitch.getProperty("OutputWorkspace").value
+    # Add the ouput to the Analysis Data Service
+    AnalysisDataService.addOrReplace(retWSname_merged, mergedQ)
+
+    # save the properties Transmission and TransmissionCan inside the merged workspace
+    # get these values from the rear_workspace because they are the same value as the front one.
+    # ticket #6929
+    rear_ws = mtd[retWSname_rear]
+    for prop in ['Transmission', 'TransmissionCan']:
+        if rear_ws.getRun().hasProperty(prop):
+            ws_name = rear_ws.getRun().getLogData(prop).value
+            if mtd.doesExist(ws_name):  # ensure the workspace has not been deleted
+                AddSampleLog(Workspace=retWSname_merged, LogName=prop, LogText=ws_name)
+
+    retWSname = retWSname_merged
+
+    # Remove the partial workspaces, this needs to be done for when we merge and/or fit
+    delete_workspaces(retWSname_rear + "_sumOfCounts")
+    delete_workspaces(retWSname_rear + "_sumOfNormFactors")
+    delete_workspaces(retWSname_front + "_sumOfCounts")
+    delete_workspaces(retWSname_front + "_sumOfNormFactors")
+    if consider_can:
+        delete_workspaces(retWSname_front + "_can_tmp_sumOfNormFactors")
+        delete_workspaces(retWSname_rear + "_can_tmp_sumOfNormFactors")
+        delete_workspaces(retWSname_front + "_can_tmp_sumOfCounts")
+        delete_workspaces(retWSname_rear + "_can_tmp_sumOfCounts")
+
+    return retWSname
+
+
+def _common_substring(val1, val2):
+    l = []
+    for i in range(len(val1)):
+        if val1[i] == val2[i]:
+            l.append(val1[i])
+        else:
+            return ''.join(l)
+
+
+def _group_workspaces(list_of_values, outputname):
+    allnames = ','.join(list_of_values)
+    GroupWorkspaces(InputWorkspaces=allnames, OutputWorkspace=outputname)
+
+
 def _WavRangeReduction(name_suffix=None):
     """
         Run a reduction that has been set up, from loading the raw data to calculating Q
@@ -677,18 +708,6 @@ def _WavRangeReduction(name_suffix=None):
             RenameWorkspace(InputWorkspace=old, OutputWorkspace=result)
         return result
 
-    def _common_substring(val1, val2):
-        l = []
-        for i in range(len(val1)):
-            if val1[i] == val2[i]:
-                l.append(val1[i])
-            else:
-                return ''.join(l)
-
-    def _group_workspaces(list_of_values, outputname):
-        allnames = ','.join(list_of_values)
-        GroupWorkspaces(InputWorkspaces=allnames, OutputWorkspace=outputname)
-
     def _reduceAllSlices():
         if ReductionSingleton().getNumSlices() > 1:
             slices = []
@@ -697,30 +716,32 @@ def _WavRangeReduction(name_suffix=None):
                 slices.append(ReductionSingleton()._reduce())
             ReductionSingleton().setSliceIndex(0)
             group_name = _common_substring(slices[0], slices[1])
+
             if group_name[-2] == "_":
                 group_name = group_name[:-2]
             _group_workspaces(slices, group_name)
-            return group_name
+            return group_name, slices
         else:
-            return ReductionSingleton()._reduce()
-
+            return ReductionSingleton()._reduce(), None
     result = ""
+    slices = []
     if ReductionSingleton().get_sample().loader.periods_in_file == 1:
-        result = _reduceAllSlices()
-        return _applySuffix(result, name_suffix)
+        result, slices = _reduceAllSlices()
+        return _applySuffix(result, name_suffix), slices
 
     calculated = []
     try:
         for period in ReductionSingleton().get_sample().loader.entries:
             _setUpPeriod(period)
-            calculated.append(_reduceAllSlices())
+            result, slices = _reduceAllSlices()
+            calculated.append(result)
 
     finally:
         if len(calculated) > 0:
             result = ReductionSingleton().get_out_ws_name(show_period=False)
             _group_workspaces(calculated, result)
 
-    return _applySuffix(result, name_suffix)
+    return _applySuffix(result, name_suffix), slices
 
 
 def delete_workspaces(workspaces):
diff --git a/scripts/SANS/SANSBatchMode.py b/scripts/SANS/SANSBatchMode.py
index 3bc9e9a3028c28f5ec3e23908860a704084688a8..9df0b0f8a00a39af638b4a96dc9ab90fd6a707f6 100644
--- a/scripts/SANS/SANSBatchMode.py
+++ b/scripts/SANS/SANSBatchMode.py
@@ -320,7 +320,6 @@ def BatchReduce(filename, format, plotresults=False, saveAlgs={'SaveRKH':'txt'},
         # | both           |    +_rear      |    +_front   |     -        |
         # | merged         |    +_rear      |    +_front   |     +_merged |
         # This is not great since it uses SANS2D terminology for all instruments
-
         names = [final_name]
         if combineDet == 'rear':
             new_name = su.rename_workspace_correctly(ins_name, su.ReducedType.LAB, final_name, reduced)
diff --git a/scripts/SANS/sans/algorithm_detail/centre_finder_new.py b/scripts/SANS/sans/algorithm_detail/centre_finder_new.py
index a6fd5ca7547ac5b17582cee3d8eecf6305355f81..03f8cd88aa4eabfee908b94952cc0a92854c5626 100644
--- a/scripts/SANS/sans/algorithm_detail/centre_finder_new.py
+++ b/scripts/SANS/sans/algorithm_detail/centre_finder_new.py
@@ -10,7 +10,7 @@ from mantid.simpleapi import CreateEmptyTableWorkspace
 # Functions for the execution of a single batch iteration
 # ----------------------------------------------------------------------------------------------------------------------
 def centre_finder_new(state, r_min = 0.06, r_max = 0.26, iterations = 10, position_1_start = 0.0, position_2_start = 0.0
-                      , tolerance = 0.0001251, find_direction = FindDirectionEnum.All):
+                      , tolerance = 0.0001251, find_direction = FindDirectionEnum.All, verbose=False):
     """
     Finds the beam centre from a good initial guess.
 
@@ -24,7 +24,7 @@ def centre_finder_new(state, r_min = 0.06, r_max = 0.26, iterations = 10, positi
     :param position_1_start: This is the starting position of the search on the x axis.
     :param position_2_start: This is the starting position of the search on the y axis.
     :param tolerance: This is the tolerance for the search.
-    :param fine_direction: This is an enumerator controlling which axis or both should be searched.
+    :param find_direction: This is an enumerator controlling which axis or both should be searched.
     """
     # ------------------------------------------------------------------------------------------------------------------
     # Load the data
@@ -55,7 +55,8 @@ def centre_finder_new(state, r_min = 0.06, r_max = 0.26, iterations = 10, positi
     beam_centre_finder = "SANSBeamCentreFinder"
     beam_centre_finder_options = {"Iterations": iterations, "RMin": r_min/1000, "RMax": r_max/1000,
                                   "Position1Start": position_1_start, "Position2Start": position_2_start,
-                                  "Tolerance": tolerance, "Direction" : FindDirectionEnum.to_string(find_direction)}
+                                  "Tolerance": tolerance, "Direction" : FindDirectionEnum.to_string(find_direction),
+                                  "Verbose": verbose}
     beam_centre_alg = create_managed_non_child_algorithm(beam_centre_finder, **beam_centre_finder_options)
     beam_centre_alg.setChild(False)
     set_properties_for_beam_centre_algorithm(beam_centre_alg, reduction_package,
@@ -70,12 +71,11 @@ def centre_finder_new(state, r_min = 0.06, r_max = 0.26, iterations = 10, positi
     # -----------------------------------
     centre1 = beam_centre_alg.getProperty("Centre1").value
     centre2 = beam_centre_alg.getProperty("Centre2").value
-    create_output_table(centre1, centre2)
 
     return {"pos1": centre1, "pos2": centre2}
 
 
-def centre_finder_mass(state, r_min = 0.06, position_1_start = 0.0, position_2_start = 0.0,
+def centre_finder_mass(state, r_min = 0.06, max_iter=10, position_1_start = 0.0, position_2_start = 0.0,
                        tolerance = 0.0001251):
     """
     Finds the beam centre from an initial guess.
@@ -130,7 +130,6 @@ def centre_finder_mass(state, r_min = 0.06, position_1_start = 0.0, position_2_s
 
     centre1 = beam_centre_alg.getProperty("Centre1").value
     centre2 = beam_centre_alg.getProperty("Centre2").value
-    create_output_table(centre1, centre2)
 
     return {"pos1": centre1, "pos2": centre2}
 
diff --git a/scripts/SANS/sans/command_interface/ISISCommandInterface.py b/scripts/SANS/sans/command_interface/ISISCommandInterface.py
index edd40ede864d812eba50479f9a056a68391d4274..8fe24e2ec42ab550e4dd4addeed66a53daed7157 100644
--- a/scripts/SANS/sans/command_interface/ISISCommandInterface.py
+++ b/scripts/SANS/sans/command_interface/ISISCommandInterface.py
@@ -1013,16 +1013,38 @@ def PhiRanges(phis, plot=True):
 def FindBeamCentre(rlow, rupp, MaxIter=10, xstart=None, ystart=None, tolerance=1.251e-4,
                    find_direction=FindDirectionEnum.All, reduction_method=True):
     state = director.process_commands()
-
-    # This is to mantain compatibility with how this function worked in the old Interface so that legacy scripts still
-    # function
-    if config['default.instrument'] == 'LARMOR':
+    """
+    Finds the beam centre position.
+    @param rlow: Inner radius of quadrant
+    @param rupp: Outer radius of quadrant
+    @param MaxIter: Maximun number of iterations
+    @param xstart: starting x centre position, if not set is taken from user file
+    @param ystart: starting y centre position, if not set is taken from user file
+    @param tolerance: Sets the step size at which the search will stop
+    @param find_direction: FindDirectionEnum controls which directions to find the centre in by default is All
+    @param reduction_method: if true uses the quadrant centre finder method. If false user the COM method
+    """
+
+    if not xstart:
+        xstart = state.move.detectors['LAB'].sample_centre_pos1
+    elif config['default.instrument'] == 'LARMOR':
+        # This is to mantain compatibility with how this function worked in the old Interface so that legacy scripts still
+        # function
         xstart = xstart * 1000
+    if not ystart:
+        ystart = state.move.detectors['LAB'].sample_centre_pos2
 
     centre_finder = SANSCentreFinder()
-    centre = centre_finder(state, rlow, rupp, MaxIter, xstart, ystart, tolerance, find_direction, reduction_method)
-    SetCentre(centre['pos1'], centre['pos2'], bank='rear')
-    SetCentre(centre['pos1'], centre['pos2'], bank='front')
+    centre = centre_finder(state, float(rlow), float(rupp), MaxIter, float(xstart), float(ystart), float(tolerance),
+                           find_direction, reduction_method)
+
+    if config['default.instrument'] == 'LARMOR':
+        SetCentre(centre['pos1'], 1000 * centre['pos2'], bank='rear')
+    else:
+        SetCentre(1000*centre['pos1'], 1000*centre['pos2'], bank='rear')
+    if 'HAB' in state.move.detectors:
+        SetCentre(1000*centre['pos1'], 1000*centre['pos2'], bank='front')
+
     return centre
 
 
diff --git a/scripts/SANS/sans/common/file_information.py b/scripts/SANS/sans/common/file_information.py
index 4305a59bdfdc01d4fa120f53cf77bcb8a0a3f98f..0de6cb8b637b6bad23f9ad460b5aea3341a01a35 100644
--- a/scripts/SANS/sans/common/file_information.py
+++ b/scripts/SANS/sans/common/file_information.py
@@ -100,7 +100,6 @@ def find_sans_file(file_name):
             # TODO: If we only provide a run number for example 98843 for LOQ measurments, but have LARMOR specified as the
             #       Mantid instrument, then the FileFinder will search itself to death. This is a general Mantid issue.
             #       One way to handle this graceful would be a timeout option.
-            print("Type is {}".format(type(file_name)))
             file_name_as_bytes = str.encode(file_name)
             assert(type(file_name_as_bytes) == bytes)
             runs = FileFinder.findRuns(file_name_as_bytes)
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 8ccce11638796fedacf21f178deaeb0170e577cb..d9278e7d46d04e6e65dc4e79be59832b93e133a1 100644
--- a/scripts/SANS/sans/gui_logic/models/beam_centre_model.py
+++ b/scripts/SANS/sans/gui_logic/models/beam_centre_model.py
@@ -1,10 +1,11 @@
-from sans.common.enums import (SANSInstrument)
+from sans.common.enums import (SANSInstrument, FindDirectionEnum)
 
 
 class BeamCentreModel(object):
-    def __init__(self):
+    def __init__(self, SANSCentreFinder):
         super(BeamCentreModel, self).__init__()
         self.reset_to_defaults_for_instrument()
+        self.SANSCentreFinder = SANSCentreFinder
 
     def __eq__(self, other):
         return self.__dict__ == other.__dict__
@@ -22,6 +23,10 @@ class BeamCentreModel(object):
         self._hab_pos_1 = ''
         self.scale_1 = 1000
         self.scale_2 = 1000
+        self.COM = False
+        self.verbose = False
+        self.q_min = 0.01
+        self.q_max = 0.1
 
         if instrument == SANSInstrument.LOQ:
             self.r_max = 200
@@ -35,6 +40,49 @@ class BeamCentreModel(object):
         if instrument == SANSInstrument.LARMOR:
             self.scale_1 = 1.0
 
+    def find_beam_centre(self, state):
+        """
+        This is called from the GUI and runs the find beam centre algorithm given a state model and a beam_centre_model object.
+
+        :param state: A SANS state object
+        :param beam_centre_model: An instance of the BeamCentreModel class.
+        :returns: The centre position found.
+        """
+        centre_finder = self.SANSCentreFinder()
+        find_direction = None
+        if self.up_down and self.left_right:
+            find_direction = FindDirectionEnum.All
+        elif self.up_down:
+            find_direction = FindDirectionEnum.Left_Right
+        elif self.left_right:
+            find_direction = FindDirectionEnum.Up_Down
+
+        if self.q_min:
+            state.convert_to_q.q_min = self.q_min
+        if self.q_max:
+            state.convert_to_q.q_max = self.q_max
+
+        if self.COM:
+            centre = centre_finder(state, r_min=self.r_min, r_max=self.r_max,
+                                   max_iter=self.max_iterations,
+                                   x_start=self.lab_pos_1, y_start=self.lab_pos_2,
+                                   tolerance=self.tolerance,
+                                   find_direction=find_direction, reduction_method=False)
+
+            centre = centre_finder(state, r_min=self.r_min, r_max=self.r_max,
+                                   max_iter=self.max_iterations,
+                                   x_start=centre['pos1'], y_start=centre['pos2'],
+                                   tolerance=self.tolerance,
+                                   find_direction=find_direction, reduction_method=True,
+                                   verbose=self.verbose)
+        else:
+            centre = centre_finder(state, r_min=self.r_min, r_max=self.r_max,
+                                   max_iter=self.max_iterations, x_start=self.lab_pos_1,
+                                   y_start=self.lab_pos_2, tolerance=self.tolerance,
+                                   find_direction=find_direction, reduction_method=True,
+                                   verbose=self.verbose)
+        return centre
+
     @property
     def max_iterations(self):
         return self._max_iterations
@@ -59,6 +107,22 @@ class BeamCentreModel(object):
     def r_max(self, value):
         self._r_max = value
 
+    @property
+    def q_min(self):
+        return self._q_min
+
+    @q_min.setter
+    def q_min(self, value):
+        self._q_min = value
+
+    @property
+    def q_max(self):
+        return self._q_max
+
+    @q_max.setter
+    def q_max(self, value):
+        self._q_max = value
+
     @property
     def left_right(self):
         return self._left_right
@@ -75,6 +139,22 @@ class BeamCentreModel(object):
     def up_down(self, value):
         self._up_down = value
 
+    @property
+    def verbose(self):
+        return self._verbose
+
+    @verbose.setter
+    def verbose(self, value):
+        self._verbose = value
+
+    @property
+    def COM(self):
+        return self._COM
+
+    @COM.setter
+    def COM(self, value):
+        self._COM = value
+
     @property
     def tolerance(self):
         return self._tolerance
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 d814792ab700fd5c60087a8caf32996d23123432..1ea371dfeab58d5b5a5b99e8ff2357085d130bd2 100644
--- a/scripts/SANS/sans/gui_logic/presenter/beam_centre_presenter.py
+++ b/scripts/SANS/sans/gui_logic/presenter/beam_centre_presenter.py
@@ -5,8 +5,6 @@ import copy
 from mantid.kernel import Logger
 from ui.sans_isis.beam_centre import BeamCentre
 from ui.sans_isis.work_handler import WorkHandler
-from sans.sans_batch import SANSCentreFinder
-from sans.common.enums import FindDirectionEnum
 
 
 class BeamCentrePresenter(object):
@@ -28,13 +26,13 @@ class BeamCentrePresenter(object):
         def on_processing_error(self, error):
             self._presenter.on_processing_error_centre_finder(error)
 
-    def __init__(self, parent_presenter, WorkHandler, BeamCentreModel):
+    def __init__(self, parent_presenter, WorkHandler, BeamCentreModel, SANSCentreFinder):
         super(BeamCentrePresenter, self).__init__()
         self._view = None
         self._parent_presenter = parent_presenter
         self._work_handler = WorkHandler()
         self._logger = Logger("SANS")
-        self._beam_centre_model = BeamCentreModel()
+        self._beam_centre_model = BeamCentreModel(SANSCentreFinder)
 
     def set_view(self, view):
         if view:
@@ -69,9 +67,10 @@ class BeamCentrePresenter(object):
 
     def on_processing_error_centre_finder(self, error):
         self._logger.warning("There has been an error. See more: {}".format(error))
+        self._view.set_run_button_to_normal()
 
     def on_processing_error(self, error):
-        pass
+        self._view.set_run_button_to_normal()
 
     def on_run_clicked(self):
         # Get the state information for the first row.
@@ -91,7 +90,8 @@ class BeamCentrePresenter(object):
         # Run the task
         listener = BeamCentrePresenter.CentreFinderListener(self)
         state_copy = copy.copy(state)
-        self._work_handler.process(listener, find_beam_centre, state_copy, self._beam_centre_model)
+
+        self._work_handler.process(listener, self._beam_centre_model.find_beam_centre, state_copy)
 
     def _update_beam_model_from_view(self):
         self._beam_centre_model.r_min = self._view.r_min
@@ -99,11 +99,15 @@ class BeamCentrePresenter(object):
         self._beam_centre_model.max_iterations = self._view.max_iterations
         self._beam_centre_model.tolerance = self._view.tolerance
         self._beam_centre_model.left_right = self._view.left_right
+        self._beam_centre_model.verbose = self._view.verbose
+        self._beam_centre_model.COM = self._view.COM
         self._beam_centre_model.up_down = self._view.up_down
         self._beam_centre_model.lab_pos_1 = self._view.lab_pos_1 / self._beam_centre_model.scale_1
         self._beam_centre_model.lab_pos_2 = self._view.lab_pos_2 / self._beam_centre_model.scale_2
         self._beam_centre_model.hab_pos_1 = self._view.hab_pos_1 / self._beam_centre_model.scale_1
         self._beam_centre_model.hab_pos_2 = self._view.hab_pos_2 / self._beam_centre_model.scale_2
+        self._beam_centre_model.q_min = self._view.q_min
+        self._beam_centre_model.q_max = self._view.q_max
 
     def set_on_state_model(self, attribute_name, state_model):
         attribute = getattr(self._view, attribute_name)
@@ -114,26 +118,3 @@ class BeamCentrePresenter(object):
         attribute = getattr(state_model, attribute_name)
         if attribute or isinstance(attribute, bool):  # We need to be careful here. We don't want to set empty strings, or None, but we want to set boolean values. # noqa
             setattr(self._view, attribute_name, attribute)
-
-
-def find_beam_centre(state, beam_centre_model):
-    """
-    This is called from the GUI and runs the find beam centre algorithm given a state model and a beam_centre_model object.
-
-    :param state: A SANS state object
-    :param beam_centre_model: An instance of the BeamCentreModel class.
-    :returns: The centre position found.
-    """
-    centre_finder = SANSCentreFinder()
-    find_direction = None
-    if beam_centre_model.up_down and beam_centre_model.left_right:
-        find_direction = FindDirectionEnum.All
-    elif beam_centre_model.up_down:
-        find_direction = FindDirectionEnum.Left_Right
-    elif beam_centre_model.left_right:
-        find_direction = FindDirectionEnum.Up_Down
-
-    centre = centre_finder(state, r_min=beam_centre_model.r_min, r_max=beam_centre_model.r_max, max_iter=beam_centre_model.max_iterations,
-                           x_start=beam_centre_model.lab_pos_1, y_start=beam_centre_model.lab_pos_2, tolerance=beam_centre_model.tolerance,
-                           find_direction=find_direction, reduction_method=True)
-    return centre
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 bc4ee57042ec982de613abaa70767078197c81fb..1979784668eeea4a47e2d5306b8c76840213433d 100644
--- a/scripts/SANS/sans/gui_logic/presenter/run_tab_presenter.py
+++ b/scripts/SANS/sans/gui_logic/presenter/run_tab_presenter.py
@@ -31,6 +31,7 @@ from sans.command_interface.batch_csv_file_parser import BatchCsvParser
 from sans.common.constants import ALL_PERIODS
 from sans.gui_logic.models.beam_centre_model import BeamCentreModel
 from ui.sans_isis.work_handler import WorkHandler
+from sans.sans_batch import SANSCentreFinder
 
 try:
     import mantidplot
@@ -104,7 +105,7 @@ class RunTabPresenter(object):
         self._masking_table_presenter = MaskingTablePresenter(self)
 
         # Beam centre presenter
-        self._beam_centre_presenter = BeamCentrePresenter(self, WorkHandler, BeamCentreModel)
+        self._beam_centre_presenter = BeamCentrePresenter(self, WorkHandler, BeamCentreModel, SANSCentreFinder)
 
     def __del__(self):
         self._delete_dummy_input_workspace()
diff --git a/scripts/SANS/sans/sans_batch.py b/scripts/SANS/sans/sans_batch.py
index 2e1bb9760efe338c91cfbedbc2f43ffdb7af528f..75d101cbb80a00fe19934a1137b9fd257d50ee7f 100644
--- a/scripts/SANS/sans/sans_batch.py
+++ b/scripts/SANS/sans/sans_batch.py
@@ -82,17 +82,17 @@ class SANSCentreFinder(object):
     def __init__(self):
         super(SANSCentreFinder, self).__init__()
 
-    def __call__(self, state, r_min = 0.06, r_max = 0.026, max_iter = 20, x_start = 0.0, y_start = 0.0,
-                 tolerance = 1.251e-4, find_direction = FindDirectionEnum.All, reduction_method = True):
+    def __call__(self, state, r_min = 60, r_max = 280, max_iter = 20, x_start = 0.0, y_start = 0.0,
+                 tolerance = 1.251e-4, find_direction = FindDirectionEnum.All, reduction_method = True, verbose=False):
         """
         This is the start of the beam centre finder algorithm.
 
         :param state: This is a sans state, to find the beam centre for.
-        :param r_min: This is the inner radius of the quartile mask.
-        :param r_max: This is the outer radius of the quartile mask.
+        :param r_min: This is the inner radius of the quartile mask in mm.
+        :param r_max: This is the outer radius of the quartile mask in mm.
         :param max_iter: This is the maximum number of iterations.
-        :param x_start: This is the starting position of the search on the x axis.
-        :param y_start: This is the starting position of the search on the y axis.
+        :param x_start: This is the starting position of the search on the x axis in metres or degrees.
+        :param y_start: This is the starting position of the search on the y axis in metres.
         :param tolerance: This is the tolerance for the search.
         :param fine_direction: This is an enumerator controlling which axis or both should be searched.
         :param reduction_method: This is a bool controlling which centre finder algorithm to use. By default the
@@ -101,14 +101,15 @@ class SANSCentreFinder(object):
         self.validate_inputs(state, r_min, r_max, max_iter, x_start, y_start, tolerance)
 
         if reduction_method:
-            return self._execute_reduction_method(state, r_min, r_max, max_iter, x_start, y_start, tolerance, find_direction)
+            return self._execute_reduction_method(state, r_min, r_max, max_iter, x_start, y_start, tolerance,
+                                                  find_direction, verbose)
         else:
             return self._execute_mass_method(state, r_min, max_iter, x_start, y_start, tolerance)
 
     @staticmethod
-    def _execute_reduction_method(state, r_min, r_max, max_iter, xstart, ystart, tolerance, find_direction):
+    def _execute_reduction_method(state, r_min, r_max, max_iter, xstart, ystart, tolerance, find_direction, verbose):
         # Perform the beam centre finder algorithm
-        return centre_finder_new(state, r_min, r_max, max_iter, xstart, ystart, tolerance, find_direction)
+        return centre_finder_new(state, r_min, r_max, max_iter, xstart, ystart, tolerance, find_direction, verbose)
 
     @staticmethod
     def _execute_mass_method(state, r_min, max_iter, xstart, ystart, tolerance):
diff --git a/scripts/test/CrystalFieldMultiSiteTest.py b/scripts/test/CrystalFieldMultiSiteTest.py
index 96b7af7b73e404343ff8a3d1e8337ca482e174c7..dc8c8404c535a7f156ede63623c8f2113d0765c3 100644
--- a/scripts/test/CrystalFieldMultiSiteTest.py
+++ b/scripts/test/CrystalFieldMultiSiteTest.py
@@ -9,19 +9,19 @@ c_mbsr = 79.5774715459  # Conversion from barn to mb/sr
 class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     def test_init_single_ion(self):
-        cfms = CrystalFieldMultiSite(Ions='Pm', Symmetries='D2', Temperatures=[20, 52], FWHMs=[1.0, 1.0])
+        cfms = CrystalFieldMultiSite(Ions='Pm', Symmetries='D2', Temperatures=[20, 52], FWHM=[1.0, 1.0])
         self.assertEqual(cfms.Ions, ['Pm'])
         self.assertEqual(cfms.Symmetries, ['D2'])
         self.assertEqual(cfms.Temperatures, [20, 52])
-        self.assertEqual(cfms.FWHMs, [1.0, 1.0])
+        self.assertEqual(cfms.FWHM, [1.0, 1.0])
 
     def test_init_multi_ions(self):
         cfms = CrystalFieldMultiSite(Ions=('Pm', 'Eu'), Symmetries=('D2', 'C3v'), Temperatures=[20, 52],
-                                     FWHMs=[1.0, 1.0])
+                                     FWHM=[1.0, 1.0])
         self.assertEqual(cfms.Ions, ['Pm', 'Eu'])
         self.assertEqual(cfms.Symmetries, ['D2', 'C3v'])
         self.assertEqual(cfms.Temperatures, [20, 52])
-        self.assertEqual(cfms.FWHMs, [1.0, 1.0])
+        self.assertEqual(cfms.FWHM, [1.0, 1.0])
 
     def test_toleranceEnergy_property(self):
         cfms = CrystalFieldMultiSite(('Pm', 'Eu'), ('D2', 'C3v'), ToleranceEnergy=1.0)
@@ -48,28 +48,28 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertEqual(cfms.FWHMVariation, 0.3)
 
     def test_init_parameters(self):
-        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2', Temperatures=[15], FWHMs=[1.0],
+        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2', Temperatures=[15], FWHM=[1.0],
                                      parameters={'BmolX': 1.0, 'B40': -0.02})
         self.assertEqual(cfms.function.getParameterValue('BmolX'), 1.0)
         self.assertEqual(cfms.function.getParameterValue('B40'), -0.02)
 
     def test_init_parameters_multi_ions(self):
         cfms = CrystalFieldMultiSite(Ions=('Pm', 'Eu'), Symmetries=('D2', 'C3v'), Temperatures=[20, 52],
-                                     FWHMs=[1.0, 1.0], parameters={'ion0.B40': -0.02, 'ion0.B42': -0.11,
-                                                                   'ion1.B42': -0.12})
+                                     FWHM=[1.0, 1.0], parameters={'ion0.B40': -0.02, 'ion0.B42': -0.11,
+                                                                  'ion1.B42': -0.12})
         self.assertEqual(cfms.function.getParameterValue('ion0.B42'), -0.11)
         self.assertEqual(cfms.function.getParameterValue('ion0.B40'), -0.02)
         self.assertEqual(cfms.function.getParameterValue('ion1.B42'), -0.12)
 
     def test_peak_values(self):
-        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2', Temperatures=[25], FWHMs=[1.5],
+        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2', Temperatures=[25], FWHM=[1.5],
                                      BmolX=1.0, B40=-0.02)
         self.assertEqual(int(cfms.function.getParameterValue('pk0.Amplitude')), 48)
         self.assertEqual(cfms.function.getParameterValue('pk0.FWHM'), 1.5)
         self.assertEqual(cfms.function.getParameterValue('pk0.PeakCentre'), 0)
 
     def test_peak_values_gaussian(self):
-        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2', Temperatures=[25], FWHMs=[1.0], PeakShape='Gaussian',
+        cfms = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2', Temperatures=[25], FWHM=[1.0], PeakShape='Gaussian',
                                      BmolX=1.0, B40=-0.02)
         self.assertEqual(int(cfms.function.getParameterValue('pk0.Height')), 45)
         self.assertAlmostEqual(cfms.function.getParameterValue('pk0.Sigma'), 0.42, 2)
@@ -77,7 +77,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     def test_peak_values_multi_ions(self):
         cfms = CrystalFieldMultiSite(Ions=('Pr', 'Nd'), Symmetries=('D2', 'C3v'), Temperatures=[20],
-                                     FWHMs=[1.5], parameters={'ion0.B60': -0.02, 'ion1.B62': -0.12})
+                                     FWHM=[1.5], parameters={'ion0.B60': -0.02, 'ion1.B62': -0.12})
         self.assertEqual(int(cfms.function.getParameterValue('ion0.pk0.Amplitude')), 278)
         self.assertEqual(cfms.function.getParameterValue('ion0.pk0.FWHM'), 1.5)
         self.assertEqual(cfms.function.getParameterValue('ion0.pk1.PeakCentre'), 982.8)
@@ -87,7 +87,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     def test_peak_values_multi_ions_and_spectra(self):
         cfms = CrystalFieldMultiSite(Ions=('Pm', 'Ce'), Symmetries=('D2', 'C3v'), Temperatures=[20, 52],
-                                     FWHMs=[1.0, 1.0], parameters={'ion0.B40': -0.02, 'ion1.B42': -0.12})
+                                     FWHM=[1.0, 1.0], parameters={'ion0.B40': -0.02, 'ion1.B42': -0.12})
         self.assertEqual(int(cfms.function.getParameterValue('ion0.sp0.pk0.Amplitude')), 308)
         self.assertEqual(cfms.function.getParameterValue('ion0.sp1.pk0.FWHM'), 1.0)
         self.assertEqual(cfms.function.getParameterValue('ion0.sp0.pk1.PeakCentre'), 0)
@@ -97,7 +97,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     def test_peak_values_multi_gaussian(self):
         cfms = CrystalFieldMultiSite(Ions=('Pm', 'Dy'), Symmetries=('D2', 'C3v'), Temperatures=[20, 52],
-                                     FWHMs=[1.0, 1.5], parameters={'ion0.B40': -0.02, 'ion1.B42': -0.12},
+                                     FWHM=[1.0, 1.5], parameters={'ion0.B40': -0.02, 'ion1.B42': -0.12},
                                      PeakShape='Gaussian')
         self.assertEqual(int(cfms.function.getParameterValue('ion0.sp0.pk0.Height')), 289)
         self.assertAlmostEqual(cfms.function.getParameterValue('ion0.sp1.pk0.Sigma'), 0.64, 2)
@@ -107,7 +107,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertAlmostEqual(cfms.function.getParameterValue('ion1.sp0.pk1.PeakCentre'), 40.957515, 6)
 
     def test_get_spectrum_from_list(self):
-        cfms = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[4.0], FWHMs=[0.1],
+        cfms = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[4.0], FWHM=[0.1],
                                      B20=0.035, B40=-0.012, B43=-0.027, B60=-0.00012, B63=0.0025, B66=0.0068,
                                      ToleranceIntensity=0.001*c_mbsr)
         r = [0.0, 1.45, 2.4, 3.0, 3.85]
@@ -120,7 +120,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
     def test_get_spectrum_ws(self):
         from mantid.simpleapi import CreateWorkspace
         cfms = CrystalFieldMultiSite(['Ce'], ['C2v'], B20=0.035, B40=-0.012, B43=-0.027, B60=-0.00012, B63=0.0025,
-                                     B66=0.0068, Temperatures=[4.0], FWHMs=[0.1], ToleranceIntensity=0.001*c_mbsr)
+                                     B66=0.0068, Temperatures=[4.0], FWHM=[0.1], ToleranceIntensity=0.001*c_mbsr)
 
         x = np.linspace(0.0, 2.0, 30)
         y = np.zeros_like(x)
@@ -146,7 +146,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertAlmostEqual(y[1], 0.054428, 6)
 
     def test_get_spectrum_from_list_multi_spectra(self):
-        cfms = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[4.0, 50.0], FWHMs=[0.1, 0.2],
+        cfms = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[4.0, 50.0], FWHM=[0.1, 0.2],
                                      B20=0.035, B40=-0.012, B43=-0.027, B60=-0.00012, B63=0.0025, B66=0.0068)
         r = [0.0, 1.45, 2.4, 3.0, 3.85]
         x, y = cfms.getSpectrum(0, r)
@@ -164,7 +164,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
     def test_get_spectrum_ws_multi_spectra(self):
         from mantid.simpleapi import CreateWorkspace
         cfms = CrystalFieldMultiSite(['Ce'], ['C2v'], B20=0.035, B40=-0.012, B43=-0.027, B60=-0.00012, B63=0.0025, B66=0.0068,
-                          Temperatures=[4.0, 50.0], FWHMs=[0.1, 0.2])
+                          Temperatures=[4.0, 50.0], FWHM=[0.1, 0.2])
 
         x = np.linspace(0.0, 2.0, 30)
         y = np.zeros_like(x)
@@ -201,7 +201,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
                   'ion0.B44': -0.12544, 'ion1.B20': 0.37737, 'ion1.B22': 3.9770, 'ion1.B40': -0.031787,
                   'ion1.B42': -0.11611, 'ion1.B44': -0.12544}
         cfms = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0, 50.0],
-                                     FWHMs=[1.1, 1.2], parameters=params)
+                                     FWHM=[1.1, 1.2], parameters=params)
         r = [0.0, 1.45, 2.4, 3.0, 3.85]
         x, y = cfms.getSpectrum(0, r)
         y = y / c_mbsr
@@ -233,7 +233,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         params = {'ion0.B20': 0.37737, 'ion0.B22': 3.9770, 'ion0.B40': -0.031787, 'ion0.B42': -0.11611,
                   'ion0.B44': -0.12544, 'ion1.B20': 0.37737, 'ion1.B22': 3.9770, 'ion1.B40': -0.031787,
                   'ion1.B42': -0.11611, 'ion1.B44': -0.12544}
-        cf = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0], FWHMs=[1.1],
+        cf = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0], FWHM=[1.1],
                                    ToleranceIntensity=6.0, ToleranceEnergy=1.0, FixAllPeaks=True, parameters=params)
 
         cf.fix('ion0.BmolX', 'ion0.BmolY', 'ion0.BmolZ', 'ion0.BextX', 'ion0.BextY', 'ion0.BextZ', 'ion0.B40',
@@ -267,7 +267,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
                   'ion0.B44': -0.12544, 'ion1.B20': 0.37737, 'ion1.B22': 3.9770, 'ion1.B40': -0.031787,
                   'ion1.B42': -0.11611, 'ion1.B44': -0.12544}
         cf = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44.0, 50.0],
-                                    FWHMs=[1.0, 1.0], ToleranceIntensity=6.0, ToleranceEnergy=1.0,  FixAllPeaks=True,
+                                    FWHM=[1.0, 1.0], ToleranceIntensity=6.0, ToleranceEnergy=1.0,  FixAllPeaks=True,
                                    parameters=params)
 
         cf.fix('ion0.BmolX', 'ion0.BmolY', 'ion0.BmolZ', 'ion0.BextX', 'ion0.BextY', 'ion0.BextZ', 'ion0.B40',
@@ -285,7 +285,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertTrue(cf.chi2 < chi2)
 
     def test_set_background(self):
-        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0], Background='name=LinearBackground,A0=1')
+        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHM=[1.0], Background='name=LinearBackground,A0=1')
         self.assertEquals('"name=LinearBackground,A0=1"', cf['Background'])
         self.assertEquals(cf.background.param['A0'], 1)
         self.assertEquals(cf.background.background.param['A0'], 1)
@@ -294,7 +294,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     def test_set_background_as_function(self):
         from mantid.fitfunctions import LinearBackground
-        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0],
+        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHM=[1.0],
                                    Background=LinearBackground(A0=1))
         self.assertEquals('"name=LinearBackground,A0=1,A1=0"', cf['Background'])
         self.assertEquals(cf.background.param['A0'], 1)
@@ -304,7 +304,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     def test_set_background_with_peak(self):
         from mantid.fitfunctions import Gaussian
-        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0], Background='name=LinearBackground', BackgroundPeak=Gaussian(Height=1))
+        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHM=[1.0], Background='name=LinearBackground', BackgroundPeak=Gaussian(Height=1))
         self.assertEquals('"name=Gaussian,Height=1,PeakCentre=0,Sigma=0;name=LinearBackground"', cf['Background'])
         self.assertEquals(cf.background.peak.param['Height'], 1)
         self.assertEquals(cf.background.param['f0.Height'], 1)
@@ -316,7 +316,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     def test_set_background_peak_only(self):
         from mantid.fitfunctions import Gaussian
-        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0], BackgroundPeak=Gaussian(Sigma=1))
+        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHM=[1.0], BackgroundPeak=Gaussian(Sigma=1))
         self.assertEquals('"name=Gaussian,Height=0,PeakCentre=0,Sigma=1"', cf['Background'])
         self.assertEquals(cf.background.peak.param['Sigma'], 1)
         self.assertEquals(cf.background.param['Sigma'], 1)
@@ -325,7 +325,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
 
     def test_set_background_composite(self):
         from mantid.fitfunctions import Gaussian, LinearBackground
-        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0],
+        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHM=[1.0],
                                    Background= Gaussian(PeakCentre=1) + LinearBackground())
         self.assertEquals('"name=Gaussian,Height=0,PeakCentre=1,Sigma=0;name=LinearBackground,A0=0,A1=0"', cf['Background'])
         cf.background.param['f1.A0'] = 1
@@ -334,7 +334,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertEquals(cf.background.param['f0.PeakCentre'], 0.5)
 
     def test_set_background_composite_as_string(self):
-        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHMs=[1.0],
+        cf = CrystalFieldMultiSite(Ions='Ce', Symmetries='C2v', Temperatures=[20], FWHM=[1.0],
                                    Background='name=Gaussian,Height=0,PeakCentre=1,Sigma=0;name=LinearBackground,A0=0,A1=0')
         self.assertEquals('"name=Gaussian,Height=0,PeakCentre=1,Sigma=0;name=LinearBackground,A0=0,A1=0"', cf['Background'])
         self.assertEquals(cf.background.param['f0.Sigma'], 0)
@@ -345,7 +345,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         from mantid.fitfunctions import Gaussian, LinearBackground
         from mantid.simpleapi import FunctionFactory
 
-        cf = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[50], FWHMs=[0.9],
+        cf = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[50], FWHM=[0.9],
                                    B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544,
                                    Background=LinearBackground(A0=1.0), BackgroundPeak=Gaussian(Height=10, Sigma=0.3))
 
@@ -377,7 +377,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
     def test_constraints_multi_spectrum(self):
         from mantid.fitfunctions import FlatBackground, Gaussian
 
-        cf = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[44, 50], FWHMs=[1.1, 0.9],
+        cf = CrystalFieldMultiSite(Ions=['Ce'], Symmetries=['C2v'], Temperatures=[44, 50], FWHM=[1.1, 0.9],
                                    Background=FlatBackground(), BackgroundPeak=Gaussian(Height=10, Sigma=0.3),
                                    B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.12544)
 
@@ -408,7 +408,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
     def test_constraints_multi_spectrum_and_ion(self):
         from mantid.fitfunctions import FlatBackground, Gaussian
 
-        cf = CrystalFieldMultiSite(Ions=['Ce','Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44, 50], FWHMs=[1.1, 0.9],
+        cf = CrystalFieldMultiSite(Ions=['Ce','Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44, 50], FWHM=[1.1, 0.9],
                                    Background=FlatBackground(), BackgroundPeak=Gaussian(Height=10, Sigma=0.3),
                                    parameters={'ion0.B20': 0.37737, 'ion0.B22': 3.9770, 'ion1.B40':-0.031787,
                                                'ion1.B42':-0.11611, 'ion1.B44':-0.12544})
@@ -440,7 +440,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
     def test_add_CrystalField(self):
         from CrystalField import CrystalField
 
-        cfms = CrystalFieldMultiSite(Ions=['Pr'], Symmetries=['C2v'], Temperatures=[44, 50], FWHMs=[1.1, 0.9],
+        cfms = CrystalFieldMultiSite(Ions=['Pr'], Symmetries=['C2v'], Temperatures=[44, 50], FWHM=[1.1, 0.9],
                                      B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.15)
 
         params = {'B20': 0.37737, 'B22': 3.9770, 'B40': -0.031787, 'B42': -0.11611, 'B44': -0.12544}
@@ -463,7 +463,7 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
     def test_add_CrystalFieldSite(self):
         from CrystalField import CrystalField
 
-        cfms = CrystalFieldMultiSite(Ions=['Pr'], Symmetries=['C2v'], Temperatures=[44, 50], FWHMs=[1.1, 0.9],
+        cfms = CrystalFieldMultiSite(Ions=['Pr'], Symmetries=['C2v'], Temperatures=[44, 50], FWHM=[1.1, 0.9],
                                      B20=0.37737, B22=3.9770, B40=-0.031787, B42=-0.11611, B44=-0.15)
 
         params = {'B20': 0.37737, 'B22': 3.9770, 'B40': -0.031787, 'B42': -0.11611, 'B44': -0.12544}
@@ -482,11 +482,11 @@ class CrystalFieldMultiSiteTests(unittest.TestCase):
         self.assertTrue('ion0.IntensityScaling=0.125*ion1.IntensityScaling' in s)
 
     def test_add_CrystalFieldMultiSite(self):
-        cfms1 = CrystalFieldMultiSite(Ions=['Pm'], Symmetries=['D2'], Temperatures=[44, 50], FWHMs=[1.1, 0.9],
+        cfms1 = CrystalFieldMultiSite(Ions=['Pm'], Symmetries=['D2'], Temperatures=[44, 50], FWHM=[1.1, 0.9],
                                      B20=0.37737, B40=-0.031787, B42=-0.11611, B44=-0.15)
         cfms2 = CrystalFieldMultiSite(Ions=['Ce', 'Pr'], Symmetries=['C2v', 'C2v'], Temperatures=[44, 50],
-                                      FWHMs=[1.1, 0.9], parameters={'ion0.B20': 0.34, 'ion0.B22': 3.9770,
-                                                                    'ion1.B40': -0.03})
+                                      FWHM=[1.1, 0.9], parameters={'ion0.B20': 0.34, 'ion0.B22': 3.9770,
+                                                                   'ion1.B40': -0.03})
         cfms3 = cfms1 + cfms2
         self.assertEqual(len(cfms3.Ions), 3)
         self.assertEqual(len(cfms3.Symmetries), 3)
diff --git a/scripts/test/CrystalFieldTest.py b/scripts/test/CrystalFieldTest.py
index 5fa0706c89f93135f46d73dbf903b3b4ec54c0bc..b704f56075dc1feff282ccc94a2b3bfb17abcf06 100644
--- a/scripts/test/CrystalFieldTest.py
+++ b/scripts/test/CrystalFieldTest.py
@@ -1105,14 +1105,14 @@ class CrystalFieldFitTest(unittest.TestCase):
     def test_ResolutionModel_func_multi(self):
         from CrystalField import ResolutionModel
         def func0(x):
-            return np.sin(x)
+            return np.sin(np.array(x))
 
         class CalcWidth:
             def __call__(self, x):
-                return np.cos(x / 2)
+                return np.cos(np.array(x) / 2)
 
             def model(self, x):
-                return np.tan(x / 2)
+                return np.tan(np.array(x) / 2)
         func1 = CalcWidth()
         func2 = func1.model
         rm = ResolutionModel([func0, func1, func2], -np.pi/2, np.pi/2, accuracy = 0.01)
diff --git a/scripts/test/ISISPowderCommonTest.py b/scripts/test/ISISPowderCommonTest.py
index 03e78eb529f2657ef96d955f7537052da386b1c9..e4adc633b67d2ebd03311e7237020ac9a60580c2 100644
--- a/scripts/test/ISISPowderCommonTest.py
+++ b/scripts/test/ISISPowderCommonTest.py
@@ -552,6 +552,7 @@ class ISISPowderCommonTest(unittest.TestCase):
 class ISISPowderMockInst(object):
     def __init__(self, file_ext=None):
         self._file_ext = file_ext
+        self._inst_prefix = "MOCK"
 
     @staticmethod
     def _get_input_batching_mode(**_):
diff --git a/scripts/test/SANS/algorithm_detail/CMakeLists.txt b/scripts/test/SANS/algorithm_detail/CMakeLists.txt
index 1e97c3fb632701eee09a64c43470a320689a49e4..761457bfe0094748ead30cfd0e0415b19079e81e 100644
--- a/scripts/test/SANS/algorithm_detail/CMakeLists.txt
+++ b/scripts/test/SANS/algorithm_detail/CMakeLists.txt
@@ -9,6 +9,7 @@ set ( TEST_PY_FILES
   q_resolution_calculator_test.py
   scale_helper_test.py
   strip_end_nans_test.py
+  centre_finder_new_test.py
 )
 
 check_tests_valid ( ${CMAKE_CURRENT_SOURCE_DIR} ${TEST_PY_FILES} )
diff --git a/scripts/test/SANS/algorithm_detail/centre_finder_new_test.py b/scripts/test/SANS/algorithm_detail/centre_finder_new_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..dbb76582722e9819cdb32899349c36bb013f04a0
--- /dev/null
+++ b/scripts/test/SANS/algorithm_detail/centre_finder_new_test.py
@@ -0,0 +1,69 @@
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+import sys
+from sans.algorithm_detail.centre_finder_new import centre_finder_new, centre_finder_mass
+from sans.common.enums import (SANSDataType, FindDirectionEnum)
+
+if sys.version_info.major == 3:
+    from unittest import mock
+else:
+    import mock
+
+
+class CentreFinderNewTest(unittest.TestCase):
+    def setUp(self):
+        self.state = mock.MagicMock()
+
+    @mock.patch('sans.algorithm_detail.centre_finder_new.provide_loaded_data')
+    @mock.patch('sans.algorithm_detail.centre_finder_new.create_managed_non_child_algorithm')
+    def test_that_create_manage_non_child_algorithm_is_called_once_in_centre_finder_new(self, make_algorithm_mock, load_data_mock):
+        r_min = 5
+        r_max = 10
+        position_1_start = 300
+        position_2_start = -300
+        tolerance = 0.001
+        find_direction = FindDirectionEnum.All
+        iterations = 10
+        verbose = False
+
+        load_data_mock.return_value = {SANSDataType.SampleScatter: [mock.MagicMock()]}, {
+            SANSDataType.SampleScatter: [mock.MagicMock()]}
+
+        beam_centre_finder = "SANSBeamCentreFinder"
+        beam_centre_finder_options = {"Iterations": iterations, "RMin": r_min / 1000, "RMax": r_max / 1000,
+                                      "Position1Start": position_1_start, "Position2Start": position_2_start,
+                                      "Tolerance": tolerance, "Direction": FindDirectionEnum.to_string(find_direction),
+                                      "Verbose": verbose}
+
+        centre_finder_new(self.state, r_min=r_min, r_max=r_max, iterations=iterations, position_1_start=position_1_start
+                          ,position_2_start=position_2_start, tolerance=tolerance, find_direction=find_direction
+                          ,verbose=verbose)
+
+        make_algorithm_mock.assert_called_once_with(beam_centre_finder, **beam_centre_finder_options)
+
+    @mock.patch('sans.algorithm_detail.centre_finder_new.provide_loaded_data')
+    @mock.patch('sans.algorithm_detail.centre_finder_new.create_managed_non_child_algorithm')
+    def test_that_create_manage_non_child_algorithm_is_called_once_in_centre_finder_mass(self, make_algorithm_mock, load_data_mock):
+        r_min = 5
+        position_1_start = 300
+        position_2_start = -300
+        tolerance = 0.001
+        iterations = 10
+
+        load_data_mock.return_value = {SANSDataType.SampleScatter: [mock.MagicMock()]}, {
+            SANSDataType.SampleScatter: [mock.MagicMock()]}
+
+        beam_centre_finder = "SANSBeamCentreFinderMassMethod"
+        beam_centre_finder_options = {"RMin": r_min / 1000,
+                                      "Centre1": position_1_start, "Centre2": position_2_start,
+                                      "Tolerance": tolerance}
+
+        centre_finder_mass(self.state, r_min=r_min, max_iter=iterations, position_1_start=position_1_start
+                           , position_2_start=position_2_start, tolerance=tolerance)
+
+        make_algorithm_mock.assert_called_once_with(beam_centre_finder, **beam_centre_finder_options)
+
+
+if __name__ == '__main__':
+    unittest.main()
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 8f86c177166f7d0e7d3547b889c36d4822eec5ab..d0e440ba6fb923f7244220ff1f43f427099deeee 100644
--- a/scripts/test/SANS/gui_logic/beam_centre_model_test.py
+++ b/scripts/test/SANS/gui_logic/beam_centre_model_test.py
@@ -2,11 +2,9 @@ from __future__ import (absolute_import, division, print_function)
 
 import unittest
 import sys
-from sans.test_helper.mock_objects import create_mock_beam_centre_tab
 from sans.gui_logic.models.beam_centre_model import BeamCentreModel
 from sans.common.enums import FindDirectionEnum, SANSInstrument
-from sans.gui_logic.presenter.beam_centre_presenter import BeamCentrePresenter, find_beam_centre
-from sans.test_helper.mock_objects import (create_run_tab_presenter_mock)
+
 if sys.version_info.major == 3:
     from unittest import mock
 else:
@@ -14,42 +12,91 @@ else:
 
 
 class BeamCentreModelTest(unittest.TestCase):
+    def setUp(self):
+        self.result = {'pos1':300, 'pos2':-300}
+        self.centre_finder_instance = mock.MagicMock(return_value = self.result)
+        self.SANSCentreFinder = mock.MagicMock(return_value = self.centre_finder_instance)
+        self.beam_centre_model = BeamCentreModel(self.SANSCentreFinder)
 
     def test_that_model_initialises_with_correct_values(self):
-        beam_centre_model = BeamCentreModel()
-
-        self.assertEqual(beam_centre_model.max_iterations, 10)
-        self.assertEqual(beam_centre_model.r_min, 60)
-        self.assertEqual(beam_centre_model.r_max, 280)
-        self.assertEqual(beam_centre_model.left_right, True)
-        self.assertEqual(beam_centre_model.up_down, True)
-        self.assertEqual(beam_centre_model.tolerance, 0.000125)
-        self.assertEqual(beam_centre_model.lab_pos_1, '')
-        self.assertEqual(beam_centre_model.lab_pos_2, '')
-        self.assertEqual(beam_centre_model.hab_pos_2, '')
-        self.assertEqual(beam_centre_model.hab_pos_1, '')
-        self.assertEqual(beam_centre_model.scale_1, 1000)
-        self.assertEqual(beam_centre_model.scale_2, 1000)
 
+        self.assertEqual(self.beam_centre_model.max_iterations, 10)
+        self.assertEqual(self.beam_centre_model.r_min, 60)
+        self.assertEqual(self.beam_centre_model.r_max, 280)
+        self.assertEqual(self.beam_centre_model.left_right, True)
+        self.assertEqual(self.beam_centre_model.up_down, True)
+        self.assertEqual(self.beam_centre_model.tolerance, 0.000125)
+        self.assertEqual(self.beam_centre_model.lab_pos_1, '')
+        self.assertEqual(self.beam_centre_model.lab_pos_2, '')
+        self.assertEqual(self.beam_centre_model.hab_pos_2, '')
+        self.assertEqual(self.beam_centre_model.hab_pos_1, '')
+        self.assertEqual(self.beam_centre_model.scale_1, 1000)
+        self.assertEqual(self.beam_centre_model.scale_2, 1000)
+        self.assertEqual(self.beam_centre_model.COM, False)
+        self.assertEqual(self.beam_centre_model.verbose, False)
+        self.assertEqual(self.beam_centre_model.q_min, 0.01)
+        self.assertEqual(self.beam_centre_model.q_max, 0.1)
 
     def test_that_can_update_model_values(self):
-        beam_centre_model = BeamCentreModel()
-        beam_centre_model.scale_2 = 1.0
+        self.beam_centre_model.scale_2 = 1.0
 
-        self.assertEqual(beam_centre_model.scale_2, 1.0)
+        self.assertEqual(self.beam_centre_model.scale_2, 1.0)
 
     def test_that_correct_values_are_set_for_LARMOR(self):
-        beam_centre_model = BeamCentreModel()
-        beam_centre_model.reset_to_defaults_for_instrument(SANSInstrument.LARMOR)
+        self.beam_centre_model.reset_to_defaults_for_instrument(SANSInstrument.LARMOR)
 
-        self.assertEqual(beam_centre_model.scale_1, 1.0)
+        self.assertEqual(self.beam_centre_model.scale_1, 1.0)
 
     def test_that_correct_values_are_set_for_LOQ(self):
-        beam_centre_model = BeamCentreModel()
-        beam_centre_model.reset_to_defaults_for_instrument(SANSInstrument.LOQ)
+        self.beam_centre_model.reset_to_defaults_for_instrument(SANSInstrument.LOQ)
+
+        self.assertEqual(self.beam_centre_model.r_max, 200)
+
+    def test_that_find_beam_centre_calls_centre_finder_once_when_COM_is_False(self):
+        state = mock.MagicMock()
+
+        self.beam_centre_model.find_beam_centre(state)
+
+        self.SANSCentreFinder.return_value.assert_called_once_with(state, r_min=self.beam_centre_model.r_min,
+                                                                   r_max=self.beam_centre_model.r_max,
+                                                                   max_iter= self.beam_centre_model.max_iterations,
+                                                                   x_start=self.beam_centre_model.lab_pos_1,
+                                                                   y_start=self.beam_centre_model.lab_pos_2,
+                                                                   tolerance=self.beam_centre_model.tolerance,
+                                                                   find_direction=FindDirectionEnum.All,
+                                                                   reduction_method=True,
+                                                                   verbose=False)
+
+        self.assertEqual(state.convert_to_q.q_min, self.beam_centre_model.q_min)
+        self.assertEqual(state.convert_to_q.q_max, self.beam_centre_model.q_max)
+
+    def test_that_find_beam_centre_calls_centre_finder_twice_when_COM_is_TRUE(self):
+        state = mock.MagicMock()
+        self.beam_centre_model.COM = True
+
+        self.beam_centre_model.find_beam_centre(state)
+
+        self.assertEqual(self.SANSCentreFinder.return_value.call_count, 2)
+
+        self.SANSCentreFinder.return_value.assert_called_with(state, r_min=self.beam_centre_model.r_min,
+                                                              r_max=self.beam_centre_model.r_max,
+                                                              max_iter= self.beam_centre_model.max_iterations,
+                                                              x_start=self.result['pos1'],
+                                                              y_start=self.result['pos2'],
+                                                              tolerance=self.beam_centre_model.tolerance,
+                                                              find_direction=FindDirectionEnum.All,
+                                                              reduction_method=True,
+                                                              verbose=False)
 
-        self.assertEqual(beam_centre_model.r_max, 200)
+        self.SANSCentreFinder.return_value.assert_any_call(state, r_min=self.beam_centre_model.r_min,
+                                                           r_max=self.beam_centre_model.r_max,
+                                                           max_iter=self.beam_centre_model.max_iterations,
+                                                           x_start=self.beam_centre_model.lab_pos_1,
+                                                           y_start=self.beam_centre_model.lab_pos_2,
+                                                           tolerance=self.beam_centre_model.tolerance,
+                                                           find_direction=FindDirectionEnum.All,
+                                                           reduction_method=False)
 
 
 if __name__ == '__main__':
-    unittest.main()
\ No newline at end of file
+    unittest.main()
diff --git a/scripts/test/SANS/gui_logic/beam_centre_presenter_test.py b/scripts/test/SANS/gui_logic/beam_centre_presenter_test.py
index ece7cf5a2ebc0d1e47488b9a5b29371ee1269303..94419bd7c1b122a0c23d5d05f10ea43b8acc936d 100644
--- a/scripts/test/SANS/gui_logic/beam_centre_presenter_test.py
+++ b/scripts/test/SANS/gui_logic/beam_centre_presenter_test.py
@@ -2,9 +2,8 @@ from __future__ import (absolute_import, division, print_function)
 import unittest
 import sys
 from sans.test_helper.mock_objects import create_mock_beam_centre_tab
-from sans.gui_logic.models.beam_centre_model import BeamCentreModel
-from sans.common.enums import FindDirectionEnum,SANSInstrument
-from sans.gui_logic.presenter.beam_centre_presenter import BeamCentrePresenter, find_beam_centre
+from sans.common.enums import SANSInstrument
+from sans.gui_logic.presenter.beam_centre_presenter import BeamCentrePresenter
 from sans.test_helper.mock_objects import (create_run_tab_presenter_mock)
 if sys.version_info.major == 3:
     from unittest import mock
@@ -21,15 +20,16 @@ class BeamCentrePresenterTest(unittest.TestCase):
         self.view = create_mock_beam_centre_tab()
         self.WorkHandler = mock.MagicMock()
         self.BeamCentreModel = mock.MagicMock()
-        self.presenter = BeamCentrePresenter(self.parent_presenter, self.WorkHandler, self.BeamCentreModel)
+        self.SANSCentreFinder = mock.MagicMock()
+        self.presenter = BeamCentrePresenter(self.parent_presenter, self.WorkHandler, self.BeamCentreModel,
+                                             self.SANSCentreFinder)
 
     def test_that_on_run_clicked_calls_find_beam_centre(self):
         self.presenter.set_view(self.view)
         self.presenter.on_run_clicked()
 
         self.assertEqual(self.presenter._work_handler.process.call_count, 1)
-        self.assertTrue(self.presenter._work_handler.process.call_args[0][1] == find_beam_centre)
-        self.assertTrue(self.presenter._work_handler.process.call_args[0][3] == self.presenter._beam_centre_model)
+        self.assertTrue(self.presenter._work_handler.process.call_args[0][1] == self.presenter._beam_centre_model.find_beam_centre)
 
     def test_that_on_run_clicked_updates_model_from_view(self):
         self.view.left_right = False
@@ -61,7 +61,8 @@ class BeamCentrePresenterTest(unittest.TestCase):
 
     def test_that_set_scaling_is_not_called_when_file_information_does_not_exist(self):
         self.parent_presenter._file_information = None
-        self.presenter = BeamCentrePresenter(self.parent_presenter, self.WorkHandler, self.BeamCentreModel)
+        self.presenter = BeamCentrePresenter(self.parent_presenter, self.WorkHandler, self.BeamCentreModel,
+                                             self.SANSCentreFinder)
         self.presenter.set_view(self.view)
 
         self.presenter.on_update_rows()
@@ -80,24 +81,6 @@ class BeamCentrePresenterTest(unittest.TestCase):
         self.assertEqual(self.view.lab_pos_2, self.presenter._beam_centre_model.scale_2*result['pos2'])
         self.view.set_run_button_to_normal.assert_called_once_with()
 
-    @mock.patch('sans.gui_logic.presenter.beam_centre_presenter.SANSCentreFinder')
-    def test_that_find_beam_centre_initiates_centre_finder_with_correct_parameters(self, centre_finder_mock):
-        state = mock.MagicMock()
-        beam_centre_model = BeamCentreModel()
-        beam_centre_model.lab_pos_1 = 0.1
-        beam_centre_model.lab_pos_2 = -0.1
-        find_direction = FindDirectionEnum.All
-
-        find_beam_centre(state, beam_centre_model)
-
-        centre_finder_mock.return_value.called_once_with(state, r_min=beam_centre_model.r_min,
-                                                         r_max=beam_centre_model.r_max,
-                                                         max_iter=beam_centre_model.max_iterations,
-                                                         x_start=beam_centre_model.lab_pos_1,
-                                                         y_start=beam_centre_model.lab_pos_2,
-                                                         tolerance=beam_centre_model.tolerance,
-                                                         find_direction=find_direction, reduction_method=True)
-
 
 if __name__ == '__main__':
     unittest.main()