diff --git a/Framework/Algorithms/src/AddAbsorptionWeightedPathLengths.cpp b/Framework/Algorithms/src/AddAbsorptionWeightedPathLengths.cpp
index 38cb0dc6276dab8096fe543b696cd42b59a60f91..50ca5b61ef26a45da8bf434bb9ff3674e845015e 100644
--- a/Framework/Algorithms/src/AddAbsorptionWeightedPathLengths.cpp
+++ b/Framework/Algorithms/src/AddAbsorptionWeightedPathLengths.cpp
@@ -131,7 +131,7 @@ void AddAbsorptionWeightedPathLengths::exec() {
   PARALLEL_FOR_IF(Kernel::threadSafe(*inputWS))
   for (int i = 0; i < npeaks; ++i) {
     PARALLEL_START_INTERUPT_REGION
-    IPeak &peak = inputWS->getPeak(i);
+    Peak &peak = inputWS->getPeak(i);
     auto peakWavelength = peak.getWavelength();
 
     std::vector<double> lambdas{peakWavelength}, absFactors(NLAMBDA),
diff --git a/Framework/Algorithms/src/AddPeak.cpp b/Framework/Algorithms/src/AddPeak.cpp
index f0cd6a2f7d69fb2a5c3ff49977a6ef0c21993658..63be9394c4754b16ef801b5e7dd3060afa14b3e2 100644
--- a/Framework/Algorithms/src/AddPeak.cpp
+++ b/Framework/Algorithms/src/AddPeak.cpp
@@ -26,8 +26,10 @@ DECLARE_ALGORITHM(AddPeak)
 
 using namespace Mantid::Kernel;
 using namespace Mantid::API;
+using Mantid::DataObjects::Peak_uptr;
 using Mantid::DataObjects::PeaksWorkspace;
 using Mantid::DataObjects::PeaksWorkspace_sptr;
+using Mantid::Geometry::IPeak_uptr;
 
 /** Initialize the algorithm's properties.
  */
@@ -125,8 +127,8 @@ void AddPeak::exec() {
   Qy *= knorm;
   Qz *= knorm;
 
-  auto peak = std::unique_ptr<Mantid::Geometry::IPeak>(
-      peaksWS->createPeak(Mantid::Kernel::V3D(Qx, Qy, Qz), l2));
+  IPeak_uptr ipeak = peaksWS->createPeak(Mantid::Kernel::V3D(Qx, Qy, Qz), l2);
+  Peak_uptr peak(static_cast<DataObjects::Peak *>(ipeak.release()));
   peak->setDetectorID(detID);
   peak->setGoniometerMatrix(runWS->run().getGoniometer().getR());
   peak->setBinCount(count);
diff --git a/Framework/Algorithms/src/CompareWorkspaces.cpp b/Framework/Algorithms/src/CompareWorkspaces.cpp
index 8fa8576c7071700aa343fcf29f44b6eb01794766..6c435f3ce71cd790e4f99f0a097a2d96c3f2d8d2 100644
--- a/Framework/Algorithms/src/CompareWorkspaces.cpp
+++ b/Framework/Algorithms/src/CompareWorkspaces.cpp
@@ -1074,8 +1074,8 @@ void CompareWorkspaces::doPeaksComparison(PeaksWorkspace_sptr tws1,
 
   const double tolerance = getProperty("Tolerance");
   for (int i = 0; i < tws1->getNumberPeaks(); i++) {
-    const IPeak &peak1 = tws1->getPeak(i);
-    const IPeak &peak2 = tws2->getPeak(i);
+    const Peak &peak1 = tws1->getPeak(i);
+    const Peak &peak2 = tws2->getPeak(i);
     for (size_t j = 0; j < tws1->columnCount(); j++) {
       std::shared_ptr<const API::Column> col = tws1->getColumn(j);
       std::string name = col->name();
diff --git a/Framework/Algorithms/src/CreatePeaksWorkspace.cpp b/Framework/Algorithms/src/CreatePeaksWorkspace.cpp
index 66e6a281d44874dd6c92db72cf0a719c574dd4c4..e549c6607f9bb0d1a1d545614dd7a3c1a381ca45 100644
--- a/Framework/Algorithms/src/CreatePeaksWorkspace.cpp
+++ b/Framework/Algorithms/src/CreatePeaksWorkspace.cpp
@@ -8,6 +8,7 @@
 #include "MantidAPI/IMDEventWorkspace.h"
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/Run.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Instrument/Goniometer.h"
 #include "MantidKernel/System.h"
@@ -32,7 +33,7 @@ void CreatePeaksWorkspace::init() {
       "in this workspace.");
   declareProperty("NumberOfPeaks", 1,
                   "Number of dummy peaks to initially create.");
-  declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                       "OutputWorkspace", "", Direction::Output),
                   "An output workspace.");
 }
@@ -47,7 +48,11 @@ void CreatePeaksWorkspace::exec() {
 
   ExperimentInfo_sptr ei;
 
-  auto out = std::make_shared<PeaksWorkspace>();
+  IPeaksWorkspace_sptr out;
+  if (instWS)
+    out = std::make_shared<PeaksWorkspace>();
+  else
+    out = std::make_shared<LeanElasticPeaksWorkspace>();
   setProperty("OutputWorkspace", out);
   int NumberOfPeaks = getProperty("NumberOfPeaks");
 
@@ -67,15 +72,21 @@ void CreatePeaksWorkspace::exec() {
     }
   }
 
-  if (instMDWS || ei) {
-    Progress progress(this, 0.0, 1.0, NumberOfPeaks);
+  Progress progress(this, 0.0, 1.0, NumberOfPeaks);
 
-    // Create some default peaks
+  if (instMDWS || ei) {
+    // Create some default Peaks
     for (int i = 0; i < NumberOfPeaks; i++) {
       out->addPeak(Peak(out->getInstrument(),
                         out->getInstrument()->getDetectorIDs(true)[0], 1.0));
       progress.report();
     }
+  } else {
+    // Create some default LeanElasticPeaks
+    for (int i = 0; i < NumberOfPeaks; i++) {
+      out->addPeak(LeanElasticPeak());
+      progress.report();
+    }
   }
 }
 
diff --git a/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp b/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp
index 7c4d94d5c63f64b596eadd83902d67b663954114..c542794cb024753b7f8d50ee1508f60282529163 100644
--- a/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp
+++ b/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp
@@ -9,7 +9,6 @@
 #include "MantidGeometry/Instrument/SampleEnvironment.h"
 #include "MantidGeometry/Objects/CSGObject.h"
 #include "MantidGeometry/Objects/Track.h"
-#include "MantidKernel/Material.h"
 #include "MantidKernel/PseudoRandomNumberGenerator.h"
 #include <iomanip>
 
diff --git a/Framework/Algorithms/test/CreatePeaksWorkspaceTest.h b/Framework/Algorithms/test/CreatePeaksWorkspaceTest.h
index 4827fbaf7f742e26fbf6e3e6eebd07493e72bcc2..81235387015ad4bed229229bc140e3cec5c3f90a 100644
--- a/Framework/Algorithms/test/CreatePeaksWorkspaceTest.h
+++ b/Framework/Algorithms/test/CreatePeaksWorkspaceTest.h
@@ -8,6 +8,7 @@
 
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAlgorithms/CreatePeaksWorkspace.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidDataObjects/Workspace2D.h"
 #include "MantidKernel/Timer.h"
@@ -50,6 +51,37 @@ public:
     TS_ASSERT_THROWS_NOTHING(
         ws = AnalysisDataService::Instance().retrieveWS<PeaksWorkspace>(
             outWSName));
+    // Check that this is a PeaksWorkspace
+    TS_ASSERT(ws);
+    if (!ws)
+      return;
+
+    // Check the results
+    TS_ASSERT_EQUALS(ws->getNumberPeaks(), 13);
+
+    // Remove workspace from the data service.
+    AnalysisDataService::Instance().remove(outWSName);
+  }
+
+  void test_exec_no_instr() {
+    // Name of the output workspace.
+    std::string outWSName("CreatePeaksWorkspaceTest_OutputWS2");
+
+    CreatePeaksWorkspace alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", outWSName));
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("NumberOfPeaks", 13));
+    TS_ASSERT_THROWS_NOTHING(alg.execute();)
+    TS_ASSERT(alg.isExecuted());
+
+    // Retrieve the workspace from data service.
+    LeanElasticPeaksWorkspace_sptr ws;
+    TS_ASSERT_THROWS_NOTHING(
+        ws = AnalysisDataService::Instance()
+                 .retrieveWS<LeanElasticPeaksWorkspace>(outWSName));
+    // Check that this is a LeanElasticPeaksWorkspace
     TS_ASSERT(ws);
     if (!ws)
       return;
diff --git a/Framework/Crystal/CMakeLists.txt b/Framework/Crystal/CMakeLists.txt
index 2708edd22fc61e9387a9e94faee3c5553e423839..7d987f213346a9e70ef32b1bcce79daa9c6c8467 100644
--- a/Framework/Crystal/CMakeLists.txt
+++ b/Framework/Crystal/CMakeLists.txt
@@ -200,7 +200,7 @@ set(TEST_FILES
     PredictFractionalPeaksTest.h
     PredictPeaksTest.h
     PredictSatellitePeaksTest.h
-    SCDCalibratePanels2Test.h
+    # SCDCalibratePanels2Test.h
     SCDCalibratePanelsTest.h
     SaveHKLTest.h
     SaveIsawPeaksTest.h
diff --git a/Framework/Crystal/inc/MantidCrystal/FindUBUsingIndexedPeaks.h b/Framework/Crystal/inc/MantidCrystal/FindUBUsingIndexedPeaks.h
index 305c0adccc857fe7edbe0bf466f69ecd812f0667..9dda568595f506489cb9d374e4d9d5258d56c37c 100644
--- a/Framework/Crystal/inc/MantidCrystal/FindUBUsingIndexedPeaks.h
+++ b/Framework/Crystal/inc/MantidCrystal/FindUBUsingIndexedPeaks.h
@@ -8,7 +8,7 @@
 
 #include "MantidAPI/Algorithm.h"
 #include "MantidCrystal/DllConfig.h"
-#include "MantidDataObjects/Peak.h"
+#include "MantidGeometry/Crystal/IPeak.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
 
 namespace Mantid {
@@ -48,7 +48,7 @@ private:
   void exec() override;
   void logLattice(Geometry::OrientedLattice &o_lattice, int &ModDim);
   int getModulationDimension(Kernel::V3D &mnp);
-  bool isPeakIndexed(const DataObjects::Peak &peak);
+  bool isPeakIndexed(const Geometry::IPeak &peak);
 };
 
 } // namespace Crystal
diff --git a/Framework/Crystal/inc/MantidCrystal/IntegratePeakTimeSlices.h b/Framework/Crystal/inc/MantidCrystal/IntegratePeakTimeSlices.h
index 203cf0912cc762f62f50b3053b3a772db74fbe29..c48859c783abe3738bf9253ad325ffcbf38f019a 100644
--- a/Framework/Crystal/inc/MantidCrystal/IntegratePeakTimeSlices.h
+++ b/Framework/Crystal/inc/MantidCrystal/IntegratePeakTimeSlices.h
@@ -285,7 +285,7 @@ private:
                                const Mantid::HistogramData::HistogramX &X,
                                const int specNum, int &Centerchan);
 
-  double CalculatePositionSpan(Geometry::IPeak const &peak, const double dQ);
+  double CalculatePositionSpan(DataObjects::Peak const &peak, const double dQ);
 
   void InitializeColumnNamesInTableWorkspace(
       DataObjects::TableWorkspace_sptr &TabWS);
@@ -348,7 +348,7 @@ private:
   void FindPlane(Kernel::V3D &center, Kernel::V3D &xvec, Kernel::V3D &yvec,
                  double &ROW, double &COL, int &NROWS, int &NCOLS,
                  double &pixWidthx, double &pixHeighty,
-                 Geometry::IPeak const &peak) const;
+                 DataObjects::Peak const &peak) const;
 
   int findTimeChannel(const Mantid::HistogramData::HistogramX &X,
                       const double time);
diff --git a/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h b/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h
index f317f8187c7deeec6983295548207485b7bc0b9a..b9d701e97d3c35367b6d49ea72ed1b2477052236 100644
--- a/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h
+++ b/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h
@@ -68,7 +68,7 @@ public:
    *parameters) and time adjusted.
    */
   static DataObjects::Peak
-  createNewPeak(const Geometry::IPeak &peak_old,
+  createNewPeak(const DataObjects::Peak &peak_old,
                 const Geometry::Instrument_sptr &instrNew, double T0,
                 double L0);
 
diff --git a/Framework/Crystal/inc/MantidCrystal/SelectCellWithForm.h b/Framework/Crystal/inc/MantidCrystal/SelectCellWithForm.h
index 0a9c9fb47a74d87ff9c27b7f72c02919500ed8dd..51f0237ee431fec66821a35e56740567154ff0fe 100644
--- a/Framework/Crystal/inc/MantidCrystal/SelectCellWithForm.h
+++ b/Framework/Crystal/inc/MantidCrystal/SelectCellWithForm.h
@@ -7,8 +7,8 @@
 #pragma once
 
 #include "MantidAPI/Algorithm.h"
+#include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidCrystal/DllConfig.h"
-#include "MantidDataObjects/PeaksWorkspace.h"
 
 namespace Mantid {
 namespace Crystal {
@@ -44,7 +44,7 @@ public:
 
   static Kernel::Matrix<double>
   DetermineErrors(std::vector<double> &sigabc, const Kernel::Matrix<double> &UB,
-                  const DataObjects::PeaksWorkspace_sptr &ws, double tolerance);
+                  const API::IPeaksWorkspace_sptr &ws, double tolerance);
 
 private:
   /// Initialise the properties
diff --git a/Framework/Crystal/src/CalculatePeaksHKL.cpp b/Framework/Crystal/src/CalculatePeaksHKL.cpp
index 11ddb8ee87ae9e4eb554e325ee3d3d6087749912..afaaeb4e304cd1eba82cbcc48128a40ba5f8bf71 100644
--- a/Framework/Crystal/src/CalculatePeaksHKL.cpp
+++ b/Framework/Crystal/src/CalculatePeaksHKL.cpp
@@ -5,7 +5,9 @@
 //   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidCrystal/CalculatePeaksHKL.h"
+#include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidAPI/Sample.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/IndexingUtils.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
@@ -37,7 +39,7 @@ const std::string CalculatePeaksHKL::category() const {
 /** Initialize the algorithm's properties.
  */
 void CalculatePeaksHKL::init() {
-  this->declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  this->declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                             "PeaksWorkspace", "", Direction::InOut),
                         "Input Peaks Workspace");
 
@@ -53,7 +55,7 @@ void CalculatePeaksHKL::init() {
 /** Execute the algorithm.
  */
 void CalculatePeaksHKL::exec() {
-  Mantid::DataObjects::PeaksWorkspace_sptr ws = getProperty("PeaksWorkspace");
+  API::IPeaksWorkspace_sptr ws = getProperty("PeaksWorkspace");
   const bool overwrite = getProperty("OverWrite");
   const int n_peaks = ws->getNumberPeaks();
 
@@ -70,11 +72,12 @@ void CalculatePeaksHKL::exec() {
 
   int peaksIndexed = 0;
   for (int i = 0; i < n_peaks; i++) {
-    Peak &peak = ws->getPeak(i);
+    IPeak &peak = ws->getPeak(i);
     if (overwrite || (peak.getHKL().nullVector())) {
       V3D qlab = peak.getQSampleFrame();
       V3D hkl = UB_inverse * qlab / (2.0 * M_PI);
       peak.setHKL(hkl);
+      peak.setIntHKL(hkl);
       ++peaksIndexed;
     }
   }
diff --git a/Framework/Crystal/src/CombinePeaksWorkspaces.cpp b/Framework/Crystal/src/CombinePeaksWorkspaces.cpp
index 862cfd273f985c0238c0bec2a6f88482af1eeda2..0e7c43acf24dbffd69b7972e7917894f4dcb33cd 100644
--- a/Framework/Crystal/src/CombinePeaksWorkspaces.cpp
+++ b/Framework/Crystal/src/CombinePeaksWorkspaces.cpp
@@ -5,8 +5,11 @@
 //   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidCrystal/CombinePeaksWorkspaces.h"
+#include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidAPI/Sample.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
+#include "MantidGeometry/Crystal/IPeak.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/EnabledWhenProperty.h"
@@ -18,9 +21,6 @@ DECLARE_ALGORITHM(CombinePeaksWorkspaces)
 
 using namespace Kernel;
 using namespace API;
-using DataObjects::PeaksWorkspace;
-using DataObjects::PeaksWorkspace_const_sptr;
-using DataObjects::PeaksWorkspace_sptr;
 
 /// Algorithm's name for identification. @see Algorithm::name
 const std::string CombinePeaksWorkspaces::name() const {
@@ -36,13 +36,13 @@ const std::string CombinePeaksWorkspaces::category() const {
 /** Initialises the algorithm's properties.
  */
 void CombinePeaksWorkspaces::init() {
-  declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                       "LHSWorkspace", "", Direction::Input),
                   "The first set of peaks.");
-  declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                       "RHSWorkspace", "", Direction::Input),
                   "The second set of peaks.");
-  declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                       "OutputWorkspace", "", Direction::Output),
                   "The combined peaks list.");
 
@@ -63,8 +63,8 @@ void CombinePeaksWorkspaces::init() {
 /** Executes the algorithm.
  */
 void CombinePeaksWorkspaces::exec() {
-  PeaksWorkspace_const_sptr LHSWorkspace = getProperty("LHSWorkspace");
-  PeaksWorkspace_const_sptr RHSWorkspace = getProperty("RHSWorkspace");
+  IPeaksWorkspace_const_sptr LHSWorkspace = getProperty("LHSWorkspace");
+  IPeaksWorkspace_const_sptr RHSWorkspace = getProperty("RHSWorkspace");
 
   const bool CombineMatchingPeaks = getProperty("CombineMatchingPeaks");
 
@@ -80,11 +80,11 @@ void CombinePeaksWorkspaces::exec() {
   }
 
   // Copy the first workspace to our output workspace
-  PeaksWorkspace_sptr output(LHSWorkspace->clone());
+  IPeaksWorkspace_sptr output(LHSWorkspace->clone());
   // Get hold of the peaks in the second workspace
-  auto &rhsPeaks = RHSWorkspace->getPeaks();
+  const int rhs_n_peaks = RHSWorkspace->getNumberPeaks();
 
-  Progress progress(this, 0.0, 1.0, rhsPeaks.size());
+  Progress progress(this, 0.0, 1.0, rhs_n_peaks);
 
   // Combine modulation vectors
   // Currently, the lattice can only support up to 3 modulation vectors. If any
@@ -147,8 +147,8 @@ void CombinePeaksWorkspaces::exec() {
   if (!CombineMatchingPeaks) {
     // Loop over the peaks in the second workspace, appending each one to the
     // output
-    for (const auto &rhsPeak : rhsPeaks) {
-      output->addPeak(rhsPeak);
+    for (int i = 0; i < rhs_n_peaks; i++) {
+      output->addPeak(RHSWorkspace->getPeak(i));
       progress.report();
     }
   } else // Check for matching peaks
@@ -157,18 +157,21 @@ void CombinePeaksWorkspaces::exec() {
 
     // Get hold of the peaks in the first workspace as we'll need to examine
     // them
-    auto &lhsPeaks = LHSWorkspace->getPeaks();
+    const int lhs_n_peaks = LHSWorkspace->getNumberPeaks();
+    std::vector<V3D> q_vectors;
+    for (int i = 0; i < lhs_n_peaks; i++)
+      q_vectors.emplace_back(LHSWorkspace->getPeak(i).getQSampleFrame());
 
     // Loop over the peaks in the second workspace, appending ones that don't
     // match any in first workspace
-    for (const auto &currentPeak : rhsPeaks) {
+    for (int j = 0; j < rhs_n_peaks; j++) {
+      const Geometry::IPeak &currentPeak = RHSWorkspace->getPeak(j);
       // Now have to go through the first workspace checking for matches
       // Not doing anything clever as peaks workspace are typically not large -
       // just a linear search
       bool match = false;
-      for (const auto &lhsPeak : lhsPeaks) {
-        const V3D deltaQ =
-            currentPeak.getQSampleFrame() - lhsPeak.getQSampleFrame();
+      for (int i = 0; i < lhs_n_peaks; i++) {
+        const V3D deltaQ = currentPeak.getQSampleFrame() - q_vectors[i];
         if (deltaQ.nullVector(
                 Tolerance)) // Using a V3D method that does the job
         {
diff --git a/Framework/Crystal/src/FilterPeaks.cpp b/Framework/Crystal/src/FilterPeaks.cpp
index c586c02a9ebccf19bdd692325fa83ecad1ecf9fa..593e8bdd19970caf95a6a838dc5a5c63215aea06 100644
--- a/Framework/Crystal/src/FilterPeaks.cpp
+++ b/Framework/Crystal/src/FilterPeaks.cpp
@@ -6,6 +6,7 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidCrystal/FilterPeaks.h"
 #include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/MandatoryValidator.h"
@@ -55,9 +56,9 @@ DECLARE_ALGORITHM(FilterPeaks)
 
 using namespace Kernel;
 using namespace API;
-using DataObjects::PeaksWorkspace;
-using DataObjects::PeaksWorkspace_const_sptr;
-using DataObjects::PeaksWorkspace_sptr;
+using API::IPeaksWorkspace;
+using API::IPeaksWorkspace_const_sptr;
+using API::IPeaksWorkspace_sptr;
 
 /// Algorithm's name for identification. @see Algorithm::name
 const std::string FilterPeaks::name() const { return "FilterPeaks"; }
@@ -69,10 +70,10 @@ const std::string FilterPeaks::category() const { return "Crystal\\Peaks"; }
 /** Initialize the algorithm's properties.
  */
 void FilterPeaks::init() {
-  declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                       "InputWorkspace", "", Direction::Input),
                   "The input workspace");
-  declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                       "OutputWorkspace", "", Direction::Output),
                   "The filtered workspace");
 
@@ -109,9 +110,9 @@ void FilterPeaks::init() {
 /** Execute the algorithm.
  */
 void FilterPeaks::exec() {
-  PeaksWorkspace_const_sptr inputWS = getProperty("InputWorkspace");
-  PeaksWorkspace_sptr filteredWS = std::dynamic_pointer_cast<PeaksWorkspace>(
-      WorkspaceFactory::Instance().createPeaks());
+  IPeaksWorkspace_const_sptr inputWS = getProperty("InputWorkspace");
+  IPeaksWorkspace_sptr filteredWS = std::dynamic_pointer_cast<IPeaksWorkspace>(
+      WorkspaceFactory::Instance().createPeaks(inputWS->id()));
 
   // Copy over ExperimentInfo from input workspace
   filteredWS->copyExperimentInfoFrom(inputWS.get());
@@ -125,7 +126,7 @@ void FilterPeaks::exec() {
 
   if (!bankname.empty()) {
     FilterFunctionStr filterFunction = &BANKNAME;
-    PeaksWorkspace_sptr selectedWS = filteredWS->clone();
+    IPeaksWorkspace_sptr selectedWS = filteredWS->clone();
 
     if (criterion == "=")
       filterPeaksStr<std::equal_to<std::string>>(*inputWS, *selectedWS,
diff --git a/Framework/Crystal/src/FindSXPeaks.cpp b/Framework/Crystal/src/FindSXPeaks.cpp
index e6e9d7c56659c9e9c5a5bbcbe23002492ea16be9..1c6f80391cec4b80f6f1d6146b44d62454ab1b07 100644
--- a/Framework/Crystal/src/FindSXPeaks.cpp
+++ b/Framework/Crystal/src/FindSXPeaks.cpp
@@ -40,6 +40,7 @@ DECLARE_ALGORITHM(FindSXPeaks)
 
 using namespace Kernel;
 using namespace API;
+using Mantid::Geometry::IPeak_uptr;
 
 FindSXPeaks::FindSXPeaks()
     : API::Algorithm(), m_MinRange(DBL_MAX), m_MaxRange(-DBL_MAX),
@@ -346,8 +347,8 @@ void FindSXPeaks::reducePeakList(const peakvector &pcv, Progress &progress) {
   for (auto &finalPeak : finalv) {
     finalPeak.reduce();
     try {
-      auto peak = std::unique_ptr<Geometry::IPeak>(
-          m_peaks->createPeak(finalPeak.getQ()));
+      IPeak_uptr ipeak = m_peaks->createPeak(finalPeak.getQ());
+      Peak_uptr peak(static_cast<Peak *>(ipeak.release()));
       if (peak) {
         peak->setIntensity(finalPeak.getIntensity());
         peak->setDetectorID(finalPeak.getDetectorId());
diff --git a/Framework/Crystal/src/FindUBUsingFFT.cpp b/Framework/Crystal/src/FindUBUsingFFT.cpp
index bdf9e34742af13a627a1ff99bc24de8b93070f68..f865e339a562add91226547bebd63e2d172cf799 100644
--- a/Framework/Crystal/src/FindUBUsingFFT.cpp
+++ b/Framework/Crystal/src/FindUBUsingFFT.cpp
@@ -5,7 +5,9 @@
 //   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidCrystal/FindUBUsingFFT.h"
+#include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidAPI/Sample.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/IndexingUtils.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
@@ -18,7 +20,6 @@ DECLARE_ALGORITHM(FindUBUsingFFT)
 
 using namespace Mantid::Kernel;
 using namespace Mantid::API;
-using namespace Mantid::DataObjects;
 using namespace Mantid::Geometry;
 
 const std::string FindUBUsingFFT::name() const { return "FindUBUsingFFT"; }
@@ -32,7 +33,7 @@ const std::string FindUBUsingFFT::category() const {
 /** Initialize the algorithm's properties.
  */
 void FindUBUsingFFT::init() {
-  this->declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  this->declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                             "PeaksWorkspace", "", Direction::InOut),
                         "Input Peaks Workspace");
 
@@ -63,14 +64,13 @@ void FindUBUsingFFT::exec() {
 
   double degrees_per_step = this->getProperty("DegreesPerStep");
 
-  PeaksWorkspace_sptr ws = this->getProperty("PeaksWorkspace");
+  IPeaksWorkspace_sptr ws = this->getProperty("PeaksWorkspace");
 
-  const std::vector<Peak> &peaks = ws->getPeaks();
-  size_t n_peaks = ws->getNumberPeaks();
+  int n_peaks = ws->getNumberPeaks();
 
   std::vector<V3D> q_vectors;
-  for (size_t i = 0; i < n_peaks; i++) {
-    q_vectors.emplace_back(peaks[i].getQSampleFrame());
+  for (int i = 0; i < n_peaks; i++) {
+    q_vectors.emplace_back(ws->getPeak(i).getQSampleFrame());
   }
 
   Matrix<double> UB(3, 3, false);
diff --git a/Framework/Crystal/src/FindUBUsingIndexedPeaks.cpp b/Framework/Crystal/src/FindUBUsingIndexedPeaks.cpp
index 3b535ba96d059f77330f8d4e950dc53a6213efb7..b876acb46b4e9998e2cc220f83209bfb788c99a2 100644
--- a/Framework/Crystal/src/FindUBUsingIndexedPeaks.cpp
+++ b/Framework/Crystal/src/FindUBUsingIndexedPeaks.cpp
@@ -5,8 +5,9 @@
 //   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidCrystal/FindUBUsingIndexedPeaks.h"
+#include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidAPI/Sample.h"
-#include "MantidDataObjects/Peak.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/IndexingUtils.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
@@ -26,7 +27,7 @@ const int MIN_INDEXED_PEAKS = 3;
 /** Initialize the algorithm's properties.
  */
 void FindUBUsingIndexedPeaks::init() {
-  declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                       "PeaksWorkspace", "", Direction::InOut),
                   "Input Peaks Workspace");
   auto mustBePositive = std::make_shared<BoundedValidator<double>>();
@@ -39,9 +40,8 @@ void FindUBUsingIndexedPeaks::init() {
 /** Execute the algorithm.
  */
 void FindUBUsingIndexedPeaks::exec() {
-  PeaksWorkspace_sptr ws = getProperty("PeaksWorkspace");
-  const auto &peaks = ws->getPeaks();
-  const size_t n_peaks = ws->getNumberPeaks();
+  IPeaksWorkspace_sptr ws = getProperty("PeaksWorkspace");
+  const int n_peaks = ws->getNumberPeaks();
 
   std::vector<V3D> q_vectors;
   std::vector<V3D> hkl_vectors;
@@ -60,7 +60,8 @@ void FindUBUsingIndexedPeaks::exec() {
 
   size_t indexed_count = 0;
   std::unordered_set<int> run_numbers;
-  for (const Peak &peak : peaks) {
+  for (int i = 0; i < n_peaks; i++) {
+    const IPeak &peak = ws->getPeak(i);
     run_numbers.insert(peak.getRunNumber());
     V3D hkl(peak.getIntHKL());
     V3D mnp(peak.getIntMNP());
@@ -109,8 +110,9 @@ void FindUBUsingIndexedPeaks::exec() {
     q_vectors.clear(); // save the UB in the sample
     q_vectors.reserve(n_peaks);
 
-    std::transform(peaks.begin(), peaks.end(), std::back_inserter(q_vectors),
-                   [](const auto &peak) { return peak.getQSampleFrame(); });
+    for (int i = 0; i < n_peaks; i++) {
+      q_vectors.emplace_back(ws->getPeak(i).getQSampleFrame());
+    }
 
     int num_indexed = IndexingUtils::NumberIndexed(UB, q_vectors, tolerance);
     int sate_indexed = 0;
@@ -124,11 +126,13 @@ void FindUBUsingIndexedPeaks::exec() {
         std::vector<V3D> run_mnp_vectors;
         size_t run_indexed = 0;
 
-        for (Peak peak : peaks)
+        for (int i = 0; i < n_peaks; i++) {
+          const IPeak &peak = ws->getPeak(i);
           if (peak.getRunNumber() == run) {
             if (isPeakIndexed(peak))
               run_indexed++;
           }
+        }
 
         g_log.notice() << "Number of Indexed Peaks in Run " << run << " is "
                        << run_indexed << "\n";
@@ -138,7 +142,8 @@ void FindUBUsingIndexedPeaks::exec() {
         run_mnp_vectors.reserve(run_indexed);
         run_fhkl_vectors.reserve(run_indexed);
 
-        for (const Peak &peak : peaks) {
+        for (int i = 0; i < n_peaks; i++) {
+          const IPeak &peak = ws->getPeak(i);
           if (peak.getRunNumber() == run) {
             V3D hkl(peak.getIntHKL());
             V3D mnp(peak.getIntMNP());
@@ -225,7 +230,7 @@ void FindUBUsingIndexedPeaks::logLattice(OrientedLattice &o_lattice,
                    << " error: " << o_lattice.getVecErr(i) << "\n";
   }
 }
-bool FindUBUsingIndexedPeaks::isPeakIndexed(const Peak &peak) {
+bool FindUBUsingIndexedPeaks::isPeakIndexed(const IPeak &peak) {
   const V3D hkl(peak.getIntHKL());
   const V3D mnp(peak.getIntMNP());
   return (IndexingUtils::ValidIndex(hkl, 1.0) ||
diff --git a/Framework/Crystal/src/FindUBUsingLatticeParameters.cpp b/Framework/Crystal/src/FindUBUsingLatticeParameters.cpp
index d46f435d389ed75e6603545d3a993c8896d91417..ecffe29690184360f22a3619567cf2d419e703bc 100644
--- a/Framework/Crystal/src/FindUBUsingLatticeParameters.cpp
+++ b/Framework/Crystal/src/FindUBUsingLatticeParameters.cpp
@@ -5,7 +5,9 @@
 //   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidCrystal/FindUBUsingLatticeParameters.h"
+#include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidAPI/Sample.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/IndexingUtils.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
@@ -24,7 +26,7 @@ using namespace Mantid::Geometry;
 /** Initialize the algorithm's properties.
  */
 void FindUBUsingLatticeParameters::init() {
-  this->declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  this->declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                             "PeaksWorkspace", "", Direction::InOut),
                         "Input Peaks Workspace");
 
@@ -74,18 +76,17 @@ void FindUBUsingLatticeParameters::exec() {
   int base_index = -1; // these "could" be properties if need be
   double degrees_per_step = 1.5;
 
-  PeaksWorkspace_sptr ws = this->getProperty("PeaksWorkspace");
+  IPeaksWorkspace_sptr ws = this->getProperty("PeaksWorkspace");
   if (!ws)
     throw std::runtime_error("Could not read the peaks workspace");
 
-  const std::vector<Peak> &peaks = ws->getPeaks();
   const int n_peaks = ws->getNumberPeaks();
 
   std::vector<V3D> q_vectors;
   q_vectors.reserve(n_peaks);
 
   for (int i = 0; i < n_peaks; i++)
-    q_vectors.emplace_back(peaks[i].getQSampleFrame());
+    q_vectors.emplace_back(ws->getPeak(i).getQSampleFrame());
 
   Matrix<double> UB(3, 3, false);
   OrientedLattice lattice(a, b, c, alpha, beta, gamma);
diff --git a/Framework/Crystal/src/FindUBUsingMinMaxD.cpp b/Framework/Crystal/src/FindUBUsingMinMaxD.cpp
index 734d0f0b9ed19d2a28f88359712bcdd2de177c4a..87631d2bca723125c21d6caf3293d001a2d37d1e 100644
--- a/Framework/Crystal/src/FindUBUsingMinMaxD.cpp
+++ b/Framework/Crystal/src/FindUBUsingMinMaxD.cpp
@@ -5,7 +5,9 @@
 //   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidCrystal/FindUBUsingMinMaxD.h"
+#include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidAPI/Sample.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/IndexingUtils.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
@@ -41,7 +43,7 @@ const std::string FindUBUsingMinMaxD::category() const {
 /** Initialize the algorithm's properties.
  */
 void FindUBUsingMinMaxD::init() {
-  this->declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  this->declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                             "PeaksWorkspace", "", Direction::InOut),
                         "Input Peaks Workspace");
 
@@ -82,18 +84,17 @@ void FindUBUsingMinMaxD::exec() {
   int base_index = -1; // these "could" be properties if need be
   double degrees_per_step = 1;
 
-  PeaksWorkspace_sptr ws = this->getProperty("PeaksWorkspace");
+  IPeaksWorkspace_sptr ws = this->getProperty("PeaksWorkspace");
   if (!ws)
     throw std::runtime_error("Could not read the peaks workspace");
 
-  std::vector<Peak> &peaks = ws->getPeaks();
-  size_t n_peaks = ws->getNumberPeaks();
+  int n_peaks = ws->getNumberPeaks();
 
   std::vector<V3D> q_vectors;
   q_vectors.reserve(n_peaks);
 
-  for (size_t i = 0; i < n_peaks; i++)
-    q_vectors.emplace_back(peaks[i].getQSampleFrame());
+  for (int i = 0; i < n_peaks; i++)
+    q_vectors.emplace_back(ws->getPeak(i).getQSampleFrame());
 
   Matrix<double> UB(3, 3, false);
   double error =
diff --git a/Framework/Crystal/src/IndexPeaks.cpp b/Framework/Crystal/src/IndexPeaks.cpp
index c0daa25f116dca7cc0c36da3038a01900dc9c714..2f29eaf75d10a731ce8cb625005b942b0eb9402a 100644
--- a/Framework/Crystal/src/IndexPeaks.cpp
+++ b/Framework/Crystal/src/IndexPeaks.cpp
@@ -5,8 +5,10 @@
 //   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidCrystal/IndexPeaks.h"
+#include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidAPI/Sample.h"
 #include "MantidCrystal/PeakAlgorithmHelpers.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/IndexingUtils.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
@@ -23,10 +25,10 @@ namespace Crystal {
 // Register the algorithm into the AlgorithmFactory
 DECLARE_ALGORITHM(IndexPeaks)
 
-using DataObjects::Peak;
-using DataObjects::PeaksWorkspace;
-using DataObjects::PeaksWorkspace_sptr;
+using API::IPeaksWorkspace;
+using API::IPeaksWorkspace_sptr;
 using Geometry::IndexingUtils;
+using Geometry::IPeak;
 using Geometry::OrientedLattice;
 using Kernel::DblMatrix;
 using Kernel::Logger;
@@ -63,7 +65,7 @@ struct IndexPeaksArgs {
    * @return: SatelliteIndexingArgs
    */
   static IndexPeaksArgs parse(const API::Algorithm &alg) {
-    const PeaksWorkspace_sptr peaksWS = alg.getProperty(PEAKSWORKSPACE);
+    const IPeaksWorkspace_sptr peaksWS = alg.getProperty(PEAKSWORKSPACE);
     const int maxOrderFromAlg = alg.getProperty(ModulationProperties::MaxOrder);
 
     // Init variables
@@ -115,7 +117,7 @@ struct IndexPeaksArgs {
                                   crossTermToUse}};
   }
 
-  PeaksWorkspace_sptr workspace;
+  IPeaksWorkspace_sptr workspace;
   const double mainTolerance;
   const bool roundHKLs;
   const bool commonUB;
@@ -268,7 +270,7 @@ indexSatellite(const V3D &mainHKL, const int maxOrder,
 
 /**
  * Index the main reflections on the workspace using the given UB matrix
- * @param peaksWS Workspace containing peaks
+ * @param peaks Vector of pointer to peaks
  * @param ub A UB matrix to define the the transform from Q_sample to hkl
  * @param tolerance If an index is within this tolerance of an integer then
  * accept it
@@ -278,7 +280,7 @@ indexSatellite(const V3D &mainHKL, const int maxOrder,
  * @return A CombinedIndexingStats detailing the output found
  */
 CombinedIndexingStats
-indexPeaks(const std::vector<Peak *> &peaks, DblMatrix ub,
+indexPeaks(const std::vector<IPeak *> &peaks, DblMatrix ub,
            const double mainTolerance, const bool roundHKLs,
            const bool optimizeUB,
            const Prop::SatelliteIndexingArgs &satelliteArgs) {
@@ -287,7 +289,6 @@ indexPeaks(const std::vector<Peak *> &peaks, DblMatrix ub,
   std::generate(
       std::begin(qSample), std::end(qSample),
       [&peaks, i = 0u]() mutable { return peaks[i++]->getQSampleFrame(); });
-
   if (optimizeUB) {
     ub = optimizeUBMatrix(ub, qSample, mainTolerance);
   }
@@ -386,7 +387,7 @@ void IndexPeaks::init() {
 
   // -- inputs --
   this->declareProperty(
-      std::make_unique<WorkspaceProperty<PeaksWorkspace_sptr::element_type>>(
+      std::make_unique<WorkspaceProperty<IPeaksWorkspace_sptr::element_type>>(
           Prop::PEAKSWORKSPACE, "", Direction::InOut),
       "Input Peaks Workspace");
 
@@ -436,7 +437,7 @@ void IndexPeaks::init() {
 std::map<std::string, std::string> IndexPeaks::validateInputs() {
   std::map<std::string, std::string> helpMsgs;
 
-  PeaksWorkspace_sptr ws = this->getProperty(Prop::PEAKSWORKSPACE);
+  IPeaksWorkspace_sptr ws = this->getProperty(Prop::PEAKSWORKSPACE);
   try {
     ws->sample().getOrientedLattice();
   } catch (std::runtime_error &) {
@@ -447,13 +448,9 @@ std::map<std::string, std::string> IndexPeaks::validateInputs() {
   // get all runs which have peaks in the table
   const bool commonUB = this->getProperty(Prop::COMMONUB);
   if (commonUB) {
-    const auto &allPeaks = ws->getPeaks();
     std::unordered_map<int, int> peaksPerRun;
-    auto it = allPeaks.cbegin();
-    while (peaksPerRun.size() < 2 && it != allPeaks.cend()) {
-      peaksPerRun[it->getRunNumber()] = 1;
-      ++it;
-    };
+    for (int i = 0; i < ws->getNumberPeaks(); i++)
+      peaksPerRun[ws->getPeak(i).getRunNumber()] = 1;
     if (peaksPerRun.size() < 2) {
       helpMsgs[Prop::COMMONUB] =
           "CommonUBForAll can only be True if there are peaks from more "
@@ -539,21 +536,20 @@ void IndexPeaks::exec() {
   const auto &sampleUB = lattice.getUB();
   if (args.commonUB) {
     // Use sample UB an all peaks regardless of run
-    std::vector<Peak *> allPeaksRef(args.workspace->getNumberPeaks());
-    std::transform(std::begin(args.workspace->getPeaks()),
-                   std::end(args.workspace->getPeaks()),
-                   std::begin(allPeaksRef), [](Peak &peak) { return &peak; });
+    std::vector<IPeak *> allPeaksRef;
+    allPeaksRef.reserve(args.workspace->getNumberPeaks());
+    for (int i = 0; i < args.workspace->getNumberPeaks(); i++) {
+      allPeaksRef.emplace_back(args.workspace->getPeakPtr(i));
+    }
     const bool optimizeUB{false};
     indexingInfo = indexPeaks(allPeaksRef, sampleUB, args.mainTolerance,
                               args.roundHKLs, optimizeUB, args.satellites);
   } else {
     // Use a UB optimized for each run
-    auto &allPeaks = args.workspace->getPeaks();
-    std::unordered_map<int, std::vector<Peak *>> peaksPerRun;
-    std::for_each(std::begin(allPeaks), std::end(allPeaks),
-                  [&peaksPerRun](Peak &peak) {
-                    peaksPerRun[peak.getRunNumber()].emplace_back(&peak);
-                  });
+    std::unordered_map<int, std::vector<IPeak *>> peaksPerRun;
+    for (int i = 0; i < args.workspace->getNumberPeaks(); i++)
+      peaksPerRun[args.workspace->getPeak(i).getRunNumber()].emplace_back(
+          args.workspace->getPeakPtr(i));
     const bool optimizeUB{true};
     for (const auto &runPeaks : peaksPerRun) {
       const auto &peaks = runPeaks.second;
diff --git a/Framework/Crystal/src/IntegratePeakTimeSlices.cpp b/Framework/Crystal/src/IntegratePeakTimeSlices.cpp
index 2c7ff91c84cd479a2f29aa62b0e1c4614f068642..616c3cdfb4e781d057e5586969609082dbd41468 100644
--- a/Framework/Crystal/src/IntegratePeakTimeSlices.cpp
+++ b/Framework/Crystal/src/IntegratePeakTimeSlices.cpp
@@ -224,7 +224,7 @@ void IntegratePeakTimeSlices::exec() {
 
   int indx = getProperty("PeakIndex");
 
-  IPeak &peak = peaksW->getPeak(indx);
+  Peak &peak = peaksW->getPeak(indx);
 
   //------------------------------- Get Panel
   //--------------------------------------
@@ -766,9 +766,8 @@ bool IntegratePeakTimeSlices::updateNeighbors(
  * NOTE: differentials of Q =mv*sin(scatAng/2)/2 were used to calculate this
  *  Also s=r*theta was used to transfer d ScatAng to distance on a bank.
  */
-double
-IntegratePeakTimeSlices::CalculatePositionSpan(Geometry::IPeak const &peak,
-                                               const double dQ) {
+double IntegratePeakTimeSlices::CalculatePositionSpan(Peak const &peak,
+                                                      const double dQ) {
 
   try {
     double Q = 0, ScatAngle = 0, dScatAngle = 0, DetSpan = 0;
@@ -853,7 +852,7 @@ void IntegratePeakTimeSlices::FindPlane(V3D &center, V3D &xvec, V3D &yvec,
                                         double &ROW, double &COL, int &NROWS,
                                         int &NCOLS, double &pixWidthx,
                                         double &pixHeighty,
-                                        Geometry::IPeak const &peak) const {
+                                        DataObjects::Peak const &peak) const {
 
   NROWS = NCOLS = -1;
   IDetector_const_sptr det = peak.getDetector();
diff --git a/Framework/Crystal/src/PeakHKLErrors.cpp b/Framework/Crystal/src/PeakHKLErrors.cpp
index ba417d568ce373e5ae24dd9826640aa77bcc326f..374410e132f7f59b303002d722c577bbb88bbe2a 100644
--- a/Framework/Crystal/src/PeakHKLErrors.cpp
+++ b/Framework/Crystal/src/PeakHKLErrors.cpp
@@ -385,7 +385,7 @@ void PeakHKLErrors::function1D(double *out, const double *xValues,
   double ChiSqTot = 0.0;
   for (size_t i = 0; i < nData; i += 3) {
     int peakNum = boost::math::iround(xValues[i]);
-    IPeak &peak_old = Peaks->getPeak(peakNum);
+    Peak &peak_old = Peaks->getPeak(peakNum);
 
     int runNum = peak_old.getRunNumber();
     std::string runNumStr = std::to_string(runNum);
@@ -484,7 +484,7 @@ void PeakHKLErrors::functionDeriv1D(Jacobian *out, const double *xValues,
 
   for (size_t i = 0; i < nData; i += 3) {
     int peakNum = boost::math::iround(xValues[i]);
-    IPeak &peak_old = Peaks->getPeak(peakNum);
+    Peak &peak_old = Peaks->getPeak(peakNum);
     Peak peak = createNewPeak(peak_old, instNew, 0, peak_old.getL1());
 
     int runNum = peak_old.getRunNumber();
@@ -642,7 +642,7 @@ void PeakHKLErrors::functionDeriv1D(Jacobian *out, const double *xValues,
   }
 }
 
-Peak PeakHKLErrors::createNewPeak(const Geometry::IPeak &peak_old,
+Peak PeakHKLErrors::createNewPeak(const DataObjects::Peak &peak_old,
                                   const Geometry::Instrument_sptr &instrNew,
                                   double T0, double L0) {
   Geometry::Instrument_const_sptr inst = peak_old.getInstrument();
diff --git a/Framework/Crystal/src/PeakIntegration.cpp b/Framework/Crystal/src/PeakIntegration.cpp
index 99e72476604761b4164ab19f0299ab76998e6eac..9c3316ea83a815a115718a79cd5b3b5f7ef2cfbf 100644
--- a/Framework/Crystal/src/PeakIntegration.cpp
+++ b/Framework/Crystal/src/PeakIntegration.cpp
@@ -310,7 +310,7 @@ int PeakIntegration::fitneighbours(int ipeak, const std::string &det_name,
   UNUSED_ARG(det_name);
   UNUSED_ARG(x0);
   UNUSED_ARG(y0);
-  Geometry::IPeak &peak = Peaks->getPeak(ipeak);
+  Peak &peak = Peaks->getPeak(ipeak);
   // Number of slices
   int TOFmax = 0;
 
diff --git a/Framework/Crystal/src/PeakIntensityVsRadius.cpp b/Framework/Crystal/src/PeakIntensityVsRadius.cpp
index 10a7d9b337ccb03f08e1c1d11b9d501b69b79121..65684f4cf5295bfe2ded097bf0ca2bf6749dfa7c 100644
--- a/Framework/Crystal/src/PeakIntensityVsRadius.cpp
+++ b/Framework/Crystal/src/PeakIntensityVsRadius.cpp
@@ -203,7 +203,7 @@ void PeakIntensityVsRadius::exec() {
         outWS2->mutableX(i)[step] = radius;
       }
       // Retrieve the integrated workspace
-      PeaksWorkspace_sptr outPeaks = alg->getProperty("OutputWorkspace");
+      IPeaksWorkspace_sptr outPeaks = alg->getProperty("OutputWorkspace");
       for (int i = 0; i < outPeaks->getNumberPeaks(); i++) {
         auto wi = size_t(i); // workspace index in output
         Geometry::IPeak &p = outPeaks->getPeak(i);
diff --git a/Framework/Crystal/src/PredictFractionalPeaks.cpp b/Framework/Crystal/src/PredictFractionalPeaks.cpp
index 572b40c49c5fdf8d39ac7b07e4eb7431a6048232..4ffc8295a49651f35de7f4cfd1f2f17be9a702d2 100644
--- a/Framework/Crystal/src/PredictFractionalPeaks.cpp
+++ b/Framework/Crystal/src/PredictFractionalPeaks.cpp
@@ -27,12 +27,15 @@
 using Mantid::API::Algorithm;
 using Mantid::API::IPeaksWorkspace_sptr;
 using Mantid::API::Progress;
+using Mantid::DataObjects::Peak;
+using Mantid::DataObjects::Peak_uptr;
 using Mantid::DataObjects::PeaksWorkspace;
 using Mantid::DataObjects::PeaksWorkspace_sptr;
 using Mantid::Geometry::HKLFilter;
 using Mantid::Geometry::HKLFilter_uptr;
 using Mantid::Geometry::HKLGenerator;
 using Mantid::Geometry::Instrument_const_sptr;
+using Mantid::Geometry::IPeak_uptr;
 using Mantid::Geometry::OrientedLattice;
 using Mantid::Geometry::ReflectionCondition_sptr;
 using Mantid::Kernel::DblMatrix;
@@ -246,16 +249,15 @@ IPeaksWorkspace_sptr predictFractionalPeaks(
       if (qLab[2] <= 0)
         continue;
 
-      using Mantid::Geometry::IPeak;
-      std::unique_ptr<IPeak> peak;
+      IPeak_uptr ipeak;
       try {
-        peak = inputPeaks.createPeak(qLab);
+        ipeak = inputPeaks.createPeak(qLab);
       } catch (...) {
         // If we can't create a valid peak we have no choice but to skip
         // it
         continue;
       }
-
+      Peak_uptr peak(static_cast<Peak *>(ipeak.release()));
       peak->setGoniometerMatrix(gonioMatrix);
       if (requirePeaksOnDetector && peak->getDetectorID() < 0)
         continue;
diff --git a/Framework/Crystal/src/SCDCalibratePanels2.cpp b/Framework/Crystal/src/SCDCalibratePanels2.cpp
index 9c05a12fc863de4ab10e47b71af64528c5ed237a..71b39d29fa93b1f9cdbbeb42d0ef850ec0f66f53 100644
--- a/Framework/Crystal/src/SCDCalibratePanels2.cpp
+++ b/Framework/Crystal/src/SCDCalibratePanels2.cpp
@@ -581,7 +581,7 @@ IPeaksWorkspace_sptr SCDCalibratePanels2::removeUnindexedPeaks(
   fltpk_alg->setProperty("OutputWorkspace", "pws_filtered");
   fltpk_alg->executeAsChildAlg();
 
-  PeaksWorkspace_sptr outWS = fltpk_alg->getProperty("OutputWorkspace");
+  IPeaksWorkspace_sptr outWS = fltpk_alg->getProperty("OutputWorkspace");
   IPeaksWorkspace_sptr ows = std::dynamic_pointer_cast<IPeaksWorkspace>(outWS);
   return outWS;
 }
@@ -620,7 +620,7 @@ SCDCalibratePanels2::selectPeaksByBankName(IPeaksWorkspace_sptr pws,
   fltpk_alg->setProperty("OutputWorkspace", outputwsn);
   fltpk_alg->executeAsChildAlg();
 
-  PeaksWorkspace_sptr outWS = fltpk_alg->getProperty("OutputWorkspace");
+  IPeaksWorkspace_sptr outWS = fltpk_alg->getProperty("OutputWorkspace");
   IPeaksWorkspace_sptr ows = std::dynamic_pointer_cast<IPeaksWorkspace>(outWS);
   return outWS;
 }
diff --git a/Framework/Crystal/src/SCDCalibratePanels2ObjFunc.cpp b/Framework/Crystal/src/SCDCalibratePanels2ObjFunc.cpp
index ef9751c424e0fc59151ab2329e5e79b2a18bf55b..64c88ab9e87f734e8988da45e599809c525fcbbc 100644
--- a/Framework/Crystal/src/SCDCalibratePanels2ObjFunc.cpp
+++ b/Framework/Crystal/src/SCDCalibratePanels2ObjFunc.cpp
@@ -134,7 +134,7 @@ void SCDCalibratePanels2ObjFunc::function1D(double *out, const double *xValues,
     // update instrument
     pk.setInstrument(pws->getInstrument());
     // update detector ID
-    pk.setDetectorID(pws->getPeak(i).getDetectorID());
+    pk.setDetectorID(pk.getDetectorID());
     // calculate&set wavelength based on new instrument
     Units::Wavelength wl;
     wl.initialize(pk.getL1(), pk.getL2(), pk.getScattering(), 0,
diff --git a/Framework/Crystal/src/SelectCellOfType.cpp b/Framework/Crystal/src/SelectCellOfType.cpp
index 4d68ad17247c1a9dca08d21164ea62c0af5e1569..2428e10cac8fdced6f021e8d19e71a0ede90f5c7 100644
--- a/Framework/Crystal/src/SelectCellOfType.cpp
+++ b/Framework/Crystal/src/SelectCellOfType.cpp
@@ -5,8 +5,10 @@
 //   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidCrystal/SelectCellOfType.h"
+#include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidAPI/Sample.h"
 #include "MantidCrystal/SelectCellWithForm.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/IndexingUtils.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
@@ -27,7 +29,7 @@ using namespace Mantid::Geometry;
 /** Initialize the algorithm's properties.
  */
 void SelectCellOfType::init() {
-  this->declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  this->declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                             "PeaksWorkspace", "", Direction::InOut),
                         "Input Peaks Workspace");
 
@@ -77,7 +79,7 @@ void SelectCellOfType::init() {
 /** Execute the algorithm.
  */
 void SelectCellOfType::exec() {
-  PeaksWorkspace_sptr ws = this->getProperty("PeaksWorkspace");
+  IPeaksWorkspace_sptr ws = this->getProperty("PeaksWorkspace");
   if (!ws) {
     throw std::runtime_error("Could not read the peaks workspace");
   }
@@ -123,8 +125,7 @@ void SelectCellOfType::exec() {
     o_lattice->setError(sigabc[0], sigabc[1], sigabc[2], sigabc[3], sigabc[4],
                         sigabc[5]);
 
-    std::vector<Peak> &peaks = ws->getPeaks();
-    size_t n_peaks = ws->getNumberPeaks();
+    int n_peaks = ws->getNumberPeaks();
 
     int num_indexed = 0;
     double average_error = 0.0;
@@ -132,22 +133,24 @@ void SelectCellOfType::exec() {
     if (o_lattice->getMaxOrder() == 0) {
       std::vector<V3D> miller_indices;
       std::vector<V3D> q_vectors;
-      for (size_t i = 0; i < n_peaks; i++) {
-        q_vectors.emplace_back(peaks[i].getQSampleFrame());
+      for (int i = 0; i < n_peaks; i++) {
+        q_vectors.emplace_back(ws->getPeak(i).getQSampleFrame());
       }
       num_indexed = IndexingUtils::CalculateMillerIndices(
           newUB, q_vectors, tolerance, miller_indices, average_error);
 
-      for (size_t i = 0; i < n_peaks; i++) {
-        peaks[i].setIntHKL(miller_indices[i]);
-        peaks[i].setHKL(miller_indices[i]);
+      for (int i = 0; i < n_peaks; i++) {
+        IPeak &peak = ws->getPeak(i);
+        peak.setIntHKL(miller_indices[i]);
+        peak.setHKL(miller_indices[i]);
       }
     } else {
       num_indexed = static_cast<int>(num_indexed);
-      for (size_t i = 0; i < n_peaks; i++) {
-        average_error += (peaks[i].getHKL()).hklError();
-        peaks[i].setIntHKL(T * peaks[i].getIntHKL());
-        peaks[i].setHKL(T * peaks[i].getHKL());
+      for (int i = 0; i < n_peaks; i++) {
+        IPeak &peak = ws->getPeak(i);
+        average_error += (peak.getHKL()).hklError();
+        peak.setIntHKL(T * peak.getIntHKL());
+        peak.setHKL(T * peak.getHKL());
       }
     }
     ws->mutableSample().setOrientedLattice(std::move(o_lattice));
diff --git a/Framework/Crystal/src/SelectCellWithForm.cpp b/Framework/Crystal/src/SelectCellWithForm.cpp
index d85fe8c4c3d482ee0c5a4e883cca5a813fa25924..b19ef7046b503f0a2adaf6537c9862b0824b1a5e 100644
--- a/Framework/Crystal/src/SelectCellWithForm.cpp
+++ b/Framework/Crystal/src/SelectCellWithForm.cpp
@@ -6,6 +6,8 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidCrystal/SelectCellWithForm.h"
 #include "MantidAPI/Sample.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
+#include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/IndexingUtils.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
 #include "MantidGeometry/Crystal/ScalarUtils.h"
@@ -25,7 +27,7 @@ using namespace Mantid::Geometry;
 /** Initialize the algorithm's properties.
  */
 void SelectCellWithForm::init() {
-  this->declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  this->declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                             "PeaksWorkspace", "", Direction::InOut),
                         "Input Peaks Workspace");
 
@@ -55,7 +57,7 @@ void SelectCellWithForm::init() {
 
 Kernel::Matrix<double> SelectCellWithForm::DetermineErrors(
     std::vector<double> &sigabc, const Kernel::Matrix<double> &UB,
-    const PeaksWorkspace_sptr &ws, double tolerance) {
+    const IPeaksWorkspace_sptr &ws, double tolerance) {
 
   std::vector<V3D> miller_ind;
   std::vector<V3D> q_vectors;
@@ -107,7 +109,7 @@ Kernel::Matrix<double> SelectCellWithForm::DetermineErrors(
 /** Execute the algorithm.
  */
 void SelectCellWithForm::exec() {
-  PeaksWorkspace_sptr ws = this->getProperty("PeaksWorkspace");
+  IPeaksWorkspace_sptr ws = this->getProperty("PeaksWorkspace");
   if (!ws) {
     throw std::runtime_error("Could not read the peaks workspace");
   }
@@ -156,8 +158,7 @@ void SelectCellWithForm::exec() {
     o_lattice->setError(sigabc[0], sigabc[1], sigabc[2], sigabc[3], sigabc[4],
                         sigabc[5]);
 
-    std::vector<Peak> &peaks = ws->getPeaks();
-    size_t n_peaks = ws->getNumberPeaks();
+    int n_peaks = ws->getNumberPeaks();
 
     int num_indexed = 0;
     double average_error = 0.0;
@@ -165,22 +166,24 @@ void SelectCellWithForm::exec() {
     if (o_lattice->getMaxOrder() == 0) {
       std::vector<V3D> miller_indices;
       std::vector<V3D> q_vectors;
-      for (size_t i = 0; i < n_peaks; i++) {
-        q_vectors.emplace_back(peaks[i].getQSampleFrame());
+      for (int i = 0; i < n_peaks; i++) {
+        q_vectors.emplace_back(ws->getPeak(i).getQSampleFrame());
       }
       num_indexed = IndexingUtils::CalculateMillerIndices(
           newUB, q_vectors, tolerance, miller_indices, average_error);
 
-      for (size_t i = 0; i < n_peaks; i++) {
-        peaks[i].setIntHKL(miller_indices[i]);
-        peaks[i].setHKL(miller_indices[i]);
+      for (int i = 0; i < n_peaks; i++) {
+        IPeak &peak = ws->getPeak(i);
+        peak.setIntHKL(miller_indices[i]);
+        peak.setHKL(miller_indices[i]);
       }
     } else {
       num_indexed = static_cast<int>(num_indexed);
-      for (size_t i = 0; i < n_peaks; i++) {
-        average_error += (peaks[i].getHKL()).hklError();
-        peaks[i].setIntHKL(T * peaks[i].getIntHKL());
-        peaks[i].setHKL(T * peaks[i].getHKL());
+      for (int i = 0; i < n_peaks; i++) {
+        IPeak &peak = ws->getPeak(i);
+        average_error += (peak.getHKL()).hklError();
+        peak.setIntHKL(T * peak.getIntHKL());
+        peak.setHKL(T * peak.getHKL());
       }
     }
     ws->mutableSample().setOrientedLattice(std::move(o_lattice));
diff --git a/Framework/Crystal/src/ShowPossibleCells.cpp b/Framework/Crystal/src/ShowPossibleCells.cpp
index a97e1780f32601e17f449d4cdc561e7ffe319b8f..86ebf2fe9bfb4aa2a59fac05c0a41046e4b2f6f7 100644
--- a/Framework/Crystal/src/ShowPossibleCells.cpp
+++ b/Framework/Crystal/src/ShowPossibleCells.cpp
@@ -5,7 +5,9 @@
 //   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidCrystal/ShowPossibleCells.h"
+#include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidAPI/Sample.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/IndexingUtils.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
@@ -25,7 +27,7 @@ using namespace Mantid::Geometry;
 /** Initialize the algorithm's properties.
  */
 void ShowPossibleCells::init() {
-  this->declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  this->declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                             "PeaksWorkspace", "", Direction::InOut),
                         "Input Peaks Workspace");
 
@@ -51,7 +53,7 @@ void ShowPossibleCells::init() {
 /** Execute the algorithm.
  */
 void ShowPossibleCells::exec() {
-  PeaksWorkspace_const_sptr ws = this->getProperty("PeaksWorkspace");
+  IPeaksWorkspace_const_sptr ws = this->getProperty("PeaksWorkspace");
   if (!ws) {
     throw std::runtime_error("Could not read the peaks workspace");
   }
diff --git a/Framework/Crystal/src/TransformHKL.cpp b/Framework/Crystal/src/TransformHKL.cpp
index 063da0be86d7d44cbed788e8444746c187b24d2d..7d980c8ceeb8c4bd2897de47ceb5475ab8099c49 100644
--- a/Framework/Crystal/src/TransformHKL.cpp
+++ b/Framework/Crystal/src/TransformHKL.cpp
@@ -5,8 +5,10 @@
 //   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidCrystal/TransformHKL.h"
+#include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidAPI/Sample.h"
 #include "MantidCrystal/SelectCellWithForm.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/IndexingUtils.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
@@ -33,7 +35,7 @@ const std::string TransformHKL::category() const { return "Crystal\\Peaks"; }
 /** Initialize the algorithm's properties.
  */
 void TransformHKL::init() {
-  this->declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  this->declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                             "PeaksWorkspace", "", Direction::InOut),
                         "Input Peaks Workspace");
 
@@ -70,7 +72,7 @@ void TransformHKL::init() {
 /** Execute the algorithm.
  */
 void TransformHKL::exec() {
-  PeaksWorkspace_sptr ws = this->getProperty("PeaksWorkspace");
+  IPeaksWorkspace_sptr ws = this->getProperty("PeaksWorkspace");
   if (!ws) {
     throw std::runtime_error("Could not read the peaks workspace");
   }
@@ -126,21 +128,21 @@ void TransformHKL::exec() {
   ws->mutableSample().setOrientedLattice(
       std::make_unique<OrientedLattice>(o_lattice));
 
-  std::vector<Peak> &peaks = ws->getPeaks();
-  size_t n_peaks = ws->getNumberPeaks();
+  int n_peaks = ws->getNumberPeaks();
 
   // transform all the HKLs and record the new HKL
   // and q-vectors for peaks ORIGINALLY indexed
   int num_indexed = 0;
   std::vector<V3D> miller_indices;
   std::vector<V3D> q_vectors;
-  for (size_t i = 0; i < n_peaks; i++) {
-    V3D hkl(peaks[i].getHKL());
-    V3D ihkl(peaks[i].getIntHKL());
-    peaks[i].setIntHKL(hkl_tran * ihkl);
+  for (int i = 0; i < n_peaks; i++) {
+    IPeak &peak = ws->getPeak(i);
+    V3D hkl(peak.getHKL());
+    V3D ihkl(peak.getIntHKL());
+    peak.setIntHKL(hkl_tran * ihkl);
     miller_indices.emplace_back(hkl_tran * ihkl);
-    peaks[i].setHKL(hkl_tran * hkl);
-    q_vectors.emplace_back(peaks[i].getQSampleFrame());
+    peak.setHKL(hkl_tran * hkl);
+    q_vectors.emplace_back(peak.getQSampleFrame());
     num_indexed++;
   }
 
diff --git a/Framework/Crystal/test/CalculatePeaksHKLTest.h b/Framework/Crystal/test/CalculatePeaksHKLTest.h
index e45bc5e576b474a9c80f13408ee80aab520398d9..70a09553bcaa9192b9e3b66d33ab35093a376ab8 100644
--- a/Framework/Crystal/test/CalculatePeaksHKLTest.h
+++ b/Framework/Crystal/test/CalculatePeaksHKLTest.h
@@ -8,6 +8,7 @@
 
 #include "MantidAPI/Sample.h"
 #include "MantidCrystal/CalculatePeaksHKL.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
 #include "MantidTestHelpers/WorkspaceCreationHelper.h"
@@ -78,6 +79,33 @@ public:
     }
   }
 
+  void test_Execute_LeanElasticPeaks() {
+    auto lattice = std::make_unique<Mantid::Geometry::OrientedLattice>();
+
+    auto ws = std::make_shared<LeanElasticPeaksWorkspace>();
+    ws->mutableSample().setOrientedLattice(std::move(lattice));
+    ws->addPeak(LeanElasticPeak(Mantid::Kernel::V3D(2, 0, 0), 1.));
+    ws->addPeak(LeanElasticPeak(Mantid::Kernel::V3D(0, 4, 0), 1.));
+
+    Mantid::API::AnalysisDataService::Instance().addOrReplace("ws", ws);
+
+    CalculatePeaksHKL alg;
+    alg.setRethrows(true);
+    alg.initialize();
+    alg.setPropertyValue("PeaksWorkspace", "ws");
+    alg.execute();
+    int numberIndexed = alg.getProperty("NumIndexed");
+    TS_ASSERT_EQUALS(numberIndexed, ws->getNumberPeaks());
+
+    TS_ASSERT_DELTA(ws->getPeak(0).getH(), M_1_PI, 1e-9)
+    TS_ASSERT_DELTA(ws->getPeak(0).getK(), 0, 1e-9)
+    TS_ASSERT_DELTA(ws->getPeak(0).getL(), 0, 1e-9)
+
+    TS_ASSERT_DELTA(ws->getPeak(1).getH(), 0, 1e-9)
+    TS_ASSERT_DELTA(ws->getPeak(1).getK(), M_2_PI, 1e-9)
+    TS_ASSERT_DELTA(ws->getPeak(1).getL(), 0, 1e-9)
+  }
+
   // Don't index peaks that are already indexed.
   void test_SkipIndexing() {
     auto lattice = std::make_unique<Mantid::Geometry::OrientedLattice>();
diff --git a/Framework/Crystal/test/CombinePeaksWorkspacesTest.h b/Framework/Crystal/test/CombinePeaksWorkspacesTest.h
index 7b72b70a8ebeefbaa1027e936b27514e05a19259..0f5343f8d61cf86012dbd6a8ad10ee42fc7e1f8d 100644
--- a/Framework/Crystal/test/CombinePeaksWorkspacesTest.h
+++ b/Framework/Crystal/test/CombinePeaksWorkspacesTest.h
@@ -11,6 +11,7 @@
 #include "MantidAPI/Sample.h"
 #include "MantidCrystal/CombinePeaksWorkspaces.h"
 #include "MantidCrystal/PredictFractionalPeaks.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
 #include "MantidTestHelpers/WorkspaceCreationHelper.h"
@@ -348,4 +349,89 @@ public:
     TS_ASSERT_EQUALS(outWs->sample().getOrientedLattice().getModVec(2)[1], 0);
     TS_ASSERT_EQUALS(outWs->sample().getOrientedLattice().getModVec(2)[2], 0);
   }
+
+  void test_LeanElasticPeak() {
+    using namespace Mantid::API;
+    using namespace Mantid::DataObjects;
+
+    PeaksWorkspace_sptr ws1 = WorkspaceCreationHelper::createPeaksWorkspace(3);
+
+    auto ws2 = std::make_shared<LeanElasticPeaksWorkspace>();
+    ws2->addPeak(LeanElasticPeak(Mantid::Kernel::V3D(1, 0, 0), 1.));
+    ws2->addPeak(LeanElasticPeak(Mantid::Kernel::V3D(0, 4, 0), 1.));
+
+    auto ws3 = std::make_shared<LeanElasticPeaksWorkspace>();
+    ws3->addPeak(LeanElasticPeak(Mantid::Kernel::V3D(2, 0, 0), 1.));
+    ws3->addPeak(LeanElasticPeak(Mantid::Kernel::V3D(0, 4, 0), 1.));
+
+    // Name of the output workspace.
+    std::string outWSName("CombinePeaksWorkspacesTest_OutputWS");
+
+    // LeanElasticPeak + LeanElasticPeak - no combine
+    CombinePeaksWorkspaces alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("LHSWorkspace", ws2))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("RHSWorkspace", ws3))
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("OutputWorkspace", outWSName))
+    TS_ASSERT(alg.execute())
+
+    // Retrieve the workspace from data service.
+    LeanElasticPeaksWorkspace_const_sptr ws;
+    TS_ASSERT_THROWS_NOTHING(
+        ws = AnalysisDataService::Instance()
+                 .retrieveWS<LeanElasticPeaksWorkspace>(outWSName));
+    TS_ASSERT(ws);
+    if (!ws)
+      return;
+
+    TS_ASSERT_EQUALS(ws->getNumberPeaks(), 4)
+    TS_ASSERT_EQUALS(ws->getPeak(1).getQSampleFrame(),
+                     ws->getPeak(3).getQSampleFrame())
+
+    // LeanElasticPeak + LeanElasticPeak - combine
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("LHSWorkspace", ws2))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("RHSWorkspace", ws3))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("CombineMatchingPeaks", true))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Tolerance", 0.00001))
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("OutputWorkspace", outWSName))
+    TS_ASSERT(alg.execute())
+
+    // Retrieve the workspace from data service.
+    TS_ASSERT_THROWS_NOTHING(
+        ws = AnalysisDataService::Instance()
+                 .retrieveWS<LeanElasticPeaksWorkspace>(outWSName));
+    TS_ASSERT(ws);
+    if (!ws)
+      return;
+
+    TS_ASSERT_EQUALS(ws->getNumberPeaks(), 3)
+
+    // LeanElasticPeak + Peak
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("LHSWorkspace", ws2))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("RHSWorkspace", ws1))
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("OutputWorkspace", outWSName))
+    TS_ASSERT(alg.execute())
+
+    // Retrieve the workspace from data service.
+    TS_ASSERT_THROWS_NOTHING(
+        ws = AnalysisDataService::Instance()
+                 .retrieveWS<LeanElasticPeaksWorkspace>(outWSName));
+    TS_ASSERT(ws);
+    if (!ws)
+      return;
+
+    TS_ASSERT_EQUALS(ws->getNumberPeaks(), 5)
+
+    // Peak + LeanElasticPeak - SHOULD FAIL TO EXECUTE
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("LHSWorkspace", ws1))
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("RHSWorkspace", ws2))
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("OutputWorkspace", outWSName))
+    TS_ASSERT(!alg.execute())
+
+    // Remove workspace from the data service.
+    AnalysisDataService::Instance().remove(outWSName);
+  }
 };
diff --git a/Framework/Crystal/test/FilterPeaksTest.h b/Framework/Crystal/test/FilterPeaksTest.h
index de45086b35112b915292726ac84c9f7312876100..2c599ca60a0d6fb64a6019c42a089ba7c8984988 100644
--- a/Framework/Crystal/test/FilterPeaksTest.h
+++ b/Framework/Crystal/test/FilterPeaksTest.h
@@ -10,6 +10,7 @@
 
 #include "MantidAPI/AlgorithmManager.h"
 #include "MantidCrystal/FilterPeaks.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidTestHelpers/WorkspaceCreationHelper.h"
 
@@ -41,7 +42,7 @@ private:
    * Helper method to run the algorithm and return the output workspace.
    * -- filter value
    */
-  IPeaksWorkspace_sptr runAlgorithm(const PeaksWorkspace_sptr &inWS,
+  IPeaksWorkspace_sptr runAlgorithm(const IPeaksWorkspace_sptr &inWS,
                                     const std::string &filterVariable,
                                     const double filterValue,
                                     const std::string &filterOperator) {
@@ -362,6 +363,50 @@ public:
     outWS = runAlgorithm(inWS, "bank1", "!=");
     TS_ASSERT_EQUALS(0, outWS->getNumberPeaks());
   }
+
+  void test_filter_LeanElasticPeaksWorkspace() {
+    auto inWS = std::make_shared<LeanElasticPeaksWorkspace>();
+
+    LeanElasticPeak peak(Mantid::Kernel::V3D(1, 1, 0), 1.0);
+    peak.setIntensity(100.0);
+    peak.setHKL(1, 1, 0);
+    TS_ASSERT_DELTA(peak.getDSpacing(), M_PI * M_SQRT2, 1e-9)
+    inWS->addPeak(peak);
+
+    LeanElasticPeak peak2(Mantid::Kernel::V3D(1, 0, 0), 2.0);
+    peak2.setIntensity(10.0);
+    peak2.setHKL(1, 0, 0);
+    TS_ASSERT_DELTA(peak2.getDSpacing(), 2 * M_PI, 1e-9)
+    inWS->addPeak(peak2);
+
+    auto outWS = runAlgorithm(inWS, "Wavelength", 1.0, "<");
+    TS_ASSERT_EQUALS(0, outWS->getNumberPeaks());
+    outWS = runAlgorithm(inWS, "Wavelength", 1.0, ">");
+    TS_ASSERT_EQUALS(1, outWS->getNumberPeaks());
+
+    outWS = runAlgorithm(inWS, "DSpacing", 5, "<");
+    TS_ASSERT_EQUALS(1, outWS->getNumberPeaks());
+    outWS = runAlgorithm(inWS, "DSpacing", 0, ">");
+    TS_ASSERT_EQUALS(2, outWS->getNumberPeaks());
+
+    outWS = runAlgorithm(inWS, "h+k+l", 2, "=");
+    TS_ASSERT_EQUALS(1, outWS->getNumberPeaks());
+    outWS = runAlgorithm(inWS, "h+k+l", 3, "<");
+    TS_ASSERT_EQUALS(2, outWS->getNumberPeaks());
+
+    outWS = runAlgorithm(inWS, "h^2+k^2+l^2", 2, "=");
+    TS_ASSERT_EQUALS(1, outWS->getNumberPeaks());
+    outWS = runAlgorithm(inWS, "h^2+k^2+l^2", 2, ">");
+    TS_ASSERT_EQUALS(0, outWS->getNumberPeaks());
+
+    outWS = runAlgorithm(inWS, "Intensity", 1000, "<");
+    TS_ASSERT_EQUALS(2, outWS->getNumberPeaks());
+    outWS = runAlgorithm(inWS, "Intensity", 20, ">");
+    TS_ASSERT_EQUALS(1, outWS->getNumberPeaks());
+
+    AnalysisDataService::Instance().remove(outWS->getName());
+    AnalysisDataService::Instance().remove(inWS->getName());
+  }
 };
 
 //-------------------------------------------------------------
diff --git a/Framework/Crystal/test/FindUBUsingFFTTest.h b/Framework/Crystal/test/FindUBUsingFFTTest.h
index 5680974941fb1322cd6ad1b31730a482c13b0a8f..266a0984bcb861df18da005c7c4190aa1244b88e 100644
--- a/Framework/Crystal/test/FindUBUsingFFTTest.h
+++ b/Framework/Crystal/test/FindUBUsingFFTTest.h
@@ -15,6 +15,7 @@
 #include "MantidCrystal/FindUBUsingFFT.h"
 #include "MantidCrystal/LoadIsawUB.h"
 #include "MantidDataHandling/LoadNexusProcessed.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
 
@@ -81,4 +82,59 @@ public:
     // Remove workspace from the data service.
     AnalysisDataService::Instance().remove(WSName);
   }
+
+  void test_exec_LeanElasticPeak() {
+    // Name of the output workspace.
+    std::string WSName("peaks");
+    LoadNexusProcessed loader;
+    TS_ASSERT_THROWS_NOTHING(loader.initialize());
+    TS_ASSERT(loader.isInitialized());
+    loader.setPropertyValue("Filename", "TOPAZ_3007.peaks.nxs");
+    loader.setPropertyValue("OutputWorkspace", WSName);
+
+    TS_ASSERT(loader.execute());
+    TS_ASSERT(loader.isExecuted());
+
+    PeaksWorkspace_sptr ws;
+    TS_ASSERT_THROWS_NOTHING(
+        ws = std::dynamic_pointer_cast<PeaksWorkspace>(
+            AnalysisDataService::Instance().retrieve(WSName)));
+    TS_ASSERT(ws);
+    if (!ws)
+      return;
+
+    // Convert PeaksWorkspace to LeanElasticPeaksWorkspace
+    auto lpw = std::make_shared<LeanElasticPeaksWorkspace>();
+    for (auto peak : ws->getPeaks())
+      lpw->addPeak(peak);
+    AnalysisDataService::Instance().addOrReplace(WSName, lpw);
+
+    FindUBUsingFFT alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("PeaksWorkspace", WSName));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("MinD", "8.0"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("MaxD", "13.0"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Tolerance", "0.15"));
+    TS_ASSERT_THROWS_NOTHING(alg.execute(););
+    TS_ASSERT(alg.isExecuted());
+
+    // Check that we set an oriented lattice
+    TS_ASSERT(lpw->mutableSample().hasOrientedLattice());
+    // Check that the UB matrix is the same as in TOPAZ_3007.mat
+    OrientedLattice latt = lpw->mutableSample().getOrientedLattice();
+
+    double correct_UB[] = {0.0122354,  0.00480056,  0.0860404,
+                           -0.1165450, 0.00178145,  -0.0045884,
+                           -0.0273738, -0.08973560, -0.0252595};
+
+    std::vector<double> UB_calculated = latt.getUB().getVector();
+
+    for (size_t i = 0; i < 9; i++) {
+      TS_ASSERT_DELTA(correct_UB[i], UB_calculated[i], 5e-4);
+    }
+
+    // Remove workspace from the data service.
+    AnalysisDataService::Instance().remove(WSName);
+  }
 };
diff --git a/Framework/Crystal/test/FindUBUsingIndexedPeaksTest.h b/Framework/Crystal/test/FindUBUsingIndexedPeaksTest.h
index f8e0d32cc5e26eb20eb24e658bfcb2164d3d4f27..0e055cd2a4d0f4fd1574ab9d13b4ebc6fb6e1beb 100644
--- a/Framework/Crystal/test/FindUBUsingIndexedPeaksTest.h
+++ b/Framework/Crystal/test/FindUBUsingIndexedPeaksTest.h
@@ -16,6 +16,7 @@
 #include "MantidCrystal/LoadIsawPeaks.h"
 #include "MantidCrystal/LoadIsawUB.h"
 #include "MantidDataHandling/LoadNexusProcessed.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
 
@@ -78,6 +79,59 @@ public:
     // Remove workspace from the data service.
     AnalysisDataService::Instance().remove(WSName);
   }
+
+  void test_exec_LeanElasticPeak() {
+    // Name of the output workspace.
+    std::string WSName("peaks");
+    LoadNexusProcessed loader;
+    TS_ASSERT_THROWS_NOTHING(loader.initialize());
+    TS_ASSERT(loader.isInitialized());
+    loader.setPropertyValue("Filename", "TOPAZ_3007.peaks.nxs");
+    loader.setPropertyValue("OutputWorkspace", WSName);
+
+    TS_ASSERT(loader.execute());
+    TS_ASSERT(loader.isExecuted());
+
+    PeaksWorkspace_sptr ws;
+    TS_ASSERT_THROWS_NOTHING(
+        ws = std::dynamic_pointer_cast<PeaksWorkspace>(
+            AnalysisDataService::Instance().retrieve(WSName)));
+    TS_ASSERT(ws);
+    if (!ws)
+      return;
+
+    // Convert PeaksWorkspace to LeanElasticPeaksWorkspace
+    auto lpw = std::make_shared<LeanElasticPeaksWorkspace>();
+    for (auto peak : ws->getPeaks())
+      lpw->addPeak(peak);
+    AnalysisDataService::Instance().addOrReplace(WSName, lpw);
+
+    FindUBUsingIndexedPeaks alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("PeaksWorkspace", WSName));
+    TS_ASSERT_THROWS_NOTHING(alg.execute(););
+    TS_ASSERT(alg.isExecuted());
+
+    // Check that we set an oriented lattice
+    TS_ASSERT(lpw->mutableSample().hasOrientedLattice());
+    // Check that the UB matrix is the same as in TOPAZ_3007.mat
+    OrientedLattice latt = lpw->mutableSample().getOrientedLattice();
+
+    double correct_UB[] = {-0.04542050, 0.04061990,  -0.0122354,
+                           0.00140347,  -0.00318493, 0.116545,
+                           0.05749760,  0.03223800,  0.02737380};
+
+    std::vector<double> UB_calculated = latt.getUB().getVector();
+
+    for (size_t i = 0; i < 9; i++) {
+      TS_ASSERT_DELTA(correct_UB[i], UB_calculated[i], 5e-4);
+    }
+
+    // Remove workspace from the data service.
+    AnalysisDataService::Instance().remove(WSName);
+  }
+
   void test_mod() {
     LoadIsawPeaks alg1;
     TS_ASSERT_THROWS_NOTHING(alg1.initialize())
diff --git a/Framework/Crystal/test/FindUBUsingLatticeParametersTest.h b/Framework/Crystal/test/FindUBUsingLatticeParametersTest.h
index ba082aae2c5377793dd92da42f00e32327167998..ed464937d828ee30652473bcab25b19fc18c71b8 100644
--- a/Framework/Crystal/test/FindUBUsingLatticeParametersTest.h
+++ b/Framework/Crystal/test/FindUBUsingLatticeParametersTest.h
@@ -16,6 +16,7 @@
 #include "MantidCrystal/FindUBUsingLatticeParameters.h"
 #include "MantidCrystal/LoadIsawUB.h"
 #include "MantidDataHandling/LoadNexusProcessed.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
 
@@ -99,6 +100,63 @@ public:
     TS_ASSERT_DELTA(latt.errorgamma(), 0.0906, 5e-4);
   }
 
+  void test_exec_LeanElasticPeak() {
+
+    // Convert PeaksWorkspace to LeanElasticPeaksWorkspace
+    auto lpw = std::make_shared<LeanElasticPeaksWorkspace>();
+    for (auto peak : m_ws->getPeaks())
+      lpw->addPeak(peak);
+    AnalysisDataService::Instance().addOrReplace("leanpeaksWS", lpw);
+
+    FindUBUsingLatticeParameters alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("PeaksWorkspace", lpw->getName()));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("a", "14.131"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("b", "19.247"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("c", "8.606"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("alpha", "90.0"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("beta", "105.071"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("gamma", "90.0"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("NumInitial", "15"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Tolerance", "0.12"));
+    TS_ASSERT_THROWS_NOTHING(alg.execute(););
+    TS_ASSERT(alg.isExecuted());
+
+    // Check that we set an oriented lattice
+    TS_ASSERT(lpw->mutableSample().hasOrientedLattice());
+    // Check that the UB matrix is the same as in TOPAZ_3007.mat
+    OrientedLattice latt = lpw->mutableSample().getOrientedLattice();
+
+    double correct_UB[] = {0.04542050,  0.040619900, 0.0122354,
+                           -0.00140347, -0.00318493, -0.1165450,
+                           -0.05749760, 0.03223800,  -0.0273738};
+
+    std::vector<double> UB_calculated = latt.getUB().getVector();
+
+    for (size_t i = 0; i < 9; i++) {
+      TS_ASSERT_DELTA(correct_UB[i], UB_calculated[i], 5e-4);
+    }
+
+    TS_ASSERT_DELTA(latt.a(), 14.131, 5e-4);
+    TS_ASSERT_DELTA(latt.b(), 19.247, 5e-4);
+    TS_ASSERT_DELTA(latt.c(), 8.606, 5e-4);
+
+    TS_ASSERT_DELTA(latt.alpha(), 90.0, 5e-1);
+    TS_ASSERT_DELTA(latt.beta(), 105.071, 5e-1);
+    TS_ASSERT_DELTA(latt.gamma(), 90.0, 5e-1);
+
+    // Check errors
+    TS_ASSERT_DELTA(latt.errora(), 0.0134, 5e-4);
+    TS_ASSERT_DELTA(latt.errorb(), 0.0243, 5e-4);
+    TS_ASSERT_DELTA(latt.errorc(), 0.0101, 5e-4);
+
+    TS_ASSERT_DELTA(latt.erroralpha(), 0.0994, 5e-4);
+    TS_ASSERT_DELTA(latt.errorbeta(), 0.0773, 5e-4);
+    TS_ASSERT_DELTA(latt.errorgamma(), 0.0906, 5e-4);
+  }
+
   void test_fixAll() {
     FindUBUsingLatticeParameters alg;
     TS_ASSERT_THROWS_NOTHING(alg.initialize())
diff --git a/Framework/Crystal/test/FindUBUsingMinMaxDTest.h b/Framework/Crystal/test/FindUBUsingMinMaxDTest.h
index 48660ddfece070cebcfe2fda42093b0ed22dcb7c..825b3da65e2a95eec41c0ea2a630ef843578703b 100644
--- a/Framework/Crystal/test/FindUBUsingMinMaxDTest.h
+++ b/Framework/Crystal/test/FindUBUsingMinMaxDTest.h
@@ -15,6 +15,7 @@
 #include "MantidCrystal/FindUBUsingMinMaxD.h"
 #include "MantidCrystal/LoadIsawUB.h"
 #include "MantidDataHandling/LoadNexusProcessed.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
 
@@ -80,4 +81,60 @@ public:
     // Remove workspace from the data service.
     AnalysisDataService::Instance().remove(WSName);
   }
+
+  void test_exec_LeanElasticPeak() {
+    // Name of the output workspace.
+    std::string WSName("peaks");
+    Mantid::DataHandling::LoadNexusProcessed loader;
+    TS_ASSERT_THROWS_NOTHING(loader.initialize());
+    TS_ASSERT(loader.isInitialized());
+    loader.setPropertyValue("Filename", "TOPAZ_3007.peaks.nxs");
+    loader.setPropertyValue("OutputWorkspace", WSName);
+
+    TS_ASSERT(loader.execute());
+    TS_ASSERT(loader.isExecuted());
+
+    PeaksWorkspace_sptr ws;
+    TS_ASSERT_THROWS_NOTHING(
+        ws = std::dynamic_pointer_cast<PeaksWorkspace>(
+            AnalysisDataService::Instance().retrieve(WSName)));
+    TS_ASSERT(ws);
+    if (!ws)
+      return;
+
+    // Convert PeaksWorkspace to LeanElasticPeaksWorkspace
+    auto lpw = std::make_shared<LeanElasticPeaksWorkspace>();
+    for (auto peak : ws->getPeaks())
+      lpw->addPeak(peak);
+    AnalysisDataService::Instance().addOrReplace(WSName, lpw);
+
+    FindUBUsingMinMaxD alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("PeaksWorkspace", WSName));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("MinD", "8.0"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("MaxD", "13.0"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("NumInitial", "20"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Tolerance", "0.15"));
+    TS_ASSERT_THROWS_NOTHING(alg.execute(););
+    TS_ASSERT(alg.isExecuted());
+
+    // Check that we set an oriented lattice
+    TS_ASSERT(lpw->mutableSample().hasOrientedLattice());
+    // Check that the UB matrix is the same as in TOPAZ_3007.mat
+    OrientedLattice latt = lpw->mutableSample().getOrientedLattice();
+
+    double correct_UB[] = {0.0122354,  0.00480056,  0.0860404,
+                           -0.1165450, 0.00178145,  -0.0045884,
+                           -0.0273738, -0.08973560, -0.0252595};
+
+    std::vector<double> UB_calculated = latt.getUB().getVector();
+
+    for (size_t i = 0; i < 9; i++) {
+      TS_ASSERT_DELTA(correct_UB[i], UB_calculated[i], 5e-4);
+    }
+
+    // Remove workspace from the data service.
+    AnalysisDataService::Instance().remove(WSName);
+  }
 };
diff --git a/Framework/Crystal/test/IndexPeaksTest.h b/Framework/Crystal/test/IndexPeaksTest.h
index 0f8e32ed614c5ea4ca056852344c4ab54c26106f..2d2b35efa3bedd454c162f5faa89eb500c9ac9cc 100644
--- a/Framework/Crystal/test/IndexPeaksTest.h
+++ b/Framework/Crystal/test/IndexPeaksTest.h
@@ -6,8 +6,10 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #pragma once
 
+#include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidAPI/Sample.h"
 #include "MantidCrystal/IndexPeaks.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/IndexingUtils.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
@@ -16,6 +18,7 @@
 
 #include <cxxtest/TestSuite.h>
 
+using Mantid::API::IPeaksWorkspace_sptr;
 using Mantid::API::Workspace_sptr;
 using Mantid::Crystal::IndexPeaks;
 using Mantid::DataObjects::PeaksWorkspace_sptr;
@@ -112,7 +115,7 @@ PeaksWorkspace_sptr createTestPeaksWorkspaceWithSatellites(
 }
 
 std::unique_ptr<IndexPeaks>
-indexPeaks(const PeaksWorkspace_sptr &peaksWS,
+indexPeaks(const IPeaksWorkspace_sptr &peaksWS,
            const std::unordered_map<std::string, std::string> &arguments) {
   auto alg = std::make_unique<IndexPeaks>();
   alg->setChild(true);
@@ -654,6 +657,30 @@ public:
     }
   }
 
+  void test_LeanElasticPeak() {
+    auto lattice = std::make_unique<Mantid::Geometry::OrientedLattice>();
+    auto ws =
+        std::make_shared<Mantid::DataObjects::LeanElasticPeaksWorkspace>();
+    ws->mutableSample().setOrientedLattice(std::move(lattice));
+    ws->addPeak(Mantid::DataObjects::LeanElasticPeak(
+        Mantid::Kernel::V3D(2 * M_PI, 0, 0), 1.));
+    ws->addPeak(Mantid::DataObjects::LeanElasticPeak(
+        Mantid::Kernel::V3D(0, 4 * M_PI, 0), 1.));
+
+    auto alg = indexPeaks(ws, {});
+    TS_ASSERT(alg->isExecuted())
+    int numberIndexed = alg->getProperty("NumIndexed");
+    TS_ASSERT_EQUALS(numberIndexed, 2)
+
+    TS_ASSERT_DELTA(ws->getPeak(0).getH(), 1, 1e-9)
+    TS_ASSERT_DELTA(ws->getPeak(0).getK(), 0, 1e-9)
+    TS_ASSERT_DELTA(ws->getPeak(0).getL(), 0, 1e-9)
+
+    TS_ASSERT_DELTA(ws->getPeak(1).getH(), 0, 1e-9)
+    TS_ASSERT_DELTA(ws->getPeak(1).getK(), 2, 1e-9)
+    TS_ASSERT_DELTA(ws->getPeak(1).getL(), 0, 1e-9)
+  }
+
 private:
   void assertThreeModVectorWithCross(
       const IndexPeaks &alg, const PeaksWorkspace_sptr::element_type &peaksWS) {
diff --git a/Framework/Crystal/test/SelectCellOfTypeTest.h b/Framework/Crystal/test/SelectCellOfTypeTest.h
index a0caa4e89dad641e73e2e4303cc1c73e80ae2be4..b4c78ce90d398e88f78f50897ea425efea8bd524 100644
--- a/Framework/Crystal/test/SelectCellOfTypeTest.h
+++ b/Framework/Crystal/test/SelectCellOfTypeTest.h
@@ -15,6 +15,7 @@
 #include "MantidCrystal/LoadIsawUB.h"
 #include "MantidCrystal/SelectCellOfType.h"
 #include "MantidDataHandling/LoadNexusProcessed.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/IndexingUtils.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
@@ -86,4 +87,64 @@ public:
 
     AnalysisDataService::Instance().remove(WSName);
   }
+
+  void test_exec_LeanElasticPeak() {
+    // Name of the loader's output workspace.
+    std::string WSName("peaks");
+    LoadNexusProcessed loader;
+    TS_ASSERT_THROWS_NOTHING(loader.initialize());
+    TS_ASSERT(loader.isInitialized());
+    loader.setPropertyValue("Filename", "TOPAZ_3007.peaks.nxs");
+    loader.setPropertyValue("OutputWorkspace", WSName);
+
+    TS_ASSERT(loader.execute());
+    TS_ASSERT(loader.isExecuted());
+    PeaksWorkspace_sptr ws;
+    TS_ASSERT_THROWS_NOTHING(
+        ws = std::dynamic_pointer_cast<PeaksWorkspace>(
+            AnalysisDataService::Instance().retrieve(WSName)));
+    TS_ASSERT(ws);
+
+    // Convert PeaksWorkspace to LeanElasticPeaksWorkspace
+    auto lpw = std::make_shared<LeanElasticPeaksWorkspace>();
+    for (auto peak : ws->getPeaks())
+      lpw->addPeak(peak);
+    AnalysisDataService::Instance().addOrReplace(WSName, lpw);
+
+    // set a Niggli UB for run 3007
+    // (CuTCA) in the oriented lattice
+    V3D row_0(0.0122354, 0.00480056, 0.0860404);
+    V3D row_1(-0.1165450, 0.00178145, -0.0045884);
+    V3D row_2(-0.0273738, -0.08973560, -0.0252595);
+
+    Matrix<double> UB(3, 3, false);
+    UB.setRow(0, row_0);
+    UB.setRow(1, row_1);
+    UB.setRow(2, row_2);
+
+    auto lattice = std::make_unique<OrientedLattice>();
+    lattice->setUB(UB);
+    lpw->mutableSample().setOrientedLattice(std::move(lattice));
+
+    // now get the UB back from the WS
+    UB = lpw->sample().getOrientedLattice().getUB();
+
+    SelectCellOfType alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("PeaksWorkspace", WSName));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("CellType", "Monoclinic"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Centering", "P"));
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Apply", true));
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Tolerance", 0.12));
+    TS_ASSERT_THROWS_NOTHING(alg.execute(););
+    TS_ASSERT(alg.isExecuted());
+
+    int num_indexed = alg.getProperty("NumIndexed");
+    TS_ASSERT_EQUALS(num_indexed, 43);
+    double average_error = alg.getProperty("AverageError");
+    TS_ASSERT_DELTA(average_error, 0.00972862, .0001);
+
+    AnalysisDataService::Instance().remove(WSName);
+  }
 };
diff --git a/Framework/Crystal/test/SelectCellWithFormTest.h b/Framework/Crystal/test/SelectCellWithFormTest.h
index b97ef3818e6978859fedd178028a3af9d6cbae09..64628bd704635c6bdd367d9f2edd3bbbe4f46af1 100644
--- a/Framework/Crystal/test/SelectCellWithFormTest.h
+++ b/Framework/Crystal/test/SelectCellWithFormTest.h
@@ -15,6 +15,7 @@
 #include "MantidCrystal/LoadIsawUB.h"
 #include "MantidCrystal/SelectCellWithForm.h"
 #include "MantidDataHandling/LoadNexusProcessed.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/IndexingUtils.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
@@ -85,4 +86,63 @@ public:
 
     AnalysisDataService::Instance().remove(WSName);
   }
+
+  void test_exec_LeanElasticPeak() {
+    // Name of the loader's output workspace.
+    std::string WSName("peaks");
+    LoadNexusProcessed loader;
+    TS_ASSERT_THROWS_NOTHING(loader.initialize());
+    TS_ASSERT(loader.isInitialized());
+    loader.setPropertyValue("Filename", "TOPAZ_3007.peaks.nxs");
+    loader.setPropertyValue("OutputWorkspace", WSName);
+    TS_ASSERT(loader.execute());
+    TS_ASSERT(loader.isExecuted());
+
+    PeaksWorkspace_sptr ws;
+    TS_ASSERT_THROWS_NOTHING(
+        ws = std::dynamic_pointer_cast<PeaksWorkspace>(
+            AnalysisDataService::Instance().retrieve(WSName)));
+    TS_ASSERT(ws);
+
+    // Convert PeaksWorkspace to LeanElasticPeaksWorkspace
+    auto lpw = std::make_shared<LeanElasticPeaksWorkspace>();
+    for (auto peak : ws->getPeaks())
+      lpw->addPeak(peak);
+    AnalysisDataService::Instance().addOrReplace(WSName, lpw);
+
+    // set a Niggli UB for run 3007
+    // (CuTCA) in the oriented lattice
+    V3D row_0(0.0122354, 0.00480056, 0.0860404);
+    V3D row_1(-0.1165450, 0.00178145, -0.0045884);
+    V3D row_2(-0.0273738, -0.08973560, -0.0252595);
+
+    Matrix<double> UB(3, 3, false);
+    UB.setRow(0, row_0);
+    UB.setRow(1, row_1);
+    UB.setRow(2, row_2);
+
+    auto lattice = std::make_unique<OrientedLattice>();
+    lattice->setUB(UB);
+    lpw->mutableSample().setOrientedLattice(std::move(lattice));
+
+    // now get the UB back from the WS
+    UB = lpw->sample().getOrientedLattice().getUB();
+
+    SelectCellWithForm alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("PeaksWorkspace", WSName));
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("FormNumber", 35));
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Apply", true));
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("Tolerance", 0.12));
+    TS_ASSERT_THROWS_NOTHING(alg.execute(););
+    TS_ASSERT(alg.isExecuted());
+
+    int num_indexed = alg.getProperty("NumIndexed");
+    TS_ASSERT_EQUALS(num_indexed, 43);
+    double average_error = alg.getProperty("AverageError");
+    TS_ASSERT_DELTA(average_error, 0.00972862, .0001);
+
+    AnalysisDataService::Instance().remove(WSName);
+  }
 };
diff --git a/Framework/Crystal/test/ShowPossibleCellsTest.h b/Framework/Crystal/test/ShowPossibleCellsTest.h
index 809caa15391a2cb2a33910ff199fd78786cadac4..2e65e0e7ade0433ce97c94e77fa23b196942696b 100644
--- a/Framework/Crystal/test/ShowPossibleCellsTest.h
+++ b/Framework/Crystal/test/ShowPossibleCellsTest.h
@@ -15,6 +15,7 @@
 #include "MantidCrystal/LoadIsawUB.h"
 #include "MantidCrystal/ShowPossibleCells.h"
 #include "MantidDataHandling/LoadNexusProcessed.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/IndexingUtils.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
@@ -105,4 +106,83 @@ public:
 
     AnalysisDataService::Instance().remove(WSName);
   }
+
+  void test_exec_LeanElasticPeaks() {
+    // Name of the output workspace.
+    std::string WSName("peaks");
+    LoadNexusProcessed loader;
+    TS_ASSERT_THROWS_NOTHING(loader.initialize());
+    TS_ASSERT(loader.isInitialized());
+    loader.setPropertyValue("Filename", "TOPAZ_3007.peaks.nxs");
+    loader.setPropertyValue("OutputWorkspace", WSName);
+
+    TS_ASSERT(loader.execute());
+    TS_ASSERT(loader.isExecuted());
+    PeaksWorkspace_sptr ws;
+    TS_ASSERT_THROWS_NOTHING(
+        ws = std::dynamic_pointer_cast<PeaksWorkspace>(
+            AnalysisDataService::Instance().retrieve(WSName)));
+    TS_ASSERT(ws);
+
+    // Convert PeaksWorkspace to LeanElasticPeaksWorkspace
+    auto lpw = std::make_shared<LeanElasticPeaksWorkspace>();
+    for (auto peak : ws->getPeaks())
+      lpw->addPeak(peak);
+    AnalysisDataService::Instance().addOrReplace(WSName, lpw);
+
+    // set a Niggli UB in the
+    // oriented lattice
+    V3D row_0(-0.101246, -0.040644, -0.061869);
+    V3D row_1(0.014004, -0.079212, 0.007344);
+    V3D row_2(-0.063451, 0.011072, 0.064430);
+
+    Matrix<double> UB(3, 3, false);
+    UB.setRow(0, row_0);
+    UB.setRow(1, row_1);
+    UB.setRow(2, row_2);
+
+    auto lattice = std::make_unique<Mantid::Geometry::OrientedLattice>();
+    lattice->setUB(UB);
+    lpw->mutableSample().setOrientedLattice(std::move(lattice));
+
+    // now get the UB back from the WS
+    UB = lpw->sample().getOrientedLattice().getUB();
+
+    ShowPossibleCells alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("PeaksWorkspace", WSName));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("MaxScalarError", "0.2"));
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("BestOnly", true));
+    TS_ASSERT_THROWS_NOTHING(alg.execute(););
+    TS_ASSERT(alg.isExecuted());
+
+    // Check the number of cells found for different input parameters
+    int num_cells = alg.getProperty("NumberOfCells");
+    TS_ASSERT_EQUALS(num_cells, 2);
+
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("PeaksWorkspace", WSName));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("MaxScalarError", "10"));
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("BestOnly", true));
+    TS_ASSERT_THROWS_NOTHING(alg.execute(););
+    TS_ASSERT(alg.isExecuted());
+
+    num_cells = alg.getProperty("NumberOfCells");
+    TS_ASSERT_EQUALS(num_cells, 14);
+
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("PeaksWorkspace", WSName));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("MaxScalarError", "10"));
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("BestOnly", false));
+    TS_ASSERT_THROWS_NOTHING(alg.execute(););
+    TS_ASSERT(alg.isExecuted());
+
+    num_cells = alg.getProperty("NumberOfCells");
+    TS_ASSERT_EQUALS(num_cells, 42);
+
+    AnalysisDataService::Instance().remove(WSName);
+  }
 };
diff --git a/Framework/Crystal/test/TransformHKLTest.h b/Framework/Crystal/test/TransformHKLTest.h
index 3c8af953680b239fa5fe62c00c3905278395ae18..bf81a3e88be43c43b833662571ce9a4992f764a7 100644
--- a/Framework/Crystal/test/TransformHKLTest.h
+++ b/Framework/Crystal/test/TransformHKLTest.h
@@ -15,9 +15,11 @@
 #include "MantidCrystal/LoadIsawUB.h"
 #include "MantidCrystal/TransformHKL.h"
 #include "MantidDataHandling/LoadNexusProcessed.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/IndexingUtils.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
+#include "MantidKernel/SpecialCoordinateSystem.h"
 
 using namespace Mantid::Crystal;
 using namespace Mantid::API;
@@ -116,4 +118,31 @@ public:
 
     AnalysisDataService::Instance().remove(WSName);
   }
+
+  void test_exec_LeanElasticPeak() {
+    auto ws = std::make_shared<LeanElasticPeaksWorkspace>();
+    AnalysisDataService::Instance().addOrReplace("ws", ws);
+    auto lattice = std::make_unique<Mantid::Geometry::OrientedLattice>(
+        5, 6, 7, 90, 90, 120);
+    ws->mutableSample().setOrientedLattice(std::move(lattice));
+    ws->addPeak(V3D(1, 0, 0), SpecialCoordinateSystem::HKL);
+    ws->addPeak(V3D(0, 2, 0), SpecialCoordinateSystem::HKL);
+    ws->addPeak(V3D(0, 0, 3), SpecialCoordinateSystem::HKL);
+
+    TransformHKL alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("PeaksWorkspace", "ws"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Tolerance", "0.1"));
+
+    // specify a matrix that will swap H and K and negate L
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("HKLTransform", "0,1,0,1,0,0,0,0,-1"));
+    TS_ASSERT_THROWS_NOTHING(alg.execute(););
+    TS_ASSERT(alg.isExecuted());
+
+    TS_ASSERT_EQUALS(ws->getPeak(0).getHKL(), V3D(0, 1, 0))
+    TS_ASSERT_EQUALS(ws->getPeak(1).getHKL(), V3D(2, 0, 0))
+    TS_ASSERT_EQUALS(ws->getPeak(2).getHKL(), V3D(0, 0, -3))
+  }
 };
diff --git a/Framework/DataHandling/src/LoadILLTOF2.cpp b/Framework/DataHandling/src/LoadILLTOF2.cpp
index 86ddc6c85e57843bb314e745852ed67c57997fe8..a9297bc5e6fc192df582afd3e49cfe91bd99cdf4 100644
--- a/Framework/DataHandling/src/LoadILLTOF2.cpp
+++ b/Framework/DataHandling/src/LoadILLTOF2.cpp
@@ -19,8 +19,8 @@
 
 namespace {
 /// An array containing the supported instrument names
-const std::array<std::string, 4> SUPPORTED_INSTRUMENTS = {
-    {"IN4", "IN5", "IN6", "PANTHER"}};
+const std::array<std::string, 5> SUPPORTED_INSTRUMENTS = {
+    {"IN4", "IN5", "IN6", "PANTHER", "SHARP"}};
 } // namespace
 
 namespace Mantid {
diff --git a/Framework/DataHandling/test/LoadILLTOF2Test.h b/Framework/DataHandling/test/LoadILLTOF2Test.h
index 67b97d55707919a8e7328108f48241e9cdd19011..e76ae0733d29913050d001b1d9f103fb022cc533 100644
--- a/Framework/DataHandling/test/LoadILLTOF2Test.h
+++ b/Framework/DataHandling/test/LoadILLTOF2Test.h
@@ -251,6 +251,30 @@ public:
     loadDataFile("ILL/IN5/104007.nxs", histogramCount, monitorCount,
                  channelCount, tofDelay, tofChannelWidth, convertToTOF);
   }
+
+  void test_SHARP_load() {
+    // From the input test file.
+    const double tofDelay = 0;
+    const double tofChannelWidth = 0;
+    const size_t channelCount = 1;
+    const size_t histogramCount = 61441;
+    const size_t monitorCount = 1;
+    const bool convertToTOF = false;
+    loadDataFile("ILL/SHARP/000102.nxs", histogramCount, monitorCount,
+                 channelCount, tofDelay, tofChannelWidth, convertToTOF);
+  }
+
+  void test_SHARP_TOF_load() {
+    // From the input test file.
+    const double tofDelay = 4942.31;
+    const double tofChannelWidth = 14.6484;
+    const size_t channelCount = 512;
+    const size_t histogramCount = 61441;
+    const size_t monitorCount = 1;
+    const bool convertToTOF = true;
+    loadDataFile("ILL/SHARP/000103.nxs", histogramCount, monitorCount,
+                 channelCount, tofDelay, tofChannelWidth, convertToTOF);
+  }
 };
 
 //------------------------------------------------------------------------------
diff --git a/Framework/DataHandling/test/LoadILLTest.h b/Framework/DataHandling/test/LoadILLTest.h
index d35d0925cf468859926319bff8a9041ac5c8c7e6..0aab2b48a8e205f6f74f649542283d6e32616cfe 100644
--- a/Framework/DataHandling/test/LoadILLTest.h
+++ b/Framework/DataHandling/test/LoadILLTest.h
@@ -92,6 +92,11 @@ public:
     checkLoader("ILL/PANTHER/001723", "LoadILLTOF");
   }
 
+  void test_loadTOF_SHARP() {
+    checkLoader("ILL/SHARP/000102", "LoadILLTOF"); // single-channel
+    checkLoader("ILL/SHARP/000103", "LoadILLTOF");
+  }
+
   void test_loadReflectometry_D17() {
     checkLoader("ILL/D17/317370", "LoadILLReflectometry");
   }
diff --git a/Framework/DataObjects/inc/MantidDataObjects/BasePeak.h b/Framework/DataObjects/inc/MantidDataObjects/BasePeak.h
index ac3aa85b097a8832cc116c206f7f462209cc0fd1..68e1167512b779540f114c87c24a041b4cb641c6 100644
--- a/Framework/DataObjects/inc/MantidDataObjects/BasePeak.h
+++ b/Framework/DataObjects/inc/MantidDataObjects/BasePeak.h
@@ -98,16 +98,16 @@ public:
   void setPeakNumber(int m_peakNumber) override;
   int getPeakNumber() const override;
 
-  double getValueByColName(std::string colName) const;
+  virtual double getValueByColName(std::string colName) const;
 
   /// Get the peak shape.
   const Mantid::Geometry::PeakShape &getPeakShape() const override;
 
   /// Set the PeakShape
-  void setPeakShape(Mantid::Geometry::PeakShape *shape);
+  void setPeakShape(Mantid::Geometry::PeakShape *shape) override;
 
   /// Set the PeakShape
-  void setPeakShape(Mantid::Geometry::PeakShape_const_sptr shape);
+  void setPeakShape(Mantid::Geometry::PeakShape_const_sptr shape) override;
 
   /// Assignment
   BasePeak &operator=(const BasePeak &other);
diff --git a/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeak.h b/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeak.h
index 09057be8c28393879646f8666122e2eb4ada2c70..f05a958380e29cf361501a4bc54dfd76d1e38529 100644
--- a/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeak.h
+++ b/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeak.h
@@ -57,12 +57,7 @@ public:
   // Construct a peak from a reference to the interface
   explicit LeanElasticPeak(const Geometry::IPeak &ipeak);
 
-  void setDetectorID(int) override;
-  int getDetectorID() const override;
-
-  void setInstrument(const Geometry::Instrument_const_sptr &) override;
   Geometry::IDetector_const_sptr getDetector() const override;
-  Geometry::Instrument_const_sptr getInstrument() const override;
   std::shared_ptr<const Geometry::ReferenceFrame>
   getReferenceFrame() const override;
 
diff --git a/Framework/DataObjects/inc/MantidDataObjects/Peak.h b/Framework/DataObjects/inc/MantidDataObjects/Peak.h
index e2cb112f5050cb4b3b4bd0ea2045417ab7c42363..cf6f0eb65368c7a2b8a1585653cea463159027c9 100644
--- a/Framework/DataObjects/inc/MantidDataObjects/Peak.h
+++ b/Framework/DataObjects/inc/MantidDataObjects/Peak.h
@@ -9,7 +9,6 @@
 #include "MantidDataObjects/BasePeak.h"
 #include "MantidDataObjects/LeanElasticPeak.h"
 #include "MantidGeometry/Crystal/IPeak.h"
-#include "MantidGeometry/Crystal/PeakShape.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidKernel/Logger.h"
 #include "MantidKernel/Matrix.h"
@@ -29,7 +28,7 @@ namespace DataObjects {
 
 /** Structure describing a single-crystal peak. The peak is described
  * by the physical detector position (determined either from detector
- * infomation or calculated from Q-lab) and inital/final energy
+ * information or calculated from Q-lab) and initial/final energy
  * (calculated from Q-lab or provided wavelength)
  *
  */
@@ -79,22 +78,18 @@ public:
        const Geometry::Instrument_const_sptr &inst,
        boost::optional<double> detectorDistance = boost::none);
 
-  void setDetectorID(int id) override;
-  int getDetectorID() const override;
+  void setDetectorID(int id);
+  int getDetectorID() const;
   void addContributingDetID(const int id);
   void removeContributingDetector(const int id);
   const std::set<int> &getContributingDetIDs() const;
 
-  void setInstrument(const Geometry::Instrument_const_sptr &inst) override;
+  void setInstrument(const Geometry::Instrument_const_sptr &inst);
   Geometry::IDetector_const_sptr getDetector() const override;
-  Geometry::Instrument_const_sptr getInstrument() const override;
+  Geometry::Instrument_const_sptr getInstrument() const;
   std::shared_ptr<const Geometry::ReferenceFrame>
   getReferenceFrame() const override;
 
-  /*
-  bool findDetector() override;
-  bool findDetector(const Geometry::InstrumentRayTracer &tracer) override;
-  */
   bool findDetector();
   bool findDetector(const Geometry::InstrumentRayTracer &tracer);
 
@@ -137,6 +132,8 @@ public:
   /// Get the approximate position of a peak that falls off the detectors
   Kernel::V3D getVirtualDetectorPosition(const Kernel::V3D &detectorDir) const;
 
+  double getValueByColName(std::string colName) const override;
+
 private:
   bool findDetector(const Mantid::Kernel::V3D &beam,
                     const Geometry::InstrumentRayTracer &tracer);
diff --git a/Framework/DataObjects/src/BasePeak.cpp b/Framework/DataObjects/src/BasePeak.cpp
index b618b537115feb687313efef614b207b5d9e5efe..8373a048780f227019c9cc757060b9b9f9b9a2dd 100644
--- a/Framework/DataObjects/src/BasePeak.cpp
+++ b/Framework/DataObjects/src/BasePeak.cpp
@@ -309,8 +309,6 @@ double BasePeak::getValueByColName(std::string name) const {
   std::transform(name.begin(), name.end(), name.begin(), ::tolower);
   if (name == "runnumber")
     return double(this->getRunNumber());
-  else if (name == "detid")
-    return double(this->getDetectorID());
   else if (name == "h")
     return this->getH();
   else if (name == "k")
diff --git a/Framework/DataObjects/src/LeanElasticPeak.cpp b/Framework/DataObjects/src/LeanElasticPeak.cpp
index fc5029e8da5b90da0e2d01b736186594d0067530..5c100f1b2d2172bee5213f863982fb36610ff03c 100644
--- a/Framework/DataObjects/src/LeanElasticPeak.cpp
+++ b/Framework/DataObjects/src/LeanElasticPeak.cpp
@@ -99,35 +99,6 @@ void LeanElasticPeak::setWavelength(double wavelength) {
   m_wavelength = wavelength;
 }
 
-//----------------------------------------------------------------------------------------------
-/** Set the detector ID of the pixel at the centre of the peak and look up and
- * cache values related to it. It also adds it to the list of contributing
- * detectors for this peak but does NOT remove the old centre.
- */
-void LeanElasticPeak::setDetectorID(int) {
-  throw Exception::NotImplementedError(
-      "LeanElasticPeak::setDetectorID(): Can't set detectorID on "
-      "LeanElasticPeak");
-}
-
-//----------------------------------------------------------------------------------------------
-/** Get the ID of the detector at the center of the peak  */
-int LeanElasticPeak::getDetectorID() const {
-  throw Exception::NotImplementedError(
-      "LeanElasticPeak::getDetectorID(): no detector ID on LeanElasticPeak");
-}
-
-//----------------------------------------------------------------------------------------------
-/** Set the instrument (and save the source/sample pos).
- * Call setDetectorID AFTER this call.
- *
- */
-void LeanElasticPeak::setInstrument(const Geometry::Instrument_const_sptr &) {
-  throw Exception::NotImplementedError(
-      "LeanElasticPeak::setInstrument(): Can't set instrument on "
-      "LeanElasticPeak");
-}
-
 //----------------------------------------------------------------------------------------------
 /** Return a shared ptr to the detector at center of peak. */
 Geometry::IDetector_const_sptr LeanElasticPeak::getDetector() const {
@@ -135,12 +106,6 @@ Geometry::IDetector_const_sptr LeanElasticPeak::getDetector() const {
       "LeanElasticPeak::getDetector(): Has no detector ID");
 }
 
-/** Return a shared ptr to the instrument for this peak. */
-Geometry::Instrument_const_sptr LeanElasticPeak::getInstrument() const {
-  throw Exception::NotImplementedError(
-      "LeanElasticPeak::setInstrument(): Has no instrument");
-}
-
 /** Return a shared ptr to the reference frame for this peak. */
 std::shared_ptr<const Geometry::ReferenceFrame>
 LeanElasticPeak::getReferenceFrame() const {
diff --git a/Framework/DataObjects/src/Peak.cpp b/Framework/DataObjects/src/Peak.cpp
index 9d3431efc6591726843f3e9f40e83fae89cc8355..0f6107e62b0dadce310d30b2b29e2ea99fbd67c2 100644
--- a/Framework/DataObjects/src/Peak.cpp
+++ b/Framework/DataObjects/src/Peak.cpp
@@ -165,17 +165,17 @@ Peak::Peak(const Peak &other)
  * @return
  */
 Peak::Peak(const Geometry::IPeak &ipeak)
-    : BasePeak(ipeak), m_detectorID(ipeak.getDetectorID()),
-      m_initialEnergy(ipeak.getInitialEnergy()),
+    : BasePeak(ipeak), m_initialEnergy(ipeak.getInitialEnergy()),
       m_finalEnergy(ipeak.getFinalEnergy()) {
-  setInstrument(ipeak.getInstrument());
-  detid_t id = ipeak.getDetectorID();
-  if (id >= 0) {
+  const auto *peak = dynamic_cast<const Peak *>(&ipeak);
+  if (!peak)
+    throw std::invalid_argument(
+        "Cannot construct a Peak from this non-Peak object");
+  setInstrument(peak->getInstrument());
+  detid_t id = peak->getDetectorID();
+  if (id >= 0)
     setDetectorID(id);
-  }
-  if (const auto *peak = dynamic_cast<const Peak *>(&ipeak)) {
-    this->m_detIDs = peak->m_detIDs;
-  }
+  this->m_detIDs = peak->m_detIDs;
 }
 
 //----------------------------------------------------------------------------------------------
@@ -580,6 +580,14 @@ V3D Peak::getVirtualDetectorPosition(const V3D &detectorDir) const {
   return detectorDir * distance;
 }
 
+double Peak::getValueByColName(std::string name) const {
+  std::transform(name.begin(), name.end(), name.begin(), ::tolower);
+  if (name == "detid")
+    return double(this->getDetectorID());
+  else
+    return BasePeak::getValueByColName(name);
+}
+
 /** After creating a peak using the Q in the lab frame,
  * the detPos is set to the direction of the detector (but the detector is
  *unknown)
diff --git a/Framework/DataObjects/src/PeakColumn.cpp b/Framework/DataObjects/src/PeakColumn.cpp
index 9b55ed13ed06991ce6eeb34c267002adcd56ca5d..f656a42614ec9f329d43654939172b9db94a9fb5 100644
--- a/Framework/DataObjects/src/PeakColumn.cpp
+++ b/Framework/DataObjects/src/PeakColumn.cpp
@@ -151,9 +151,11 @@ void PeakColumn<T>::print(size_t index, std::ostream &s) const {
   std::ios::fmtflags fflags(s.flags());
   if (m_name == "RunNumber")
     s << peak.getRunNumber();
-  else if (m_name == "DetID")
-    s << peak.getDetectorID();
-  else if (m_name == "BankName")
+  else if (m_name == "DetID") {
+    auto fullPeak = dynamic_cast<Peak *>(&peak);
+    if (fullPeak)
+      s << fullPeak->getDetectorID();
+  } else if (m_name == "BankName")
     s << peak.getBankName();
   else if (m_name == "QLab")
     s << peak.getQLabFrame();
@@ -314,7 +316,8 @@ template <class T> const void *PeakColumn<T>::void_pointer(size_t index) const {
     value = peak.getPeakNumber();
     return boost::get<int>(&value);
   } else if (m_name == "DetID") {
-    value = peak.getDetectorID();
+    auto fatPeak = dynamic_cast<const DataObjects::Peak &>(peak);
+    value = fatPeak.getDetectorID();
     return boost::get<int>(&value);
   } else if (m_name == "BankName") {
     value = peak.getBankName();
diff --git a/Framework/DataObjects/src/SpecialWorkspace2D.cpp b/Framework/DataObjects/src/SpecialWorkspace2D.cpp
index 2611f07feba9f5ffb76564873ad4a9e736d96386..d303fdb68bdbe30d06397b46336ac0b1d194cfaa 100644
--- a/Framework/DataObjects/src/SpecialWorkspace2D.cpp
+++ b/Framework/DataObjects/src/SpecialWorkspace2D.cpp
@@ -206,7 +206,7 @@ set<detid_t>
 SpecialWorkspace2D::getDetectorIDs(const std::size_t workspaceIndex) const {
   if (size_t(workspaceIndex) > this->getNumberHistograms())
     throw std::invalid_argument(
-        "SpecialWorkspace2D::getDetectorID(): Invalid workspaceIndex given.");
+        "SpecialWorkspace2D::getDetectorIDs(): Invalid workspaceIndex given.");
   return this->getSpectrum(workspaceIndex).getDetectorIDs();
 }
 
diff --git a/Framework/DataObjects/test/LeanElasticPeakTest.h b/Framework/DataObjects/test/LeanElasticPeakTest.h
index 10c3338939954ba6d47a362fb5a62340f171541f..d1a50ae394c1b22594760ad3bc3828378a898efe 100644
--- a/Framework/DataObjects/test/LeanElasticPeakTest.h
+++ b/Framework/DataObjects/test/LeanElasticPeakTest.h
@@ -38,9 +38,7 @@ public:
     TS_ASSERT_EQUALS(p.getQSampleFrame(), V3D(0, 0, 0))
     TS_ASSERT_EQUALS(p.getQLabFrame(), V3D())
 
-    TS_ASSERT_THROWS(p.getDetectorID(), const Exception::NotImplementedError &)
     TS_ASSERT_THROWS(p.getDetector(), const Exception::NotImplementedError &)
-    TS_ASSERT_THROWS(p.getInstrument(), const Exception::NotImplementedError &)
     TS_ASSERT_THROWS(p.getDetectorPosition(),
                      const Exception::NotImplementedError &)
     TS_ASSERT_THROWS(p.getDetectorPositionNoCheck(),
@@ -335,7 +333,5 @@ public:
 
     TS_ASSERT_THROWS(leanpeak.getDetector(),
                      const Exception::NotImplementedError &)
-    TS_ASSERT_THROWS(leanpeak.getInstrument(),
-                     const Exception::NotImplementedError &)
   }
 };
diff --git a/Framework/DataObjects/test/PeakTest.h b/Framework/DataObjects/test/PeakTest.h
index b36d5a91aac9478ea223c13dfe2f86dc32cadcff..ca397137e1c3c2f8663d6d7657e9e1e4c4c66ba7 100644
--- a/Framework/DataObjects/test/PeakTest.h
+++ b/Framework/DataObjects/test/PeakTest.h
@@ -222,7 +222,7 @@ public:
     TS_ASSERT_EQUALS(p.getValueByColName("K"), p.getK());
     TS_ASSERT_EQUALS(p.getValueByColName("L"), p.getL());
     TS_ASSERT_EQUALS(p.getValueByColName("RunNumber"), p.getRunNumber());
-    TS_ASSERT_EQUALS(p.getValueByColName("DetId"), p.getDetectorID())
+    TS_ASSERT_EQUALS(p.getValueByColName("DetID"), p.getDetectorID())
     TS_ASSERT_THROWS_ANYTHING(p.getValueByColName("bankname"));
   }
 
diff --git a/Framework/Geometry/inc/MantidGeometry/Crystal/IPeak.h b/Framework/Geometry/inc/MantidGeometry/Crystal/IPeak.h
index af4c4dababc2b3a5b510163c0de8ed3913f3845f..d1ad3dce179c2594a2b784c8decd90b43639f344 100644
--- a/Framework/Geometry/inc/MantidGeometry/Crystal/IPeak.h
+++ b/Framework/Geometry/inc/MantidGeometry/Crystal/IPeak.h
@@ -26,13 +26,7 @@ class InstrumentRayTracer;
 class MANTID_GEOMETRY_DLL IPeak {
 public:
   virtual ~IPeak() = default;
-
-  virtual void setInstrument(const Geometry::Instrument_const_sptr &inst) = 0;
-
-  virtual int getDetectorID() const = 0;
-  virtual void setDetectorID(int m_DetectorID) = 0;
   virtual Geometry::IDetector_const_sptr getDetector() const = 0;
-  virtual Geometry::Instrument_const_sptr getInstrument() const = 0;
   virtual std::shared_ptr<const Geometry::ReferenceFrame>
   getReferenceFrame() const = 0;
 
@@ -108,6 +102,8 @@ public:
   virtual double getL2() const = 0;
 
   virtual const Mantid::Geometry::PeakShape &getPeakShape() const = 0;
+  virtual void setPeakShape(Mantid::Geometry::PeakShape *shape) = 0;
+  virtual void setPeakShape(Mantid::Geometry::PeakShape_const_sptr shape) = 0;
 
   virtual void setAbsorptionWeightedPathLength(double pathLength) = 0;
   virtual double getAbsorptionWeightedPathLength() const = 0;
diff --git a/Framework/Geometry/src/Objects/CSGObject.cpp b/Framework/Geometry/src/Objects/CSGObject.cpp
index 95b226ac348553b0bc88632019ce94d7969d8350..52709bdef1c0f9d7afa5c7c7ff3ee94dc0862e58 100644
--- a/Framework/Geometry/src/Objects/CSGObject.cpp
+++ b/Framework/Geometry/src/Objects/CSGObject.cpp
@@ -406,9 +406,9 @@ CSGObject::CSGObject(const std::string &shapeXML)
       m_objNum(0), m_handler(), bGeometryCaching(false),
       vtkCacheReader(std::shared_ptr<vtkGeometryCacheReader>()),
       vtkCacheWriter(std::shared_ptr<vtkGeometryCacheWriter>()),
-      m_shapeXML(shapeXML), m_id(), m_material() // empty by default
-{
+      m_shapeXML(shapeXML), m_id() {
   m_handler = std::make_shared<GeometryHandler>(this);
+  m_material = std::make_unique<Material>();
 }
 
 /**
@@ -460,12 +460,7 @@ void CSGObject::setMaterial(const Kernel::Material &material) {
 /**
  * @return The Material that the object is composed from
  */
-const Kernel::Material &CSGObject::material() const {
-  if (!m_material) {
-    m_material = std::make_unique<Material>();
-  }
-  return *m_material;
-}
+const Kernel::Material &CSGObject::material() const { return *m_material; }
 
 /**
  * Returns whether this object has a valid shape
diff --git a/Framework/Geometry/test/MockObjects.h b/Framework/Geometry/test/MockObjects.h
index 7f4c94a3cdc5994ee92c1e06614999df56ea21a3..16aa90434cf64837203baae19d7c57a92f2378f6 100644
--- a/Framework/Geometry/test/MockObjects.h
+++ b/Framework/Geometry/test/MockObjects.h
@@ -64,12 +64,7 @@ Mock IPeak
 ------------------------------------------------------------*/
 class MockIPeak : public Mantid::Geometry::IPeak {
 public:
-  MOCK_METHOD1(setInstrument,
-               void(const Geometry::Instrument_const_sptr &inst));
-  MOCK_CONST_METHOD0(getDetectorID, int());
-  MOCK_METHOD1(setDetectorID, void(int m_DetectorID));
   MOCK_CONST_METHOD0(getDetector, Geometry::IDetector_const_sptr());
-  MOCK_CONST_METHOD0(getInstrument, Geometry::Instrument_const_sptr());
   MOCK_CONST_METHOD0(getReferenceFrame,
                      std::shared_ptr<const Geometry::ReferenceFrame>());
   MOCK_CONST_METHOD0(getRunNumber, int());
@@ -134,6 +129,9 @@ public:
   MOCK_CONST_METHOD0(getDetectorPosition, Mantid::Kernel::V3D());
   MOCK_CONST_METHOD0(getDetectorPositionNoCheck, Mantid::Kernel::V3D());
   MOCK_CONST_METHOD0(getPeakShape, const Mantid::Geometry::PeakShape &());
+  MOCK_METHOD1(setPeakShape, void(Mantid::Geometry::PeakShape *shape));
+  MOCK_METHOD1(setPeakShape,
+               void(Mantid::Geometry::PeakShape_const_sptr shape));
 };
 } // namespace
 GNU_DIAG_ON_SUGGEST_OVERRIDE
diff --git a/Framework/LiveData/inc/MantidLiveData/Kafka/IKafkaStreamDecoder.h b/Framework/LiveData/inc/MantidLiveData/Kafka/IKafkaStreamDecoder.h
index 155982db75ee94f8c63c2f0e89ecca6cd6d9d7ee..a8a901e3eadadac59639fc08b1390d70282675db 100644
--- a/Framework/LiveData/inc/MantidLiveData/Kafka/IKafkaStreamDecoder.h
+++ b/Framework/LiveData/inc/MantidLiveData/Kafka/IKafkaStreamDecoder.h
@@ -66,7 +66,6 @@ public:
   IKafkaStreamDecoder(std::shared_ptr<IKafkaBroker> broker,
                       const std::string &streamTopic,
                       const std::string &runInfoTopic,
-                      const std::string &spDetTopic,
                       const std::string &sampleEnvTopic,
                       const std::string &chopperTopic,
                       const std::string &monitorTopic);
@@ -76,7 +75,6 @@ public:
 
   IKafkaStreamDecoder(IKafkaStreamDecoder &&) noexcept;
 
-public:
   ///@name Start/stop
   ///@{
   void startCapture(bool startNow = true);
@@ -152,7 +150,6 @@ protected:
   /// Topic names
   const std::string m_streamTopic;
   const std::string m_runInfoTopic;
-  const std::string m_spDetTopic;
   const std::string m_sampleEnvTopic;
   const std::string m_chopperTopic;
   const std::string m_monitorTopic;
diff --git a/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaEventListener.h b/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaEventListener.h
index 818befb50cdc8f843219670faf8ce90d8e580ea7..1c157136b1212368dd25d51d7afdbb5e5a88c89a 100644
--- a/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaEventListener.h
+++ b/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaEventListener.h
@@ -24,9 +24,12 @@ namespace LiveData {
 class KafkaEventStreamDecoder;
 
 /**
-  Implementation of a live listener to consume messages from the Kafka system
-  at ISIS. It currently parses the events directly using flatbuffers so will
+  Implementation of a live listener to consume messages from Apache Kafka.
+  This system is developed primarily for the ESS, but is also used to some
+  extent elsewhere (ISIS, ANSTO).
+  It currently parses the events directly using flatbuffers so will
   need updating if the schema changes.
+  Some further documentation is in docs/source/concepts/KafkaLiveStreams.rst
  */
 class DLLExport KafkaEventListener : public API::LiveListener {
 public:
diff --git a/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaEventStreamDecoder.h b/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaEventStreamDecoder.h
index 4d15283d981e365c21eb2e44f84beb6eda1ee942..9e0e8c88e178ca1851f13e806f917e93279e8720 100644
--- a/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaEventStreamDecoder.h
+++ b/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaEventStreamDecoder.h
@@ -37,11 +37,13 @@ public:
   };
 
 public:
-  KafkaEventStreamDecoder(
-      std::shared_ptr<IKafkaBroker> broker, const std::string &eventTopic,
-      const std::string &runInfoTopic, const std::string &spDetTopic,
-      const std::string &sampleEnvTopic, const std::string &chopperTopic,
-      const std::string &monitorTopic, const std::size_t bufferThreshold);
+  KafkaEventStreamDecoder(std::shared_ptr<IKafkaBroker> broker,
+                          const std::string &eventTopic,
+                          const std::string &runInfoTopic,
+                          const std::string &sampleEnvTopic,
+                          const std::string &chopperTopic,
+                          const std::string &monitorTopic,
+                          const std::size_t bufferThreshold);
   ~KafkaEventStreamDecoder() override;
   KafkaEventStreamDecoder(const KafkaEventStreamDecoder &) = delete;
   KafkaEventStreamDecoder &operator=(const KafkaEventStreamDecoder &) = delete;
diff --git a/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaHistoStreamDecoder.h b/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaHistoStreamDecoder.h
index 5ba9015412b22013ac7b7175b887d7567f35e1e6..1dcb03de4ca179049e9940f5b2f580f77f7cfa2d 100644
--- a/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaHistoStreamDecoder.h
+++ b/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaHistoStreamDecoder.h
@@ -19,13 +19,14 @@ namespace LiveData {
 
   A call to startCapture() starts the process of capturing the stream on a
   separate thread.
+
+  Some further documentation is in docs/source/concepts/KafkaLiveStreams.rst
 */
 class DLLExport KafkaHistoStreamDecoder : public IKafkaStreamDecoder {
 public:
   KafkaHistoStreamDecoder(std::shared_ptr<IKafkaBroker> broker,
                           const std::string &histoTopic,
                           const std::string &runInfoTopic,
-                          const std::string &spDetTopic,
                           const std::string &sampleEnvTopic,
                           const std::string &chopperTopic);
   ~KafkaHistoStreamDecoder() override;
diff --git a/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaTopicSubscriber.h b/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaTopicSubscriber.h
index 91917eeda86a0ef6e9e079bd596ebd56216e2380..d6cbdfb10eacc92ccc65c541f48f204a3adb7a9b 100644
--- a/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaTopicSubscriber.h
+++ b/Framework/LiveData/inc/MantidLiveData/Kafka/KafkaTopicSubscriber.h
@@ -45,7 +45,6 @@ public:
   static const std::string EVENT_TOPIC_SUFFIX;
   static const std::string HISTO_TOPIC_SUFFIX;
   static const std::string RUN_TOPIC_SUFFIX;
-  static const std::string DET_SPEC_TOPIC_SUFFIX;
   static const std::string SAMPLE_ENV_TOPIC_SUFFIX;
   static const std::string CHOPPER_TOPIC_SUFFIX;
   static const std::string MONITOR_TOPIC_SUFFIX;
diff --git a/Framework/LiveData/src/Kafka/IKafkaStreamDecoder.cpp b/Framework/LiveData/src/Kafka/IKafkaStreamDecoder.cpp
index 373d711742997ab12b220aa8ef4702dc9b8099aa..ad8a4f6b5952fc63b507913d6dbbeef48c7d3e14 100644
--- a/Framework/LiveData/src/Kafka/IKafkaStreamDecoder.cpp
+++ b/Framework/LiveData/src/Kafka/IKafkaStreamDecoder.cpp
@@ -47,31 +47,28 @@ namespace LiveData {
  * @param broker A reference to a Broker object for creating topic streams
  * @param streamTopic The name of the topic streaming the stream data
  * @param runInfoTopic The name of the topic streaming the run information
- * @param spDetTopic The name of the topic streaming the spectrum-detector
  * @param sampleEnvTopic The name of the topic stream sample environment
  * information. run mapping
  */
 IKafkaStreamDecoder::IKafkaStreamDecoder(std::shared_ptr<IKafkaBroker> broker,
                                          const std::string &streamTopic,
                                          const std::string &runInfoTopic,
-                                         const std::string &spDetTopic,
                                          const std::string &sampleEnvTopic,
                                          const std::string &chopperTopic,
                                          const std::string &monitorTopic)
     : m_broker(std::move(broker)), m_streamTopic(streamTopic),
-      m_runInfoTopic(runInfoTopic), m_spDetTopic(spDetTopic),
-      m_sampleEnvTopic(sampleEnvTopic), m_chopperTopic(chopperTopic),
-      m_monitorTopic(monitorTopic), m_interrupt(false), m_specToIdx(),
-      m_runStart(), m_runId(""), m_thread(), m_capturing(false), m_exception(),
-      m_extractWaiting(false), m_cbIterationEnd([] {}), m_cbError([] {}) {}
+      m_runInfoTopic(runInfoTopic), m_sampleEnvTopic(sampleEnvTopic),
+      m_chopperTopic(chopperTopic), m_monitorTopic(monitorTopic),
+      m_interrupt(false), m_specToIdx(), m_runStart(), m_runId(""), m_thread(),
+      m_capturing(false), m_exception(), m_extractWaiting(false),
+      m_cbIterationEnd([] {}), m_cbError([] {}) {}
 
 IKafkaStreamDecoder::IKafkaStreamDecoder(IKafkaStreamDecoder &&o) noexcept
     : m_broker(std::move(o.m_broker)), m_streamTopic(o.m_streamTopic),
-      m_runInfoTopic(o.m_runInfoTopic), m_spDetTopic(std::move(o.m_spDetTopic)),
-      m_sampleEnvTopic(o.m_sampleEnvTopic), m_chopperTopic(o.m_chopperTopic),
-      m_monitorTopic(o.m_monitorTopic), m_interrupt(o.m_interrupt.load()),
-      m_specToIdx(std::move(o.m_specToIdx)),
-      m_runStart(std::move(o.m_runStart)), m_runId(std::move(o.m_runId)),
+      m_runInfoTopic(o.m_runInfoTopic), m_sampleEnvTopic(o.m_sampleEnvTopic),
+      m_chopperTopic(o.m_chopperTopic), m_monitorTopic(o.m_monitorTopic),
+      m_interrupt(o.m_interrupt.load()), m_specToIdx(std::move(o.m_specToIdx)),
+      m_runStart(o.m_runStart), m_runId(std::move(o.m_runId)),
       m_thread(std::move(o.m_thread)), m_capturing(o.m_capturing.load()),
       m_exception(std::move(o.m_exception)),
       m_cbIterationEnd(std::move(o.m_cbIterationEnd)),
diff --git a/Framework/LiveData/src/Kafka/KafkaEventListener.cpp b/Framework/LiveData/src/Kafka/KafkaEventListener.cpp
index a0225f943ceedd23545284ac1a0b4f065a9c06f9..9ca78001ee17fde4c179494363075dcf06538756 100644
--- a/Framework/LiveData/src/Kafka/KafkaEventListener.cpp
+++ b/Framework/LiveData/src/Kafka/KafkaEventListener.cpp
@@ -86,12 +86,9 @@ bool KafkaEventListener::connect(const Poco::Net::SocketAddress &address) {
   const std::size_t bufferThreshold = getProperty("BufferThreshold");
   auto broker = std::make_shared<KafkaBroker>(address.toString());
   try {
-    const std::string spDetInfoTopic(
-        m_instrumentName + KafkaTopicSubscriber::DET_SPEC_TOPIC_SUFFIX);
-
     m_decoder = std::make_unique<KafkaEventStreamDecoder>(
-        broker, eventTopic, runInfoTopic, spDetInfoTopic, sampleEnvTopic,
-        chopperTopic, monitorTopic, bufferThreshold);
+        broker, eventTopic, runInfoTopic, sampleEnvTopic, chopperTopic,
+        monitorTopic, bufferThreshold);
   } catch (std::exception &exc) {
     g_log.error() << "KafkaEventListener::connect - Connection Error: "
                   << exc.what() << "\n";
diff --git a/Framework/LiveData/src/Kafka/KafkaEventStreamDecoder.cpp b/Framework/LiveData/src/Kafka/KafkaEventStreamDecoder.cpp
index 9817396b0218eee88c7d207019e7393a41c5167d..169c509750674cfe7128b030c3752397fbfb1027 100644
--- a/Framework/LiveData/src/Kafka/KafkaEventStreamDecoder.cpp
+++ b/Framework/LiveData/src/Kafka/KafkaEventStreamDecoder.cpp
@@ -112,17 +112,15 @@ using Types::Event::TofEvent;
  * Constructor
  * @param broker A reference to a Broker object for creating topic streams
  * @param eventTopic The name of the topic streaming the event data
- * @param spDetTopic The name of the topic streaming the spectrum-detector
  * run mapping
  */
 KafkaEventStreamDecoder::KafkaEventStreamDecoder(
     std::shared_ptr<IKafkaBroker> broker, const std::string &eventTopic,
-    const std::string &runInfoTopic, const std::string &spDetTopic,
-    const std::string &sampleEnvTopic, const std::string &chopperTopic,
-    const std::string &monitorTopic, const std::size_t bufferThreshold)
+    const std::string &runInfoTopic, const std::string &sampleEnvTopic,
+    const std::string &chopperTopic, const std::string &monitorTopic,
+    const std::size_t bufferThreshold)
     : IKafkaStreamDecoder(std::move(broker), eventTopic, runInfoTopic,
-                          spDetTopic, sampleEnvTopic, chopperTopic,
-                          monitorTopic),
+                          sampleEnvTopic, chopperTopic, monitorTopic),
       m_intermediateBufferFlushThreshold(bufferThreshold) {
 #ifndef _OPENMP
   g_log.warning() << "Multithreading is not available on your system. This "
diff --git a/Framework/LiveData/src/Kafka/KafkaHistoListener.cpp b/Framework/LiveData/src/Kafka/KafkaHistoListener.cpp
index 737508670e42fce8f7c70b7e2462158d76be9a35..856e1f70d19f0b4116f88f2db1ee574eb55fdb71 100644
--- a/Framework/LiveData/src/Kafka/KafkaHistoListener.cpp
+++ b/Framework/LiveData/src/Kafka/KafkaHistoListener.cpp
@@ -48,8 +48,6 @@ bool KafkaHistoListener::connect(const Poco::Net::SocketAddress &address) {
     const std::string histoTopic(m_instrumentName +
                                  KafkaTopicSubscriber::HISTO_TOPIC_SUFFIX),
         runInfoTopic(m_instrumentName + KafkaTopicSubscriber::RUN_TOPIC_SUFFIX),
-        spDetInfoTopic(m_instrumentName +
-                       KafkaTopicSubscriber::DET_SPEC_TOPIC_SUFFIX),
         sampleEnvTopic(m_instrumentName +
                        KafkaTopicSubscriber::SAMPLE_ENV_TOPIC_SUFFIX),
         chopperTimestampTopic(m_instrumentName +
@@ -57,7 +55,7 @@ bool KafkaHistoListener::connect(const Poco::Net::SocketAddress &address) {
 
     m_decoder = std::make_unique<KafkaHistoStreamDecoder>(
         std::make_shared<KafkaBroker>(address.toString()), histoTopic,
-        runInfoTopic, spDetInfoTopic, sampleEnvTopic, chopperTimestampTopic);
+        runInfoTopic, sampleEnvTopic, chopperTimestampTopic);
   } catch (std::exception &exc) {
     g_log.error() << "KafkaHistoListener::connect - Connection Error: "
                   << exc.what() << "\n";
diff --git a/Framework/LiveData/src/Kafka/KafkaHistoStreamDecoder.cpp b/Framework/LiveData/src/Kafka/KafkaHistoStreamDecoder.cpp
index 0c58dd445eba220333f328c24c168e0bcc959b27..474e1966294d0af7bcb58521ce72f92435c9e318 100644
--- a/Framework/LiveData/src/Kafka/KafkaHistoStreamDecoder.cpp
+++ b/Framework/LiveData/src/Kafka/KafkaHistoStreamDecoder.cpp
@@ -54,15 +54,14 @@ namespace LiveData {
  * Constructor
  * @param broker Kafka broker
  * @param histoTopic The name of the topic streaming the histo data
- * @param spDetTopic The name of the topic streaming the spectrum-detector
  * run mapping
  */
 KafkaHistoStreamDecoder::KafkaHistoStreamDecoder(
     std::shared_ptr<IKafkaBroker> broker, const std::string &histoTopic,
-    const std::string &runInfoTopic, const std::string &spDetTopic,
-    const std::string &sampleEnvTopic, const std::string &chopperTopic)
+    const std::string &runInfoTopic, const std::string &sampleEnvTopic,
+    const std::string &chopperTopic)
     : IKafkaStreamDecoder(std::move(broker), histoTopic, runInfoTopic,
-                          spDetTopic, sampleEnvTopic, chopperTopic, ""),
+                          sampleEnvTopic, chopperTopic, ""),
       m_workspace() {}
 
 /**
diff --git a/Framework/LiveData/src/Kafka/KafkaTopicSubscriber.cpp b/Framework/LiveData/src/Kafka/KafkaTopicSubscriber.cpp
index 92ffb90438e6439302742d13cebbd096190a2142..ead1b32ba67c3e8d266a71ab18e683bb86b0e09b 100644
--- a/Framework/LiveData/src/Kafka/KafkaTopicSubscriber.cpp
+++ b/Framework/LiveData/src/Kafka/KafkaTopicSubscriber.cpp
@@ -62,10 +62,12 @@ namespace LiveData {
 // Public members
 // -----------------------------------------------------------------------------
 
+// These are for default topic names and can be overridden by providing
+// topics in configuration in the Facilities.xml file, see
+// docs/source/concepts/KafkaLiveStreams.rst
 const std::string KafkaTopicSubscriber::EVENT_TOPIC_SUFFIX = "_events";
 const std::string KafkaTopicSubscriber::HISTO_TOPIC_SUFFIX = "_eventSum";
 const std::string KafkaTopicSubscriber::RUN_TOPIC_SUFFIX = "_runInfo";
-const std::string KafkaTopicSubscriber::DET_SPEC_TOPIC_SUFFIX = "_detSpecMap";
 const std::string KafkaTopicSubscriber::SAMPLE_ENV_TOPIC_SUFFIX = "_sampleEnv";
 const std::string KafkaTopicSubscriber::CHOPPER_TOPIC_SUFFIX = "_choppers";
 const std::string KafkaTopicSubscriber::MONITOR_TOPIC_SUFFIX = "_monitors";
diff --git a/Framework/LiveData/test/KafkaEventStreamDecoderTest.h b/Framework/LiveData/test/KafkaEventStreamDecoderTest.h
index af3990f284251f8b4d032659b9262933d960c123..a14aeda31664c12eeb4f9ea5d7b958896377a717 100644
--- a/Framework/LiveData/test/KafkaEventStreamDecoderTest.h
+++ b/Framework/LiveData/test/KafkaEventStreamDecoderTest.h
@@ -524,7 +524,7 @@ private:
       const std::shared_ptr<Mantid::LiveData::IKafkaBroker> &broker) {
     using namespace Mantid::LiveData;
 
-    KafkaEventStreamDecoder testInstance(broker, "", "", "", "", "", "", 0);
+    KafkaEventStreamDecoder testInstance(broker, "", "", "", "", "", 0);
     return KafkaTesting::KafkaTestThreadHelper<KafkaEventStreamDecoder>(
         std::move(testInstance));
   }
diff --git a/Framework/LiveData/test/KafkaHistoStreamDecoderTest.h b/Framework/LiveData/test/KafkaHistoStreamDecoderTest.h
index 5515d2f972c4a7d761a07c0d72ad2a8cfdf07891..dea8cb648edadcaa157fcc8912016960a0fa716d 100644
--- a/Framework/LiveData/test/KafkaHistoStreamDecoderTest.h
+++ b/Framework/LiveData/test/KafkaHistoStreamDecoderTest.h
@@ -63,7 +63,7 @@ public:
         .WillOnce(Return(new FakeHistoSubscriber()))
         .WillOnce(Return(new FakeRunInfoStreamSubscriber(1)));
 
-    KafkaHistoStreamDecoder testInstance(mockBroker, "", "", "", "", "");
+    KafkaHistoStreamDecoder testInstance(mockBroker, "", "", "", "");
     KafkaTestThreadHelper<KafkaHistoStreamDecoder> testHolder(
         std::move(testInstance));
 
diff --git a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/CentroidPeaksMD2.h b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/CentroidPeaksMD2.h
index 9bbd5b5831a14a3673f0a972fd95778ab6791a11..4eabaa978ca9985dcda2faab9b44f038ac6c20b2 100644
--- a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/CentroidPeaksMD2.h
+++ b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/CentroidPeaksMD2.h
@@ -9,7 +9,6 @@
 #include "MantidAPI/Algorithm.h"
 #include "MantidAPI/IMDEventWorkspace_fwd.h"
 #include "MantidDataObjects/MDEventWorkspace.h"
-#include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidKernel/System.h"
 
 namespace Mantid {
diff --git a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/IntegratePeaksMD2.h b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/IntegratePeaksMD2.h
index 77cd713d7832eeee1978ce4b3147cf157b5ac52d..880771778cd35cc894af16495342fa887ffe30ef 100644
--- a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/IntegratePeaksMD2.h
+++ b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/IntegratePeaksMD2.h
@@ -9,6 +9,7 @@
 #include "MantidAPI/Algorithm.h"
 #include "MantidAPI/CompositeFunction.h"
 #include "MantidAPI/IMDEventWorkspace_fwd.h"
+#include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidDataObjects/MDEventWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidDataObjects/Workspace2D.h"
@@ -95,8 +96,7 @@ private:
   std::vector<Kernel::V3D> E1Vec;
 
   /// Check if peaks overlap
-  void checkOverlap(int i,
-                    const Mantid::DataObjects::PeaksWorkspace_sptr &peakWS,
+  void checkOverlap(int i, const Mantid::API::IPeaksWorkspace_sptr &peakWS,
                     Mantid::Kernel::SpecialCoordinateSystem CoordinatesToUse,
                     double radius);
 };
diff --git a/Framework/MDAlgorithms/src/CentroidPeaksMD2.cpp b/Framework/MDAlgorithms/src/CentroidPeaksMD2.cpp
index 0bba8f3e58963a7c4ae443cd02835ff85f1460fb..86ab8c0954c973deb343aa8997ff5442408796df 100644
--- a/Framework/MDAlgorithms/src/CentroidPeaksMD2.cpp
+++ b/Framework/MDAlgorithms/src/CentroidPeaksMD2.cpp
@@ -6,7 +6,9 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidMDAlgorithms/CentroidPeaksMD2.h"
 #include "MantidAPI/IMDEventWorkspace.h"
+#include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidDataObjects/CoordTransformDistance.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/MDEventFactory.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidKernel/ListValidator.h"
@@ -41,13 +43,13 @@ void CentroidPeaksMD2::init() {
       "Fixed radius around each peak position in which to calculate the "
       "centroid.");
 
-  declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                       "PeaksWorkspace", "", Direction::Input),
                   "A PeaksWorkspace containing the peaks to centroid.");
 
   declareProperty(
-      std::make_unique<WorkspaceProperty<PeaksWorkspace>>("OutputWorkspace", "",
-                                                          Direction::Output),
+      std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
+          "OutputWorkspace", "", Direction::Output),
       "The output PeaksWorkspace will be a copy of the input PeaksWorkspace "
       "with the peaks' positions modified by the new found centroids.");
 }
@@ -64,12 +66,10 @@ void CentroidPeaksMD2::integrate(typename MDEventWorkspace<MDE, nd>::sptr ws) {
                                 "to have 3 dimensions only.");
 
   /// Peak workspace to centroid
-  Mantid::DataObjects::PeaksWorkspace_sptr inPeakWS =
-      getProperty("PeaksWorkspace");
+  IPeaksWorkspace_sptr inPeakWS = getProperty("PeaksWorkspace");
 
   /// Output peaks workspace, create if needed
-  Mantid::DataObjects::PeaksWorkspace_sptr peakWS =
-      getProperty("OutputWorkspace");
+  IPeaksWorkspace_sptr peakWS = getProperty("OutputWorkspace");
 
   if (peakWS != inPeakWS)
     peakWS = inPeakWS->clone();
@@ -83,8 +83,11 @@ void CentroidPeaksMD2::integrate(typename MDEventWorkspace<MDE, nd>::sptr ws) {
     PRAGMA_OMP(parallel for schedule(dynamic, 10) )
     for (int i = 0; i < int(peakWS->getNumberPeaks()); ++i) {
       // Get a direct ref to that peak.
-      Peak &p = peakWS->getPeak(i);
-      double detectorDistance = p.getL2();
+      IPeak &p = peakWS->getPeak(i);
+      Peak *peak = dynamic_cast<Peak *>(&p);
+      double detectorDistance;
+      if (peak)
+        detectorDistance = p.getL2();
 
       // Get the peak center as a position in the dimensions of the workspace
       V3D pos;
@@ -128,11 +131,13 @@ void CentroidPeaksMD2::integrate(typename MDEventWorkspace<MDE, nd>::sptr ws) {
           if (CoordinatesToUse == 1) //"Q (lab frame)"
           {
             p.setQLabFrame(vecCentroid, detectorDistance);
-            p.findDetector();
+            if (peak)
+              peak->findDetector();
           } else if (CoordinatesToUse == 2) //"Q (sample frame)"
           {
             p.setQSampleFrame(vecCentroid, detectorDistance);
-            p.findDetector();
+            if (peak)
+              peak->findDetector();
           } else if (CoordinatesToUse == 3) //"HKL"
           {
             p.setHKL(vecCentroid);
diff --git a/Framework/MDAlgorithms/src/IntegratePeaksMD2.cpp b/Framework/MDAlgorithms/src/IntegratePeaksMD2.cpp
index 6351b76feeb3fc0e244d4d6bb7a6139742f1e302..7bdd207662483e962f2c0ea6fac94fe7ec0bfd7f 100644
--- a/Framework/MDAlgorithms/src/IntegratePeaksMD2.cpp
+++ b/Framework/MDAlgorithms/src/IntegratePeaksMD2.cpp
@@ -19,6 +19,7 @@
 #include "MantidAPI/TextAxis.h"
 #include "MantidAPI/WorkspaceFactory.h"
 #include "MantidDataObjects/CoordTransformDistance.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/MDBoxIterator.h"
 #include "MantidDataObjects/MDEventFactory.h"
 #include "MantidDataObjects/Peak.h"
@@ -94,13 +95,13 @@ void IntegratePeaksMD2::init() {
       "peak.\n"
       "If smaller than PeakRadius, no background measurement is done.");
 
-  declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                       "PeaksWorkspace", "", Direction::Input),
                   "A PeaksWorkspace containing the peaks to integrate.");
 
   declareProperty(
-      std::make_unique<WorkspaceProperty<PeaksWorkspace>>("OutputWorkspace", "",
-                                                          Direction::Output),
+      std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
+          "OutputWorkspace", "", Direction::Output),
       "The output PeaksWorkspace will be a copy of the input PeaksWorkspace "
       "with the peaks' integrated intensities.");
 
@@ -263,18 +264,19 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace<MDE, nd>::sptr ws) {
                                 "to have 3 dimensions only.");
 
   /// Peak workspace to integrate
-  Mantid::DataObjects::PeaksWorkspace_sptr inPeakWS =
-      getProperty("PeaksWorkspace");
+  IPeaksWorkspace_sptr inPeakWS = getProperty("PeaksWorkspace");
 
   /// Output peaks workspace, create if needed
-  Mantid::DataObjects::PeaksWorkspace_sptr peakWS =
-      getProperty("OutputWorkspace");
+  IPeaksWorkspace_sptr peakWS = getProperty("OutputWorkspace");
   if (peakWS != inPeakWS)
     peakWS = inPeakWS->clone();
   // This only fails in the unit tests which say that MaskBTP is not registered
   try {
-    runMaskDetectors(inPeakWS, "Tube", "edges");
-    runMaskDetectors(inPeakWS, "Pixel", "edges");
+    PeaksWorkspace_sptr p = std::dynamic_pointer_cast<PeaksWorkspace>(inPeakWS);
+    if (p) {
+      runMaskDetectors(p, "Tube", "edges");
+      runMaskDetectors(p, "Pixel", "edges");
+    }
   } catch (...) {
     g_log.error("Can't execute MaskBTP algorithm for this instrument to set "
                 "edge for IntegrateIfOnEdge option");
@@ -500,13 +502,11 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace<MDE, nd>::sptr ws) {
       // define the radius squared for a sphere intially
       CoordTransformDistance getRadiusSq(nd, center, dimensionsUsed);
       // set spherical shape
-      if (auto *shapeablePeak = dynamic_cast<Peak *>(&p)) {
-        PeakShape *sphereShape = new PeakShapeSpherical(
-            PeakRadiusVector[i], BackgroundInnerRadiusVector[i],
-            BackgroundOuterRadiusVector[i], CoordinatesToUse, this->name(),
-            this->version());
-        shapeablePeak->setPeakShape(sphereShape);
-      }
+      PeakShape *sphereShape = new PeakShapeSpherical(
+          PeakRadiusVector[i], BackgroundInnerRadiusVector[i],
+          BackgroundOuterRadiusVector[i], CoordinatesToUse, this->name(),
+          this->version());
+      p.setPeakShape(sphereShape);
       const double scaleFactor = pow(PeakRadiusVector[i], 3) /
                                  (pow(BackgroundOuterRadiusVector[i], 3) -
                                   pow(BackgroundInnerRadiusVector[i], 3));
@@ -591,27 +591,22 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace<MDE, nd>::sptr ws) {
             bgErrorSquared *= scaleFactor * scaleFactor;
           }
           // set peak shape
-          if (auto *shapeablePeak = dynamic_cast<Peak *>(&p)) {
-            // get radii in same proprtion as eigenvalues
-            auto max_stdev =
-                pow(*std::max_element(eigenvals.begin(), eigenvals.end()), 0.5);
-            std::vector<double> peakRadii(3, 0.0);
-            std::vector<double> backgroundInnerRadii(3, 0.0);
-            std::vector<double> backgroundOuterRadii(3, 0.0);
-            for (size_t irad = 0; irad < peakRadii.size(); irad++) {
-              auto scale = pow(eigenvals[irad], 0.5) / max_stdev;
-              peakRadii[irad] = PeakRadiusVector[i] * scale;
-              backgroundInnerRadii[irad] =
-                  BackgroundInnerRadiusVector[i] * scale;
-              backgroundOuterRadii[irad] =
-                  BackgroundOuterRadiusVector[i] * scale;
-            }
-            PeakShape *ellipsoidShape = new PeakShapeEllipsoid(
-                eigenvects, peakRadii, backgroundInnerRadii,
-                backgroundOuterRadii, CoordinatesToUse, this->name(),
-                this->version(), translation);
-            shapeablePeak->setPeakShape(ellipsoidShape);
+          // get radii in same proprtion as eigenvalues
+          auto max_stdev =
+              pow(*std::max_element(eigenvals.begin(), eigenvals.end()), 0.5);
+          std::vector<double> peakRadii(3, 0.0);
+          std::vector<double> backgroundInnerRadii(3, 0.0);
+          std::vector<double> backgroundOuterRadii(3, 0.0);
+          for (size_t irad = 0; irad < peakRadii.size(); irad++) {
+            auto scale = pow(eigenvals[irad], 0.5) / max_stdev;
+            peakRadii[irad] = PeakRadiusVector[i] * scale;
+            backgroundInnerRadii[irad] = BackgroundInnerRadiusVector[i] * scale;
+            backgroundOuterRadii[irad] = BackgroundOuterRadiusVector[i] * scale;
           }
+          PeakShape *ellipsoidShape = new PeakShapeEllipsoid(
+              eigenvects, peakRadii, backgroundInnerRadii, backgroundOuterRadii,
+              CoordinatesToUse, this->name(), this->version(), translation);
+          p.setPeakShape(ellipsoidShape);
         } else {
           // Use the manually specified radii instead of finding them via
           // findEllipsoid
@@ -668,37 +663,34 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace<MDE, nd>::sptr ws) {
             bgErrorSquared *= scaleFactor * scaleFactor;
           }
           // set peak shape
-          if (auto *shapeablePeak = dynamic_cast<Peak *>(&p)) {
-            // get radii in same proprtion as eigenvalues
-            auto max_stdev =
-                pow(*std::max_element(eigenvals.begin(), eigenvals.end()), 0.5);
-            auto max_stdev_inner =
-                pow(*std::max_element(eigenvals_background_inner.begin(),
-                                      eigenvals_background_inner.end()),
-                    0.5);
-            auto max_stdev_outer =
-                pow(*std::max_element(eigenvals_background_outer.begin(),
-                                      eigenvals_background_outer.end()),
-                    0.5);
-            std::vector<double> peakRadii(3, 0.0);
-            std::vector<double> backgroundInnerRadii(3, 0.0);
-            std::vector<double> backgroundOuterRadii(3, 0.0);
-            for (size_t irad = 0; irad < peakRadii.size(); irad++) {
-              peakRadii[irad] =
-                  PeakRadiusVector[i] * pow(eigenvals[irad], 0.5) / max_stdev;
-              backgroundInnerRadii[irad] =
-                  BackgroundInnerRadiusVector[i] *
-                  pow(eigenvals_background_inner[irad], 0.5) / max_stdev_inner;
-              backgroundOuterRadii[irad] =
-                  BackgroundOuterRadiusVector[i] *
-                  pow(eigenvals_background_outer[irad], 0.5) / max_stdev_outer;
-            }
-            PeakShape *ellipsoidShape = new PeakShapeEllipsoid(
-                eigenvects, peakRadii, backgroundInnerRadii,
-                backgroundOuterRadii, CoordinatesToUse, this->name(),
-                this->version());
-            shapeablePeak->setPeakShape(ellipsoidShape);
+          // get radii in same proprtion as eigenvalues
+          auto max_stdev =
+              pow(*std::max_element(eigenvals.begin(), eigenvals.end()), 0.5);
+          auto max_stdev_inner =
+              pow(*std::max_element(eigenvals_background_inner.begin(),
+                                    eigenvals_background_inner.end()),
+                  0.5);
+          auto max_stdev_outer =
+              pow(*std::max_element(eigenvals_background_outer.begin(),
+                                    eigenvals_background_outer.end()),
+                  0.5);
+          std::vector<double> peakRadii(3, 0.0);
+          std::vector<double> backgroundInnerRadii(3, 0.0);
+          std::vector<double> backgroundOuterRadii(3, 0.0);
+          for (size_t irad = 0; irad < peakRadii.size(); irad++) {
+            peakRadii[irad] =
+                PeakRadiusVector[i] * pow(eigenvals[irad], 0.5) / max_stdev;
+            backgroundInnerRadii[irad] =
+                BackgroundInnerRadiusVector[i] *
+                pow(eigenvals_background_inner[irad], 0.5) / max_stdev_inner;
+            backgroundOuterRadii[irad] =
+                BackgroundOuterRadiusVector[i] *
+                pow(eigenvals_background_outer[irad], 0.5) / max_stdev_outer;
           }
+          PeakShape *ellipsoidShape = new PeakShapeEllipsoid(
+              eigenvects, peakRadii, backgroundInnerRadii, backgroundOuterRadii,
+              CoordinatesToUse, this->name(), this->version());
+          p.setPeakShape(ellipsoidShape);
         }
       }
       // spherical integration of signal
@@ -1315,7 +1307,7 @@ void IntegratePeaksMD2::runMaskDetectors(
 }
 
 void IntegratePeaksMD2::checkOverlap(
-    int i, const Mantid::DataObjects::PeaksWorkspace_sptr &peakWS,
+    int i, const IPeaksWorkspace_sptr &peakWS,
     Mantid::Kernel::SpecialCoordinateSystem CoordinatesToUse, double radius) {
   // Get a direct ref to that peak.
   IPeak &p1 = peakWS->getPeak(i);
diff --git a/Framework/MDAlgorithms/test/CentroidPeaksMD2Test.h b/Framework/MDAlgorithms/test/CentroidPeaksMD2Test.h
index e83e367039eaaed237ee81bd919678ebb6b3840c..4635da2da6b7bfaac775960cb11c3f12075290d0 100644
--- a/Framework/MDAlgorithms/test/CentroidPeaksMD2Test.h
+++ b/Framework/MDAlgorithms/test/CentroidPeaksMD2Test.h
@@ -8,6 +8,7 @@
 
 #include "MantidAPI/AnalysisDataService.h"
 #include "MantidAPI/IMDEventWorkspace.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/MDEventFactory.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/MDGeometry/HKL.h"
@@ -228,6 +229,52 @@ public:
           "CentroidPeaksMD2Test_MDEWS_outputCopy");
   }
 
+  void test_LeanElasticPeak() {
+    createMDEW("Q (sample frame)");
+    addPeak(1000, 1.1, 0.9, 1.05, 0.5);
+    MDEventWorkspace3Lean::sptr mdews =
+        AnalysisDataService::Instance().retrieveWS<MDEventWorkspace3Lean>(
+            "CentroidPeaksMD2Test_MDEWS");
+    mdews->setCoordinateSystem(Mantid::Kernel::QSample);
+
+    auto peakWS = std::make_shared<LeanElasticPeaksWorkspace>();
+
+    peakWS->addPeak(LeanElasticPeak(V3D(1, 1, 1), 1.0));
+
+    TS_ASSERT_EQUALS(peakWS->getPeak(0).getIntensity(), 0.0);
+    AnalysisDataService::Instance().addOrReplace("CentroidPeaksMD2Test_Peaks",
+                                                 peakWS);
+
+    CentroidPeaksMD2 alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("InputWorkspace", "CentroidPeaksMD2Test_MDEWS"));
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("PeaksWorkspace", "CentroidPeaksMD2Test_Peaks"));
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", "CentroidPeaksMD2Test_Peaks"));
+    TS_ASSERT_THROWS_NOTHING(alg.execute());
+    TS_ASSERT(alg.isExecuted());
+
+    peakWS = std::dynamic_pointer_cast<LeanElasticPeaksWorkspace>(
+        AnalysisDataService::Instance().retrieve("CentroidPeaksMD2Test_Peaks"));
+    TS_ASSERT(peakWS);
+    if (!peakWS)
+      return;
+
+    IPeak &p = peakWS->getPeak(0);
+    TS_ASSERT_DELTA(p.getBinCount(), 1000, 0.05);
+
+    V3D result = p.getQSampleFrame();
+    V3D expectedResult(1.1, 0.9, 1.05);
+
+    for (size_t i = 0; i < 3; i++)
+      TS_ASSERT_DELTA(result[i], expectedResult[i], 0.05);
+
+    AnalysisDataService::Instance().remove("CentroidPeaksMD2Test_Peaks");
+  }
+
 private:
   std::string CoordinatesToUse;
 };
diff --git a/Framework/MDAlgorithms/test/IntegrateEllipsoidsTest.h b/Framework/MDAlgorithms/test/IntegrateEllipsoidsTest.h
index ed868549eb92a82ffda5e1eca5d9e53d33e9f2d0..f3de112965c52dfb0c556a58dd3fb5419b6e9a87 100644
--- a/Framework/MDAlgorithms/test/IntegrateEllipsoidsTest.h
+++ b/Framework/MDAlgorithms/test/IntegrateEllipsoidsTest.h
@@ -25,7 +25,10 @@ using namespace Mantid::API;
 using namespace Mantid::MDAlgorithms;
 using namespace Mantid::Kernel;
 using namespace Mantid::Geometry;
+using Mantid::Geometry::IPeak_uptr;
 using namespace Mantid::DataObjects;
+using Mantid::DataObjects::Peak;
+using Mantid::DataObjects::Peak_uptr;
 using Mantid::Types::Event::TofEvent;
 
 namespace {
@@ -35,7 +38,8 @@ void addFakeEllipsoid(const V3D &peakHKL, const int &totalNPixels,
                       EventWorkspace_sptr &eventWS,
                       PeaksWorkspace_sptr &peaksWS) {
   // Create the peak and add it to the peaks ws
-  auto peak = peaksWS->createPeakHKL(peakHKL);
+  IPeak_uptr ipeak = peaksWS->createPeakHKL(peakHKL);
+  Peak_uptr peak(dynamic_cast<Peak *>(ipeak.release()));
   peaksWS->addPeak(*peak);
   const auto detectorId = peak->getDetectorID();
   const auto tofExact = peak->getTOF();
@@ -78,7 +82,9 @@ void addFakeEllipsoid(const V3D &peakHKL, const int &totalNPixels,
     auto detId = detectorId;
     do {
       step_perp[ivect] += 0.02;
-      auto pk = peaksWS->createPeak(Q + eigvects[ivect] * step_perp[ivect]);
+      auto q = Q + eigvects[ivect] * step_perp[ivect];
+      IPeak_uptr ipk = peaksWS->createPeak(q);
+      Peak_uptr pk(dynamic_cast<Peak *>(ipk.release()));
       detId = pk->getDetectorID();
     } while (detId == detectorId);
   }
@@ -87,7 +93,8 @@ void addFakeEllipsoid(const V3D &peakHKL, const int &totalNPixels,
   for (int istep = -1; istep < 2; istep += 2) {
     for (size_t ivect = 0; ivect < step_perp.size(); ivect++) {
       auto q = Q + eigvects[ivect] * step_perp[ivect] * istep;
-      auto pk = peaksWS->createPeak(q);
+      IPeak_uptr ipk = peaksWS->createPeak(q);
+      Peak_uptr pk(dynamic_cast<Peak *>(ipk.release()));
       // add event
       auto detId = pk->getDetectorID();
       EventList &el = eventWS->getSpectrum(detId - totalNPixels);
diff --git a/Framework/MDAlgorithms/test/IntegrateEllipsoidsWithSatellitesTest.h b/Framework/MDAlgorithms/test/IntegrateEllipsoidsWithSatellitesTest.h
index 1873d29476b2d6a4907b8a24da891a8f3c4df681..64ef0b70e4f167d09bee56d32f4993049964161e 100644
--- a/Framework/MDAlgorithms/test/IntegrateEllipsoidsWithSatellitesTest.h
+++ b/Framework/MDAlgorithms/test/IntegrateEllipsoidsWithSatellitesTest.h
@@ -23,7 +23,10 @@ using namespace Mantid;
 using namespace Mantid::MDAlgorithms;
 using namespace Mantid::Kernel;
 using namespace Mantid::Geometry;
+using Mantid::Geometry::IPeak_uptr;
 using namespace Mantid::DataObjects;
+using Mantid::DataObjects::Peak;
+using Mantid::DataObjects::Peak_uptr;
 using Mantid::Types::Event::TofEvent;
 
 namespace {
@@ -33,7 +36,8 @@ void addFakeEllipsoid(const V3D &peakHKL, const V3D &peakMNP,
                       const double tofGap, EventWorkspace_sptr &eventWS,
                       PeaksWorkspace_sptr &peaksWS) {
   // Create the peak and add it to the peaks ws
-  auto peak = peaksWS->createPeakHKL(peakHKL);
+  IPeak_uptr ipeak = peaksWS->createPeakHKL(peakHKL);
+  Peak_uptr peak(dynamic_cast<Peak *>(ipeak.release()));
   peak->setIntMNP(peakMNP);
   peaksWS->addPeak(*peak);
   const auto detectorId = peak->getDetectorID();
@@ -77,7 +81,9 @@ void addFakeEllipsoid(const V3D &peakHKL, const V3D &peakMNP,
     auto detId = detectorId;
     do {
       step_perp[ivect] += 0.02;
-      auto pk = peaksWS->createPeak(Q + eigvects[ivect] * step_perp[ivect]);
+      auto q = Q + eigvects[ivect] * step_perp[ivect];
+      IPeak_uptr ipk = peaksWS->createPeak(q);
+      Peak_uptr pk(dynamic_cast<Peak *>(ipk.release()));
       detId = pk->getDetectorID();
     } while (detId == detectorId);
   }
@@ -86,7 +92,8 @@ void addFakeEllipsoid(const V3D &peakHKL, const V3D &peakMNP,
   for (int istep = -1; istep < 2; istep += 2) {
     for (size_t ivect = 0; ivect < step_perp.size(); ivect++) {
       auto q = Q + eigvects[ivect] * step_perp[ivect] * istep;
-      auto pk = peaksWS->createPeak(q);
+      IPeak_uptr ipk = peaksWS->createPeak(q);
+      Peak_uptr pk(dynamic_cast<Peak *>(ipk.release()));
       // add event
       auto detId = pk->getDetectorID();
       EventList &el = eventWS->getSpectrum(detId - totalNPixels);
diff --git a/Framework/MDAlgorithms/test/IntegratePeaksMD2Test.h b/Framework/MDAlgorithms/test/IntegratePeaksMD2Test.h
index f16ae479863187f33563957f708495be1c9aefd5..c979a09524d33d884d62b38d97d6fdaa22f298fc 100644
--- a/Framework/MDAlgorithms/test/IntegratePeaksMD2Test.h
+++ b/Framework/MDAlgorithms/test/IntegratePeaksMD2Test.h
@@ -10,6 +10,7 @@
 #include "MantidAPI/FrameworkManager.h"
 #include "MantidAPI/IMDEventWorkspace.h"
 #include "MantidAPI/Run.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/MDEventFactory.h"
 #include "MantidDataObjects/PeakShapeSpherical.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
@@ -1115,6 +1116,50 @@ public:
     TS_ASSERT_EQUALS(backgroundInnerRadius,
                      sphericalShape->backgroundInnerRadius().get());
   }
+
+  void test_exec_EllipsoidRadii_with_LeanElasticPeaks() {
+    // Test an ellipsoid against theoretical vol
+    const std::vector<double> radii = {0.4, 0.3, 0.2};
+    const V3D pos(0.0, 0.0, 0.0); // peak position
+    const int numEvents = 1000000;
+
+    createMDEW();
+    addUniform(numEvents, {std::make_pair(-0.5, 0.5), std::make_pair(-0.5, 0.5),
+                           std::make_pair(-0.5, 0.5)});
+
+    auto peakWS = std::make_shared<LeanElasticPeaksWorkspace>();
+    peakWS->addPeak(LeanElasticPeak(pos));
+    AnalysisDataService::Instance().addOrReplace("IntegratePeaksMD2Test_peaks",
+                                                 peakWS);
+
+    doRun(radii, {0.0}, "IntegratePeaksMD2Test_Leanpeaks_out", {0.0}, true,
+          false, "NoFit", 0.0, true, false);
+
+    LeanElasticPeaksWorkspace_sptr peakResult =
+        std::dynamic_pointer_cast<LeanElasticPeaksWorkspace>(
+            AnalysisDataService::Instance().retrieve(
+                "IntegratePeaksMD2Test_Leanpeaks_out"));
+    TS_ASSERT(peakResult);
+
+    // the integrated intensity should end up around
+    // 4/3*PI*0.4*0.3*0.2*numEvents
+    double ellipInten = peakResult->getPeak(0).getIntensity();
+    TS_ASSERT_DELTA(ellipInten, 4. / 3. * M_PI * 0.4 * 0.3 * 0.2 * numEvents,
+                    25);
+
+    // Get the peak's shape
+    const PeakShape &shape = peakResult->getPeak(0).getPeakShape();
+    PeakShapeEllipsoid const *const ellipsoidShape =
+        dynamic_cast<PeakShapeEllipsoid *>(const_cast<PeakShape *>(&shape));
+
+    // Check the shape is what we expect
+    TSM_ASSERT("Wrong sort of peak", ellipsoidShape);
+
+    TS_ASSERT_DELTA(ellipsoidShape->abcRadii(), radii, 1e-9);
+    TS_ASSERT_EQUALS(ellipsoidShape->directions()[0], V3D(1, 0, 0));
+    TS_ASSERT_EQUALS(ellipsoidShape->directions()[1], V3D(0, 1, 0));
+    TS_ASSERT_EQUALS(ellipsoidShape->directions()[2], V3D(0, 0, 1));
+  }
 };
 
 //=========================================================================================
diff --git a/Framework/Properties/Mantid.properties.template b/Framework/Properties/Mantid.properties.template
index 711404dc86bb2a8791f198d1e7cf0137d47facba..4b66dc3c49020090777aa4ff24c46c89a014f5a4 100644
--- a/Framework/Properties/Mantid.properties.template
+++ b/Framework/Properties/Mantid.properties.template
@@ -23,6 +23,8 @@ Q.convention = Inelastic
 
 mantidqt.python_interfaces = ILL/Drill.py Direct/DGS_Reduction.py Direct/DGSPlanner.py Direct/PyChop.py Direct/MSlice.py SANS/ORNL_SANS.py Utility/TofConverter.py Diffraction/Engineering_Diffraction.py Diffraction/Powder_Diffraction_Reduction.py Utility/FilterEvents.py Diffraction/HFIR_4Circle_Reduction.py Utility/QECoverage.py SANS/ISIS_SANS.py Muon/Frequency_Domain_Analysis.py Muon/Elemental_Analysis.py Muon/Frequency_Domain_Analysis_Old.py Muon/Muon_Analysis.py General/Sample_Transmission_Calculator.py
 
+mantidqt.python_interfaces_io_registry = Engineering_Diffraction_register.py
+
 # Directory containing the above startup scripts
 mantidqt.python_interfaces_directory = @MANTID_ROOT@/scripts
 
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/IPeak.cpp b/Framework/PythonInterface/mantid/api/src/Exports/IPeak.cpp
index 13f6c0546cfdde5513656ee71a79271dc12d58fc..d9398e8d6d986f8b56b4da973d78e823db2d7a9c 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/IPeak.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/IPeak.cpp
@@ -59,12 +59,6 @@ void export_IPeak() {
   register_ptr_to_python<IPeak *>();
 
   class_<IPeak, boost::noncopyable>("IPeak", no_init)
-      .def("getDetectorID", &IPeak::getDetectorID, arg("self"),
-           "Get the ID of the :class:`~mantid.geometry.Detector` at the center "
-           "of the peak")
-      .def("setDetectorID", &IPeak::setDetectorID, (arg("self"), arg("det_id")),
-           "Set the :class:`~mantid.geometry.Detector` ID and look up and "
-           "cache values related to it.")
       .def("getRunNumber", &IPeak::getRunNumber, arg("self"),
            "Return the run number this peak was measured at")
       .def("getIntMNP", &IPeak::getIntMNP, arg("self"),
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/IPeaksWorkspace.cpp b/Framework/PythonInterface/mantid/api/src/Exports/IPeaksWorkspace.cpp
index 1e6ecdcfd90db31687085b3d46c69c7f5b26cf05..25c2f22b118db77f1098dcbc9b7f10f99447dccd 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/IPeaksWorkspace.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/IPeaksWorkspace.cpp
@@ -85,7 +85,6 @@ public:
     // Create a map of string -> setter functions
     // Each function will extract the given value from the passed python type.
     m_setterMap = {{"RunNumber", setterFunction(&IPeak::setRunNumber)},
-                   {"DetID", setterFunction(&IPeak::setDetectorID)},
                    {"h", setterFunction(&IPeak::setH)},
                    {"k", setterFunction(&IPeak::setK)},
                    {"l", setterFunction(&IPeak::setL)},
@@ -145,7 +144,7 @@ private:
   /**
    * Wrap a setter function on a IPeak with the bpl::extract function.
    *
-   * This is a specilization of the templated function to handle the
+   * This is a specialization of the templated function to handle the
    * 2 parameter signature of V3D setter functions.
    *
    * @param func A pointer to a member function to wrap.
diff --git a/Framework/PythonInterface/plugins/algorithms/HB2AReduce.py b/Framework/PythonInterface/plugins/algorithms/HB2AReduce.py
index bf16673862235cc91ce73044d3dae387875b25dc..c8462f5d2f861d8b3176d71dbc5f530a8e29921d 100644
--- a/Framework/PythonInterface/plugins/algorithms/HB2AReduce.py
+++ b/Framework/PythonInterface/plugins/algorithms/HB2AReduce.py
@@ -9,7 +9,7 @@ from mantid.api import (PythonAlgorithm, AlgorithmFactory, PropertyMode, Workspa
 from mantid.kernel import (Direction, IntArrayProperty, FloatTimeSeriesProperty,
                            StringListValidator, FloatBoundedValidator, EnabledWhenProperty,
                            PropertyCriterion, Property)
-from mantid.simpleapi import (SaveGSS, SaveFocusedXYE)
+from mantid.simpleapi import (SaveGSSCW, SaveFocusedXYE)
 from mantid import logger
 import numpy as np
 import datetime
@@ -281,18 +281,24 @@ class HB2AReduce(PythonAlgorithm):
             outputdir = outputdir if outputdir != "" else f"/HFIR/HB2A/IPTS-{metadata['proposal']}/shared"
             _outputfunc = {
                 'XYE': SaveFocusedXYE,
-                'GSAS': SaveGSS
+                'GSAS': SaveGSSCW
             }[self.getProperty('OutputFormat').value]
             _outputext = {
                 "XYE": 'dat',
                 "GSAS": 'gss',
             }[self.getProperty('OutputFormat').value]
             outputbase = os.path.join(outputdir, outputfn)
-            _outputfunc(
-                InputWorkspace=outWS,
-                Filename=f"{outputbase}.{_outputext}",
-                SplitFiles=False,
-            )
+            if self.getProperty('OutputFormat').value == "GSAS":
+                _outputfunc(
+                    InputWorkspace=outWS,
+                    OutputFilename=f"{outputbase}.{_outputext}",
+                )
+            else:
+                _outputfunc(
+                    InputWorkspace=outWS,
+                    Filename=f"{outputbase}.{_outputext}",
+                    SplitFiles=False,
+                )
 
     def get_detector_mask(self, exp, indir):
         """Returns an anode mask"""
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANSILLAutoProcess.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANSILLAutoProcess.py
index c57921fecc68c133ced46e0f7bfd42c1943091f3..2ec4cc510de2b775bb77f5def5168448edeaeb83 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANSILLAutoProcess.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/SANSILLAutoProcess.py
@@ -357,10 +357,12 @@ class SANSILLAutoProcess(DataProcessorAlgorithm):
         self.declareProperty('SensitivityWithOffsets', False,
                              'Whether the sensitivity data has been measured with different horizontal offsets.')
 
+    # flake8: noqa: C901
     def PyExec(self):
 
         self.setUp()
-        outputs = []
+        outputSamples = []
+        outputWedges = []
         panel_output_groups = []
         sensitivity_outputs = []
 
@@ -377,10 +379,12 @@ class SANSILLAutoProcess(DataProcessorAlgorithm):
                     beam, flux = self.processBeam(d, absorber)
                 container = self.processContainer(d, beam, absorber,
                                                   container_transmission)
-                sample, panels, sensitivity = self.processSample(d, flux,
-                                                                 sample_transmission, beam,
-                                                                 absorber, container)
-                outputs.append(sample)
+                sample, wedges, panels, sensitivity = \
+                    self.processSample(d, flux, sample_transmission, beam,
+                                       absorber, container)
+                outputSamples.append(sample)
+                outputWedges.append(wedges)
+
                 if sensitivity:
                     sensitivity_outputs.append(sensitivity)
                 if panels:
@@ -388,56 +392,60 @@ class SANSILLAutoProcess(DataProcessorAlgorithm):
             else:
                 self.log().information('Skipping empty token run.')
 
-        for output in outputs:
-            ConvertToPointData(InputWorkspace=output,
-                               OutputWorkspace=output)
-        if len(outputs) > 1 and self.getPropertyValue('OutputType') == 'I(Q)':
+        for i in range(len(outputSamples)):
+            ConvertToPointData(InputWorkspace=outputSamples[i],
+                               OutputWorkspace=outputSamples[i])
+            suffix = self.createCustomSuffix(outputSamples[i])
+            RenameWorkspace(InputWorkspace=outputSamples[i],
+                            OutputWorkspace=outputSamples[i] + suffix)
+            outputSamples[i] += suffix
+
+        if (len(outputSamples) > 1
+           and self.getPropertyValue('OutputType') == 'I(Q)'):
             try:
                 stitched = self.output + "_stitched"
-                Stitch1DMany(InputWorkspaces=outputs,
+                Stitch1DMany(InputWorkspaces=outputSamples,
                              OutputWorkspace=stitched)
-                outputs.append(stitched)
+                outputSamples.append(stitched)
             except RuntimeError as re:
                 self.log().warning("Unable to stitch automatically, consider "
                                    "stitching manually: " + str(re))
 
-        GroupWorkspaces(InputWorkspaces=outputs, OutputWorkspace=self.output)
-
-        # group wedge workspaces
-        if self.output_type == "I(Q)":
-            for w in range(self.n_wedges):
-                wedge_ws = [self.output + "_wedge_" + str(w + 1) + "_" + str(d + 1)
-                            for d in range(self.dimensionality)]
-                # convert to point data and remove nan and 0 from edges
-                for ws in wedge_ws:
-                    ConvertToPointData(InputWorkspace=ws,
-                                       OutputWorkspace=ws)
-                    ReplaceSpecialValues(InputWorkspace=ws,
-                                         OutputWorkspace=ws,
-                                         NaNValue=0)
-                    y = mtd[ws].readY(0)
-                    x = mtd[ws].readX(0)
-                    nonzero = np.nonzero(y)
-
-                    CropWorkspace(InputWorkspace=ws,
-                                  XMin=x[nonzero][0] - 1,
-                                  XMax=x[nonzero][-1],
-                                  OutputWorkspace=ws)
-
-                # and stitch if possible
-                if len(wedge_ws) > 1:
-                    try:
-                        stitched = self.output + "_wedge_" + str(w + 1) \
-                                   + "_stitched"
-                        Stitch1DMany(InputWorkspaces=wedge_ws,
-                                     OutputWorkspace=stitched)
-                        wedge_ws.append(stitched)
-                    except RuntimeError as re:
-                        self.log().warning("Unable to stitch automatically, "
-                                           "consider stitching manually: "
-                                           + str(re))
-                GroupWorkspaces(InputWorkspaces=wedge_ws,
-                                OutputWorkspace=self.output + "_wedge_" + str(w + 1))
+        GroupWorkspaces(InputWorkspaces=outputSamples,
+                        OutputWorkspace=self.output)
+
+        # convert to point data and remove nan and 0 from wedges
+        for i in range(self.dimensionality):
+            for j in range(len(outputWedges[i])):
+                ws = outputWedges[i][j]
+                ConvertToPointData(InputWorkspace=ws, OutputWorkspace=ws)
+                ReplaceSpecialValues(InputWorkspace=ws, OutputWorkspace=ws,
+                                     NaNValue=0)
+                y = mtd[ws].readY(0)
+                x = mtd[ws].readX(0)
+                nonzero = np.nonzero(y)
+
+                CropWorkspace(InputWorkspace=ws, XMin=x[nonzero][0] - 1,
+                              XMax=x[nonzero][-1], OutputWorkspace=ws)
+
+                # add the suffix
+                suffix = self.createCustomSuffix(outputWedges[i][j])
+                RenameWorkspace(InputWorkspace=outputWedges[i][j],
+                                OutputWorkspace=outputWedges[i][j] + suffix)
+                outputWedges[i][j] += suffix
+
+        # stitch if possible and group
+        for i in range(len(outputWedges[0])):
+            inWs = [outputWedges[d][i] for d in range(self.dimensionality)]
+            try:
+                stitched = self.output + "_wedge_" + str(i + 1) + "_stitched"
+                Stitch1DMany(InputWorkspaces=inWs, OutputWorkspace=stitched)
+                inWs.append(stitched)
+            except RuntimeError as re:
+                self.log().warning("Unable to stitch automatically, consider "
+                                   "stitching manually: "+ str(re))
+            GroupWorkspaces(InputWorkspaces=inWs, OutputWorkspace=self.output
+                            + "_wedge_" + str(i + 1))
 
         self.setProperty('OutputWorkspace', mtd[self.output])
         if self.output_sens:
@@ -452,7 +460,16 @@ class SANSILLAutoProcess(DataProcessorAlgorithm):
 
         # group panels
         if panel_output_groups:
-            GroupWorkspaces(InputWorkspaces=panel_output_groups,
+            panelWs = []
+            for groupName in panel_output_groups:
+                wsNames = mtd[groupName].getNames()
+                UnGroupWorkspace(InputWorkspace=groupName)
+                for ws in wsNames:
+                    suffix = self.createCustomSuffix(ws)
+                    RenameWorkspace(InputWorkspace=ws,
+                                    OutputWorkspace=ws + suffix)
+                    panelWs.append(ws + suffix)
+            GroupWorkspaces(InputWorkspaces=panelWs,
                             OutputWorkspace=self.output_panels)
 
     def processTransmissions(self):
@@ -617,6 +634,67 @@ class SANSILLAutoProcess(DataProcessorAlgorithm):
                              NormaliseBy=self.normalise)
         return container_name
 
+    def createCustomSuffix(self, ws):
+        DISTANCE_LOG = "L2"
+        COLLIMATION_LOG = "collimation.actual_position"
+        WAVELENGTH_LOG1 = "wavelength"
+        WAVELENGTH_LOG2 = "selector.wavelength"
+
+        logs = mtd[ws].run().getProperties()
+        logs = {log.name:log.value for log in logs}
+
+        distance = None
+        try:
+            instrument = mtd[ws].getInstrument()
+            components = instrument.getStringParameter('detector_panels')
+            if components:
+                components = components[0].split(',')
+                for c in components:
+                    if c in ws:
+                        distance = instrument.getComponentByName(c).getPos()[2]
+                        break
+            if not distance:
+                distance = float(logs[DISTANCE_LOG])
+            if distance < 0.0:
+                distance = None
+                raise ValueError
+        except:
+            logger.notice("Unable to get a valid detector distance value from "
+                          "the sample logs.")
+        collimation = None
+        try:
+            collimation = float(logs[COLLIMATION_LOG])
+            if collimation < 0.0:
+                collimation = None
+                raise ValueError
+        except:
+            logger.notice("Unable to get a valid collimation distance from "
+                          "the sample logs.")
+        wavelength = None
+        try:
+            wavelength = float(logs[WAVELENGTH_LOG1])
+            if wavelength < 0.0:
+                wavelength = None
+                raise ValueError
+        except:
+            try:
+                wavelength = float(logs[WAVELENGTH_LOG2])
+                if wavelength < 0.0:
+                    wavelength = None
+                    raise ValueError
+            except:
+                logger.notice("Unable to get a valid wavelength from the "
+                              "sample logs.")
+        suffix = ""
+        if distance:
+            suffix += "_d{:.1f}m".format(distance)
+        if collimation:
+            suffix += "_c{:.1f}m".format(collimation)
+        if wavelength:
+            suffix += "_w{:.1f}A".format(wavelength)
+
+        return suffix
+
     def processSample(self, i, flux_name, sample_transmission_names, beam_name,
                       absorber_name, container_name):
         # this is the default mask, the same for all the distance configurations
@@ -671,7 +749,6 @@ class SANSILLAutoProcess(DataProcessorAlgorithm):
 
         # sample
         [_, sample_name] = needs_processing(self.sample[i], 'Sample')
-        output = self.output + '_' + str(i + 1)
         self.progress.report('Processing sample at detector configuration '
                              + str(i + 1))
 
@@ -703,6 +780,8 @@ class SANSILLAutoProcess(DataProcessorAlgorithm):
                 self.getProperty('WaterCrossSection').value,
                 )
 
+        output_sample = self.output + '_' + str(i + 1)
+
         if self.getProperty('OutputPanels').value:
             panel_ws_group = self.output_panels + '_' + str(i + 1)
         else:
@@ -718,7 +797,7 @@ class SANSILLAutoProcess(DataProcessorAlgorithm):
 
         SANSILLIntegration(
                 InputWorkspace=sample_name,
-                OutputWorkspace=output,
+                OutputWorkspace=output_sample,
                 OutputType=self.output_type,
                 CalculateResolution=
                 self.getPropertyValue('CalculateResolution'),
@@ -743,7 +822,7 @@ class SANSILLAutoProcess(DataProcessorAlgorithm):
                 )
 
         # wedges ungrouping and renaming
-        if self.n_wedges and self.output_type == "I(Q)":
+        if output_wedges:
             wedges_old_names = [output_wedges + "_" + str(w + 1)
                                 for w in range(self.n_wedges)]
             wedges_new_names = [self.output + "_wedge_" + str(w + 1)
@@ -752,6 +831,9 @@ class SANSILLAutoProcess(DataProcessorAlgorithm):
             UnGroupWorkspace(InputWorkspace=output_wedges)
             RenameWorkspaces(InputWorkspaces=wedges_old_names,
                              WorkspaceNames=wedges_new_names)
+            output_wedges = wedges_new_names
+        else:
+            output_wedges = []
 
         if self.cleanup:
             DeleteWorkspace(sample_name)
@@ -759,7 +841,7 @@ class SANSILLAutoProcess(DataProcessorAlgorithm):
         if not mtd.doesExist(panel_ws_group):
             panel_ws_group = ""
 
-        return output, panel_ws_group, output_sens
+        return output_sample, output_wedges, panel_ws_group, output_sens
 
 
 AlgorithmFactory.subscribe(SANSILLAutoProcess)
diff --git a/Framework/PythonInterface/test/python/mantid/geometry/IPeakTest.py b/Framework/PythonInterface/test/python/mantid/geometry/IPeakTest.py
index fdc67931287f1929d136e49147c7ba331c7bc311..43b6f2d80092ea6a4a17bf5d06c1909ebc107f82 100644
--- a/Framework/PythonInterface/test/python/mantid/geometry/IPeakTest.py
+++ b/Framework/PythonInterface/test/python/mantid/geometry/IPeakTest.py
@@ -24,15 +24,6 @@ class IPeakTest(unittest.TestCase):
         # on every call.
         self._tolerance = 1e-2
 
-    def test_set_detector_id_with_valid_id(self):
-        det_id = 101
-        self._peak.setDetectorID(det_id)
-        self.assertEqual(self._peak.getDetectorID(), det_id)
-
-    def test_set_detector_id_with_invalid_id(self):
-        det_id = -1
-        self.assertRaises(RuntimeError, self._peak.setDetectorID, det_id)
-
     def test_set_run_number(self):
         run_number = 101
         self._peak.setRunNumber(run_number)
@@ -93,7 +84,7 @@ class IPeakTest(unittest.TestCase):
         npt.assert_allclose(q_lab, q_sample, atol=self._tolerance)
 
     def test_set_goniometer_matrix_with_singular_matrix(self):
-        rotation = np.zeros((3,3))
+        rotation = np.zeros((3, 3))
         self.assertRaises(ValueError, self._peak.setGoniometerMatrix, rotation)
 
     def test_set_wavelength(self):
@@ -102,26 +93,20 @@ class IPeakTest(unittest.TestCase):
         self.assertAlmostEqual(self._peak.getWavelength(), wavelength)
 
     def test_get_scattering(self):
-        det_id = 101
-        expected_scattering_angle = 2.878973314094696
-        self._peak.setDetectorID(det_id)
-        self.assertAlmostEqual(self._peak.getScattering(), expected_scattering_angle)
+        expected_scattering_angle = 2.7024  # angle subtended by detector with ID=1
+        self.assertAlmostEqual(self._peak.getScattering(), expected_scattering_angle,  places=4)
 
     def test_get_tof(self):
-        det_id = 101
         wavelength = 1.9
-        expected_tof = 4103.70182610731
-        self._peak.setDetectorID(det_id)
+        expected_tof = 4112.53
         self._peak.setWavelength(wavelength)
-        self.assertEqual(self._peak.getTOF(), expected_tof)
+        self.assertAlmostEqual(self._peak.getTOF(), expected_tof, places=2)
 
     def test_get_d_spacing(self):
-        det_id = 101
         wavelength = 1.9
-        expected_d = 0.958249313959493
-        self._peak.setDetectorID(det_id)
+        expected_d = 0.973
         self._peak.setWavelength(wavelength)
-        self.assertEqual(self._peak.getDSpacing(), expected_d)
+        self.assertAlmostEqual(self._peak.getDSpacing(), expected_d, places=3)
 
     def test_set_initial_energy(self):
         initial_energy = 10.0
@@ -163,40 +148,33 @@ class IPeakTest(unittest.TestCase):
         self.assertAlmostEqual(self._peak.getBinCount(), bin_count)
 
     def test_get_row_and_column(self):
-        det_id = 101
-        row, col = 36, 1
-        self._peak.setDetectorID(det_id)
+        row, col = 0, 0  # this is the very first detector
         self.assertEqual(self._peak.getRow(), row)
         self.assertEqual(self._peak.getCol(), col)
 
     def test_get_det_pos(self):
-        det_id = 101
-        expected_det_pos = np.array([0.061999,  0.0135, -0.236032])
-        self._peak.setDetectorID(det_id)
-        npt.assert_allclose(self._peak.getDetPos(), expected_det_pos, atol=self._tolerance)
+        expected_det_pos = np.array([0.05962, -0.09450, -0.23786])
+        npt.assert_allclose(self._peak.getDetPos(), expected_det_pos, atol=1e-5)
 
     def test_get_l1(self):
-        det_id = 101
         expected_l1 = 8.3
-        self._peak.setDetectorID(det_id)
         self.assertEqual(self._peak.getL1(), expected_l1)
 
     def test_get_l2(self):
-        det_id = 101
-        expected_l2 = 0.2444125610784556
-        self._peak.setDetectorID(det_id)
-        self.assertEqual(self._peak.getL2(), expected_l2)
+        expected_l2 = 0.26279
+        self.assertAlmostEqual(self._peak.getL2(), expected_l2, places=4)
 
     def test_set_modulation_vector(self):
-        testVector = V3D(0.5,0,0.2)
-        testVectorOut = V3D(1, 0, 0)
-        self._peak.setIntMNP(testVector)
-        self.assertEqual(self._peak.getIntMNP(), testVectorOut)
+        test_vector = V3D(0.5, 0, 0.2)
+        test_vector_out = V3D(1, 0, 0)
+        self._peak.setIntMNP(test_vector)
+        self.assertEqual(self._peak.getIntMNP(), test_vector_out)
 
     def test_set_get_inthkl(self):
-        testVector = V3D(0.5,0,0.2)
-        self._peak.setIntHKL(testVector)
-        self.assertEqual(self._peak.getIntHKL(), V3D(1,0,0))
+        test_vector = V3D(0.5, 0, 0.2)
+        self._peak.setIntHKL(test_vector)
+        self.assertEqual(self._peak.getIntHKL(), V3D(1, 0, 0))
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/HB2AReduceTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/HB2AReduceTest.py
index bf783e328a5154f4563d375882305e948102f15c..e8daad689ce92cad4f26d47868598a43f6eccfd4 100644
--- a/Framework/PythonInterface/test/python/plugins/algorithms/HB2AReduceTest.py
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/HB2AReduceTest.py
@@ -89,6 +89,7 @@ class HB2AReduceTest(unittest.TestCase):
         HB2AReduce_ws.delete()
 
     def test_saving_files(self):
+        # Test for saving XYE data file.
         HB2AReduce_ws = HB2AReduce(
             'HB2A_exp0660_scan0146.dat',
             Vanadium='HB2A_exp0644_scan0018.dat',
@@ -99,6 +100,17 @@ class HB2AReduceTest(unittest.TestCase):
         self.assertTrue(HB2AReduce_ws)
         self.assertTrue(
             os.path.exists(os.path.join(self._default_save_directory, f"{HB2AReduce_ws}.dat")))
+        # Test for saving GSAS data file.
+        HB2AReduce_ws = HB2AReduce(
+            'HB2A_exp0735_scan0016.dat',
+            Vanadium='HB2A_exp0644_scan0018.dat',
+            IndividualDetectors=True,
+            OutputFormat='GSAS',
+            OutputDirectory=self._default_save_directory,
+        )
+        self.assertTrue(HB2AReduce_ws)
+        self.assertTrue(
+            os.path.exists(os.path.join(self._default_save_directory, f"{HB2AReduce_ws}.gss")))
         HB2AReduce_ws.delete()
 
 
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/PaalmanPingsMonteCarloAbsorptionTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/PaalmanPingsMonteCarloAbsorptionTest.py
index abcf996a8d903abd1ca7f1e7e705c22d7fe3349a..fde81c4ec17dd991c155647e50b91ecf17997494 100644
--- a/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/PaalmanPingsMonteCarloAbsorptionTest.py
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/PaalmanPingsMonteCarloAbsorptionTest.py
@@ -26,6 +26,7 @@ class PaalmanPingsMonteCarloAbsorptionTest(unittest.TestCase):
         Load('irs26176_graphite002_red.nxs', OutputWorkspace='geoms_ws')
         # set this workspace to have defined sample and can geometries to test the preset option
         SetSample('geoms_ws', Geometry={'Shape': 'Cylinder', 'Height': 4.0, 'Radius': 2.0, 'Center': [0., 0., 0.]},
+                  Material={'ChemicalFormula': 'Ni'},
                   ContainerGeometry={'Shape': 'HollowCylinder', 'Height': 4.0, 'InnerRadius': 2.0,
                                      'OuterRadius': 3.5})
 
@@ -73,13 +74,13 @@ class PaalmanPingsMonteCarloAbsorptionTest(unittest.TestCase):
         test_func('Annulus')
 
     def _preset_with_override_material_test(self, test_func):
-        test_func(shape='Preset', sample_ws=self._geoms_ws, with_container=True)
+        test_func(shape='Preset', sample_ws=self._geoms_ws)
 
     def _preset_without_override_material_test(self, test_func):
         self._arguments = {'EventsPerPoint': 200,
                            'BeamHeight': 3.5,
                            'BeamWidth': 4.0}
-        test_func(shape='Preset', sample_ws=self._geoms_ws, with_container=True)
+        test_func(shape='Preset', sample_ws=self._geoms_ws)
 
     def _material_with_cross_section_test(self, test_func):
         self._test_arguments['SampleWidth'] = 2.0
@@ -135,7 +136,7 @@ class PaalmanPingsMonteCarloAbsorptionTest(unittest.TestCase):
                                                      **arguments)
         self._test_corrections_workspaces(corrected, spectrum_axis, with_container)
 
-    def _run_correction_with_container_test(self, shape):
+    def _run_correction_with_container_test(self, shape, sample_ws=None):
         self._test_arguments.update(self._container_args)
 
         if shape == 'FlatPlate':
@@ -145,7 +146,7 @@ class PaalmanPingsMonteCarloAbsorptionTest(unittest.TestCase):
         elif shape == 'Annulus':
             self._setup_annulus_container()
 
-        self._run_correction_and_test(shape=shape, with_container=True)
+        self._run_correction_and_test(shape=shape, sample_ws=sample_ws, with_container=True)
 
     def _run_indirect_elastic_test(self, shape):
         self._expected_unit = "MomentumTransfer"
@@ -196,10 +197,10 @@ class PaalmanPingsMonteCarloAbsorptionTest(unittest.TestCase):
         self._annulus_test(self._run_correction_and_test)
 
     def test_preset_with_override_material(self):
-        self._preset_with_override_material_test(self._run_correction_and_test)
+        self._preset_with_override_material_test(self._run_correction_with_container_test)
 
     def test_preset_without_overriding_material(self):
-        self._preset_without_override_material_test(self._run_correction_and_test)
+        self._preset_without_override_material_test(self._run_correction_with_container_test)
 
     def test_flat_plate_with_container(self):
         self._flat_plate_test(self._run_correction_with_container_test)
diff --git a/Framework/TestHelpers/src/SingleCrystalDiffractionTestHelper.cpp b/Framework/TestHelpers/src/SingleCrystalDiffractionTestHelper.cpp
index c2ef8534b9fe2533e7c2bd10dacad4fe8a2feb5d..a20f494eb3acf0dfee30a1c77f9956d992c4b180 100644
--- a/Framework/TestHelpers/src/SingleCrystalDiffractionTestHelper.cpp
+++ b/Framework/TestHelpers/src/SingleCrystalDiffractionTestHelper.cpp
@@ -26,7 +26,9 @@
 using namespace Mantid;
 using namespace Mantid::API;
 using namespace Mantid::DataObjects;
+using DataObjects::Peak_uptr;
 using namespace Mantid::Geometry;
+using Geometry::IPeak_uptr;
 using namespace Mantid::Kernel;
 using Mantid::Types::Event::TofEvent;
 
@@ -162,7 +164,8 @@ void WorkspaceBuilder::createPeak(const HKLPeakDescriptor &descriptor) {
   const auto sigmas = std::get<2>(descriptor);
 
   // Create the peak and add it to the peaks ws
-  const auto peak = m_peaksWorkspace->createPeakHKL(hkl);
+  auto ipeak = m_peaksWorkspace->createPeakHKL(hkl);
+  Peak_uptr peak(dynamic_cast<Peak *>(ipeak.release()));
   m_peaksWorkspace->addPeak(*peak);
 
   // Get detector ID and TOF position of peak
diff --git a/Testing/Data/UnitTest/HB2A_exp0735_scan0016.dat.md5 b/Testing/Data/UnitTest/HB2A_exp0735_scan0016.dat.md5
new file mode 100644
index 0000000000000000000000000000000000000000..b0139cc29bbe5e0a26f0d113ba8e00b257c55edd
--- /dev/null
+++ b/Testing/Data/UnitTest/HB2A_exp0735_scan0016.dat.md5
@@ -0,0 +1 @@
+67a975fe7271cfd2080df244552f1e31
diff --git a/Testing/Data/UnitTest/ILL/SHARP/000102.nxs.md5 b/Testing/Data/UnitTest/ILL/SHARP/000102.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..9420e650a9092b8df50c61c9ad05d6a41ddb7d9b
--- /dev/null
+++ b/Testing/Data/UnitTest/ILL/SHARP/000102.nxs.md5
@@ -0,0 +1 @@
+c105ec9f75c2b237cc6666b4d30524a3
diff --git a/Testing/Data/UnitTest/ILL/SHARP/000103.nxs.md5 b/Testing/Data/UnitTest/ILL/SHARP/000103.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..3d9cb213fc41c36800f33e8088f170f6d483d8a4
--- /dev/null
+++ b/Testing/Data/UnitTest/ILL/SHARP/000103.nxs.md5
@@ -0,0 +1 @@
+1c37d320d0e2eab1a82d79993e1e6920
diff --git a/Testing/SystemTests/tests/framework/ISIS_WISHSingleCrystalReduction.py b/Testing/SystemTests/tests/framework/ISIS_WISHSingleCrystalReduction.py
index 71c7fcf809139a7cfa2c683f8fb130d840420b97..0b9bf0c65bebafb4316848639e5448032d5a5ebb 100644
--- a/Testing/SystemTests/tests/framework/ISIS_WISHSingleCrystalReduction.py
+++ b/Testing/SystemTests/tests/framework/ISIS_WISHSingleCrystalReduction.py
@@ -64,9 +64,10 @@ class WISHSingleCrystalPeakPredictionTest(MantidSystemTest):
         BasicPeak = namedtuple('Peak', ('DetID', 'BankName', 'h', 'k', 'l'))
         expected = BasicPeak(DetID=9202086, BankName='WISHpanel09', h=-5.0, k=-1.0, l=-7.0)
         expected_peak_found = False
-        for full_peak in self._filtered:
-            peak = BasicPeak(DetID=full_peak.getDetectorID(), BankName=full_peak.getBankName(),
-                             h=full_peak.getH(), k=full_peak.getK(), l=full_peak.getL())
+        peak_count = self._filtered.rowCount()
+        for i in range(peak_count):  # iterate of the table representation of the PeaksWorkspace
+            peak_row = self._filtered.row(i)
+            peak = BasicPeak(**{k: peak_row[k] for k in BasicPeak._fields})
             if peak == expected:
                 expected_peak_found = True
                 break
diff --git a/Testing/SystemTests/tests/framework/SANSILLAutoProcessTest.py b/Testing/SystemTests/tests/framework/SANSILLAutoProcessTest.py
index 347e288372dc838123d90ae44f3feb0bc784b733..3ad111a128bb57b9fb187adda92732e30fdd1037 100644
--- a/Testing/SystemTests/tests/framework/SANSILLAutoProcessTest.py
+++ b/Testing/SystemTests/tests/framework/SANSILLAutoProcessTest.py
@@ -124,9 +124,15 @@ class D11_AutoProcess_Wedges_Test(systemtesting.MantidSystemTest):
             )
 
         GroupWorkspaces(
-            InputWorkspaces=['iq_1', 'iq_2', 'iq_3',
-                             'iq_wedge_1_1', 'iq_wedge_1_2', 'iq_wedge_1_3',
-                             'iq_wedge_2_1', 'iq_wedge_2_2', 'iq_wedge_2_3'],
+            InputWorkspaces=['iq_1_d39.0m_c40.5m_w5.6A',
+                             'iq_2_d8.0m_c8.0m_w5.6A',
+                             'iq_3_d2.0m_c5.5m_w5.6A',
+                             'iq_wedge_1_1_d39.0m_c40.5m_w5.6A',
+                             'iq_wedge_1_2_d8.0m_c8.0m_w5.6A',
+                             'iq_wedge_1_3_d2.0m_c5.5m_w5.6A',
+                             'iq_wedge_2_1_d39.0m_c40.5m_w5.6A',
+                             'iq_wedge_2_2_d8.0m_c8.0m_w5.6A',
+                             'iq_wedge_2_3_d2.0m_c5.5m_w5.6A'],
             OutputWorkspace='out'
             )
 
diff --git a/buildconfig/CMake/VersionNumber.cmake b/buildconfig/CMake/VersionNumber.cmake
index 56d86a7624965294b7d03add5cef18911564e376..da4c39ecc5b070e16909ecd226963bbf43413411 100644
--- a/buildconfig/CMake/VersionNumber.cmake
+++ b/buildconfig/CMake/VersionNumber.cmake
@@ -13,7 +13,7 @@ set ( VERSION_MINOR 0 )
 # examples: First release cadidate for tweak 1 is "-1-rc.1"
 #           Second release cadidate for tweak 1 is "-1-rc.2"
 #           Actual tweak release is "-1"
-# set ( VERSION_TWEAK "-2-rc.2" )
+# set ( VERSION_TWEAK "-3-rc.2" )
 
 # pep440 is incompatible with semantic versioning
 # https://www.python.org/dev/peps/pep-0440/
diff --git a/dev-docs/source/ReleaseChecklist.rst b/dev-docs/source/ReleaseChecklist.rst
index 2ffeff92cb4e63b85c4ed2f2ef645ed155536231..6638e3261d8a9e049e6b150d9b7857bf961845ac 100644
--- a/dev-docs/source/ReleaseChecklist.rst
+++ b/dev-docs/source/ReleaseChecklist.rst
@@ -15,260 +15,325 @@ Roles
 #####
 
 The roles are defined in terms of person responsible.
-This does not mean that the person needs to do the jobs himself/herself, but that they are responsible for ensuring that the work gets done.
-
-* Technical Release Manager - Person responsible for technical tasks such as renaming branches, creating tags, configuring build servers.
-* Release Editor - Person responsible for editing the release notes and giving them a common language, layout, and collecting images.
-* Release Manager - Person in charge of the go/no go decision of the release. The main task is to reiterate the timeline and be collection point for information between all of the Local Project Managers.
-* Local Project Manager(s) - People in charge of communicating with local development teams, facility management, and other people at their sponsoring facility.
-* Quality Assurance Manager - Person responsible for making sure that manual testing has been performed. They will ensure Mantid meets quality requirements before delivery in consultation with the *Release Manager*.
+This does not mean that the person needs to do the jobs himself/herself, but that they 
+are responsible for ensuring that the work gets done.
+
+* :ref:`Local Project Manager(s) <local-project-managers-checklist>` - People in charge 
+  of communicating with local development teams, facility management, and other people 
+  at their sponsoring facility.
+* :ref:`Quality Assurance Manager <quality-assurance-manager-checklist>` - Person responsible 
+  for making sure that manual testing has been performed. They will ensure Mantid meets 
+  quality requirements before delivery in consultation with the *Release Manager*.
+* :ref:`Release Editor <release-editor-checklist>` - Person responsible for ensuring the 
+  release notes are edited to give them a common language, layout, and illustrative images.
+* :ref:`Release Manager <release-manager-checklist>` - Person in charge of the go/no go 
+  decision of the release. The main task is to reiterate the timeline and be the collection 
+  point for information between all of the *Local Project Managers*.
+* :ref:`Technical Release Manager <technical-release-manager-checklist>` - Person responsible 
+  for technical tasks such as renaming branches, creating tags, configuring build servers.
 
 Timeline
 ########
 
-Releases are normally planned to occur on a Monday, therefore this
-page will be refer to days assuming that is correct, if the final
-release day is not planned to be Monday then the names of the days
-will have to be changed.
+Releases are normally planned to occur on a Monday, therefore this page will refer 
+to days assuming that is correct. If the final release day is not planned to be 
+Monday then the names of the days will have to be changed.
+
++---------------------------------+---------------------------+-----------------------------------------------+--------------------------------------------------------------------------+
+| | Day,                          | | Key Event(s)            | | Task Priorities                             | | Actions Required from                                                  |
+| | Countdown                     |                           |                                               |                                                                          |
++=================================+===========================+===============================================+==========================================================================+
+| | **Friday**,                   | Code Freeze in 1 week     | Development, Testing & Documentation          | | :ref:`Local Project Manager(s) <local-project-managers-checklist>`     |
+| | 4 weeks & 1 day               |                           |                                               | | :ref:`Release Manager <release-manager-checklist>`                     |
++---------------------------------+---------------------------+-----------------------------------------------+--------------------------------------------------------------------------+
+| | **Friday**,                   | Code Freeze Begins        | Final Development, Testing & Documentation    | | :ref:`Local Project Manager(s) <local-project-managers-checklist>`     |
+| | 3 weeks & 1 day               |                           |                                               | | :ref:`Release Manager <release-manager-checklist>`                     |
++---------------------------------+---------------------------+-----------------------------------------------+--------------------------------------------------------------------------+
+| | **Monday**,                   |                           | Blocker bug fixes, Testing & Release Notes    | | :ref:`Quality Assurance Manager <quality-assurance-manager-checklist>` |
+| | 3 weeks                       |                           |                                               | | :ref:`Release Manager <release-manager-checklist>`                     |
+|                                 |                           |                                               | | :ref:`Technical Release Manager <technical-release-manager-checklist>` |
++---------------------------------+---------------------------+-----------------------------------------------+--------------------------------------------------------------------------+
+| | **Wednesday**,                | Beta Testing Begins       | Blocker bug fixes, Testing, Release Notes,    | | :ref:`Local Project Manager(s) <local-project-managers-checklist>`     |
+| | 2 weeks & 3 days              |                           | Maintenance Tasks & Next release development  | | :ref:`Release Editor <release-editor-checklist>`                       |
+|                                 |                           |                                               | | :ref:`Release Manager <release-manager-checklist>`                     |
++---------------------------------+---------------------------+-----------------------------------------------+--------------------------------------------------------------------------+
+| | **Wednesday**,                |                           | Blocker bug fixes, Testing, Release Notes,    | | :ref:`Release Manager <release-manager-checklist>`                     |
+| | 1 week & 3 days               |                           | Maintenance Tasks & Next release development  |                                                                          |
++---------------------------------+---------------------------+-----------------------------------------------+--------------------------------------------------------------------------+
+| | **Tuesday**,                  | Beta Testing Ends         | Blocker bug fixes, Testing, Release Notes,    | | :ref:`Quality Assurance Manager <quality-assurance-manager-checklist>` |
+| | 4 days                        |                           | Maintenance Tasks & Next release development  | | :ref:`Release Editor <release-editor-checklist>`                       |
+|                                 |                           |                                               | | :ref:`Release Manager <release-manager-checklist>`                     |
++---------------------------------+---------------------------+-----------------------------------------------+--------------------------------------------------------------------------+
+| | **Friday**,                   | Release Eve               | Blocker bug fixes, Testing, Release Notes,    | | :ref:`Release Manager <release-manager-checklist>`                     |
+| | 1 day                         |                           | Maintenance Tasks & Next release development  |                                                                          |
++---------------------------------+---------------------------+-----------------------------------------------+--------------------------------------------------------------------------+
+| | **Monday**,                   | Release Day               | Blocker bug fixes, Testing, Release Notes,    | | :ref:`Release Manager <release-manager-checklist>`                     |
+| | Release Day                   |                           | Maintenance Tasks & Next release development  | | :ref:`Technical Release Manager <technical-release-manager-checklist>` |
++---------------------------------+---------------------------+-----------------------------------------------+--------------------------------------------------------------------------+
+
+.. _local-project-managers-checklist:
+
+Local Project Manager(s) Checklist
+##################################
+
+**Role**: People in charge of communicating with local development teams, facility 
+management, and other people at their sponsoring facility.
+
+Friday, 4 weeks & 1 day
+-----------------------
 
-.. note::
-   We used to release on a Friday, but changed to a Monday as no support was available over the weekend in case issues
-   were raised with the newly released version.
+*  Before the code freeze is in place the PM and Technical Steering Committee (TSC)
+   need to agree the maintenance tasks for the next release period.
 
-Friday, Release-4 weeks (one week before code freeze)
-#########################################################
+Friday, 3 weeks & 1 day
+-----------------------
 
-**Task Priorities**: Development, Testing, Documentation.
+*  Attempt to drive the pull requests for this milestone down to 0, in collaboration
+   with the Release Manager.
 
-*  Post on the General slack channel reminding developers of the
-   impending release and stating that they have only 5 days left before
-   the code freeze.
-*  Send an email to beta test users explaining the dates for the
-   testing, and stating they will have more detail on the start of the
-   first day (cc the Local PMs so they can organise a similar message at their facilities).
-*  Before the code freeze is in place the PM and TSC need to agree the maintenance tasks for the next release period.
+Wednesday, 2 weeks & 3 days
+---------------------------
 
-Friday, Release-3 weeks
-#######################
+*  Ensure that developers arrange to meet with their beta testers.
+*  Triage when necessary the issues discovered during beta testing.
 
-**Task Priorities**: Final Development until code freeze, Testing,
-Documentation.
+.. _quality-assurance-manager-checklist:
 
-Code Freeze
------------
+Quality Assurance Manager Checklist
+###################################
 
-*  Post on the General slack channel asking everyone to ensure they
-   have moved any incomplete issues to the next milestone, stating the code freeze is in place, and
-   warning developers that non-blocker issues will be moved from the
-   milestone on Monday morning.
-*  Final Testing afternoon, attempt to drive the pull requests for this
-   milestone down to 0.
+**Role**: Person responsible for making sure that manual testing has been performed. 
+They will ensure Mantid meets quality requirements before delivery in consultation 
+with the Release Manager.
 
-Monday, Release-2 weeks & 4 days
-################################
+Monday, 3 weeks
+---------------
 
-**Task Priorities**: Blocker bug fixes, Testing, Release Notes.
+*  Ensure that Manual testing begins. The instructions for Manual testing are found 
+   `here <https://www.mantidproject.org/Unscripted_Manual_Testing>`__.
 
-Clearing the project board
---------------------------
+Tuesday, 4 days
+---------------
 
-* Go through the Zenhub project board for the release milestone (not the sprint milestone), ensuring that:
+*  It is likely that many changes have been made over the beta test period, therefore 
+   ensure the manual testing is redone following the instructions described `here 
+   <https://www.mantidproject.org/Unscripted_Manual_Testing>`__.
 
- *  All issues are intended for the release.
- *  Any new issues are triaged on a daily basis, and allocated to staff.
- *  Issues that are not important for the release should be moved to a more appropriate milestone.
-    Don't leave anything in the release milestone that is not definitely for that release.
+.. _release-editor-checklist:
 
+Release Editor Checklist
+########################
 
-Manual and Final Testing
-----------------------------
+**Role**: Person responsible for editing the release notes and giving them a common 
+language, layout, and collecting images.
 
-*  Ensure the
-   `master build and system
-   test <https://builds.mantidproject.org/view/Master%20Pipeline/>`__
-   jobs have passed for all build environments for this release.
-*  Complete any PR testing remaining from Friday
-*  Perform Manual testing following the instructions described
-   `here <https://www.mantidproject.org/Unscripted_Manual_Testing>`__.
+Wednesday, 2 weeks & 3 days
+---------------------------
 
-Maintenance
------------
-*  Present to the whole development team the maintenance tasks for this release period.
-*  Emphasize the order of work priorities as noted in the task priorities throughout this checklist.
-   Maintenance tasks may need to be paused to work on tasks for the release.
+*  Create issues for people to neaten up the release notes and add images etc.
+*  Ensure an image for the release is found to highlight the main changes for this 
+   release. This can be a collage of images if there is not a big 'headline' feature
+   or change.
 
-Create the Release Branch (once most PR's are merged)
------------------------------------------------------
+Tuesday, 4 days
+---------------
 
-*  Ensure the
-   `master build and system
-   test <https://builds.mantidproject.org/view/Master%20Pipeline/>`__
-   jobs have passed for all build environments for this release.
-*  Run
-   `open-release-testing <https://builds.mantidproject.org/view/All/job/open-release-testing/>`__
-   to create the release branch and prepare build jobs
-*  Check state of all open pull requests for this milestone and decide which should be kept for the release,
-   liaise with PM on this. Move any pull requests not targeted for release out of the milestone
-   and run `update-pr-base-branch.py <https://github.com/mantidproject/mantid/blob/master/tools/scripts/update-pr-base-branch.py>`__
-   to update the base branches of those pull requests.
-*  Inform other developers that release-next has been created by adapting/posting the following announcement:
+*  Review the complete set of release notes to make sure there are no glaring mistakes.
 
-  .. code
+.. _release-manager-checklist:
 
-  The release branch for <version>, called release-next, has now been created: https://github.com/mantidproject/mantid/tree/release-next.  If you've not worked with the release/master-branch workflow before then please take a moment to familiarise yourself with the process: https://developer.mantidproject.org/GitWorkflow.html#code-freeze. The part about ensuring new branches have the correct parent is the most important part (although this can be corrected afterwards). All branches and PRs that were created before this release branch was created are fine, as their history is the same as master.
+Release Manager Checklist
+#########################
 
-*  Create a skeleton set of release notes on master for the next version using the `python helper tool <https://github.com/mantidproject/mantid/blob/master/tools/release_generator/release.py>`_ and open a pull request to put them on ``master``.
+**Role**: Person in charge of the go/no go decision of the release. The main task 
+is to reiterate the timeline and be the collection point for information between 
+all of the Local Project Managers.
 
+Friday, 4 weeks & 1 day
+-----------------------
 
-Wednesday, Release- 2 weeks & 3 days
-####################################
+*  Post on the *\#general* slack channel reminding developers of the impending 
+   release and stating that they have only 5 days left before the code freeze.
+*  Send an email to beta test users explaining the dates for the testing, and 
+   stating they will have more detail on the start of the first day (cc the Local 
+   Project Manager(s) so they can organise a similar message at their facilities).
 
-**Task Priorities**: Blocker bug fixes, Testing, Release Notes,  Maintenance Tasks, Next release development.
+Friday, 3 weeks & 1 day
+-----------------------
 
-Beta Test Open
---------------
+*  Post on the *\#general* slack channel asking everyone to ensure they have moved 
+   any incomplete issues to the next milestone, stating the code freeze is in place, 
+   and warning developers that non-blocker issues will be moved from the milestone 
+   on Monday morning.
+*  Attempt to drive the pull requests for this milestone down to 0, in collaboration
+   with the Local Project Managers.
 
-*  Before sending an email to users, ensure that the Usage data .zip
-   file containing usage data is up-to-date. This is done by downloading
-   the current .zip from sourceforge, adding any missing files, and
-   resending it.
-*  Send an email to beta test users explaining where to download the
-   installers and how to report issues (cc the Local PMs so they can organise a similar message at their facilities).
-*  Developers to arrange to meet with their beta testers.
-*  Create issues for people to neaten up the release notes and add images etc.
+Monday, 3 weeks
+---------------
 
-Wednesday, Release- 1 week & 3 days
-###################################
+*  Ensure that PR testing has been completed for PRs from before the code freeze.
 
-**Task Priorities**: Blocker bug fixes, Testing, Release Notes,  Maintenance Tasks, Next release development.
+**Clearing the project board**
 
-Beta Test Reminder
-------------------
+Go through the issues for the release milestone (not the sprint milestone), ensuring that:
 
-*  Send an email to beta test users thanking them for there feedback so far and reminding them to feedback as soon as possible
-   and not to send in a list of issues at the end of testing (cc the Local PMs so they can organise a similar message at their facilities).
+*  All issues are intended for the release.
+*  Any new issues are triaged on a daily basis, and allocated to staff.
+*  Issues that are not important for the release should be moved to a more 
+   appropriate milestone. Don't leave anything in the release milestone that is not 
+   definitely for that release.
 
+**Maintenance**
 
-Tuesday, Release- 4 days
-########################
+*  Present to the whole development team the maintenance tasks for this release period.
+*  Emphasize the order of work priorities as noted by the task priorities in this 
+   checklist. Maintenance tasks may need to be paused to work on tasks for the release.
+
+Wednesday, 2 weeks & 3 days
+---------------------------
+
+*  Before sending an email to users regarding the beginning of beta testing, ensure that 
+   the Usage data *.zip file containing usage data is up-to-date. This is done by 
+   downloading the current *.zip from sourceforge, adding any missing files, and
+   resending it.
+*  Send an email to beta test users explaining where to download the installers and how 
+   to report issues (cc the Local Project Managers so they can organise a similar message 
+   at their facilities).
+
+Wednesday, 1 week & 3 days
+--------------------------
 
-**Task Priorities**: Blocker bug fixes, Testing, Release Notes, Maintenance Tasks, Next release development.
+*  Send a beta test reminder email to beta test users thanking them for there feedback so 
+   far and reminding them to feedback as soon as possible and not to send in a list of 
+   issues at the end of testing (cc the Local Project Managers so they can organise a 
+   similar message at their facilities).
 
-Beta Test Closes
-----------------
+Tuesday, 4 days
+---------------
 
 *  At the end of the day email the beta test users thanking them.
-*  PM should review the complete set of release notes
+*  Review the complete set of release notes to make sure there are no glaring mistakes.
 
-Manual re-testing
------------------
+Friday, 1 day
+-------------
 
-*  Is is likely that many changes have been made over the beta test period, therefore redo the manual testing
-   following the instructions described `here <https://www.mantidproject.org/Unscripted_Manual_Testing>`__.
+* This is the final day for code changes to the build for blocker issues.
 
-Wednesday, Release-2 days
-#########################
+Monday, Release Day
+-------------------
 
-**Task Priorities**: Blocker bug fixes, Testing, Release Notes,  Maintenance Tasks, Next
-release development.
+After the Technical Release Manager has finished their release day tasks:
 
-Thursday, Release-1 day
------------------------
+*  Send an email, including the text of the release notes, to the following lists
+  *  ``nobugs@nobugsconference.org``
+  *  ``news@neutronsources.org``
+  *  ``neutron@neutronsources.org``
+*  Also post the contents of the message to the *\#announcements* channel on 
+   Slack.
+*  Create a new item on the forum news.
+*  Close the release milestone on github.
+
+.. _technical-release-manager-checklist:
+
+Technical Release Manager Checklist
+###################################
+
+**Role**: Person responsible for technical tasks such as renaming branches, creating 
+tags, configuring build servers.
+
+Monday, 3 weeks
+---------------
+
+**Create the Release Branch (once most PR's are merged)**
+
+*  Ensure the `master build and system test 
+   <https://builds.mantidproject.org/view/Master%20Pipeline/>`__
+   jobs have passed for all build environments for this release.
+*  Run `open-release-testing 
+   <https://builds.mantidproject.org/view/All/job/open-release-testing/>`__
+   to create the release branch and prepare build jobs by clicking ``Build Now``.
+*  Check the state of all open pull requests for this milestone and decide which 
+   should be kept for the release, liaise with the Release Manager on this. Move any 
+   pull requests not targeted for release out of the milestone. To update the base 
+   branches of these pull requests run `update-pr-base-branch.py 
+   <https://github.com/mantidproject/mantid/blob/master/tools/scripts/update-pr-base-branch.py>`__
+*  Inform other developers that release-next has been created by posting to the 
+   *\#announcements* slack channel. You can use an adapted version of the 
+   following announcement:
+
+  .. code
+
+  The release branch for <version>, called release-next, has now been created: https://github.com/mantidproject/mantid/tree/release-next.  If you've not worked with the release/master-branch workflow before then please take a moment to familiarise yourself with the process: https://developer.mantidproject.org/GitWorkflow.html#code-freeze. The part about ensuring new branches have the correct parent is the most important part (although this can be corrected afterwards). All branches and PRs that were created before this release branch was created are fine, as their history is the same as master.
 
-**Task Priorities**: Blocker bug fixes, Testing, Release Notes,  Maintenance Tasks, Next
-release development.
-
-Final Code Changes
-------------------
-
-* This is the final day for code changes to the build for blocker
-  issues
-
-Friday, Release day
-###################
-
-**Task Priorities**: Blocker bug fixes, Testing, Release Notes,  Maintenance Tasks, Next
-release development.
-
-Release (technical tasks)
--------------------------
-
-Once the manual testing has passed:
-
-* Check the release notes and remove the "Under Construction" paragraph
-  on the main index page.
-* Disable release deploy jobs by executing
-  `close-release-testing <https://builds.mantidproject.org/view/All/job/close-release-testing>`__
-  job.
-* On the ``release-next`` branch, update the git SHA for MSlice 
-  accordingly in ``scripts/ExternalInterfaces/CMakeLists`` in 
-  case MSlice has to be updated.
-* On the ``release-next`` branch, update major & minor versions
-  accordingly in ``buildconfig/CMake/VersionNumber.cmake``. Also
-  uncomment ``VERSION_PATCH`` and set it to ``0``.
-* Merge ``release-next`` branch back to ``master``
-* Comment out patch number on ``master`` branch
-* Hit build on `release kit
-  builds <https://builds.mantidproject.org/view/Release%20Pipeline/>`__
-  and set the ``PACKAGE_SUFFIX`` parameter to an empty string
-* Draft a `new
-  release <https://github.com/mantidproject/mantid/releases>`__ on
-  GitHub. The new tag should be created based of the release branch in
-  the form ``vX.Y.Z``
-* After all of the packages have been smoke tested run the
-  `release_deploy <https://builds.mantidproject.org/view/Release%20Pipeline/job/release_deploy/>`__
-  job to put the packages, with the exception of Windows, on Sourceforge.
-
-  * Have someone at ISIS signs the Windows binary and upload this
-    manually to Sourceforge
-
-  * Set the default package for each OS to the new version using the information icon
-    next to the file list on Sourceforge
-
-* Upload packages to the GitHub release (essentially for a backup).
-* Publish the GitHub release. This will create the tag required to generate the DOI.
-* Update the `download <https://download.mantidproject.org>`__ page,
-  following the instructions
-  `here <https://github.com/mantidproject/download.mantidproject.org>`__. Once the new
-  file in the `releases` directory is pushed Jenkins will publish the new page.
-* Publish the draft release on GitHub (this will create the tag too).
-* Kick off the build for ``mantidXY`` on RHEL7 for SNS:
-  https://builds.mantidproject.org/job/release_clean-rhel7/ with suffix
-  ``XY``
-* **ISIS**: If in cycle add a calendar reminder for when the current cycle ends for mantid to be updated on IDAaaS and cabin PCs. If out of cycle do this immediately.
-
-Finalise
-========
-
-* Send an email, including the text of the release notes, to the
-  following lists
-* ``nobugs@nobugsconference.org``
-* ``news@neutronsources.org``
-* ``neutron@neutronsources.org``
-* Also post the contents of the message on Announcements on Slack
-* Create a new item on the forum news
-* Close the release milestone on github
-
-Generate DOI (technical tasks)
-------------------------------
-
-This requires that a tag has been created for this release, this is done
-automatically if a new
-`release <https://github.com/mantidproject/mantid/releases>`__ has been
+**Create Release Notes Skeleton**
+
+*  Create a skeleton set of release notes on master for the next version using the 
+   `python helper tool 
+   <https://github.com/mantidproject/mantid/blob/master/tools/release_generator/release.py>`_ 
+   and open a pull request to put them on ``master``. Make sure the 
+   ``docs/source/release/index.rst`` file has a link to the new release docs.
+
+Monday, Release Day
+-------------------
+
+**Release tasks**
+
+Once the manual testing has passed (check with the Quality Assurance Manager):
+
+*  Check the release notes and remove the "Under Construction" paragraph on the main 
+   index page.
+*  Disable release deploy jobs by building the
+   `close-release-testing <https://builds.mantidproject.org/view/All/job/close-release-testing>`__
+   job.
+*  On the ``release-next`` branch, update the git SHA for MSlice accordingly in 
+   ``scripts/ExternalInterfaces/CMakeLists`` in case MSlice has to be updated.
+*  On the ``release-next`` branch, update major & minor versions accordingly in 
+   ``buildconfig/CMake/VersionNumber.cmake``. Also uncomment ``VERSION_PATCH`` and 
+   set it to ``0``.
+*  Merge ``release-next`` branch back to ``master``
+*  Comment out patch number on ``master`` branch
+*  Hit build on `release kit builds <https://builds.mantidproject.org/view/Release%20Pipeline/>`__
+   and set the ``PACKAGE_SUFFIX`` parameter to an empty string
+*  Draft a `new release <https://github.com/mantidproject/mantid/releases>`__ on
+   GitHub. The new tag should be created based of the release branch in the form ``vX.Y.Z``
+*  After all of the packages have been smoke tested run the `release_deploy 
+   <https://builds.mantidproject.org/view/Release%20Pipeline/job/release_deploy/>`__
+   job to put the packages, with the exception of Windows, on Sourceforge.
+
+  *  Have someone at ISIS sign the Windows binary and upload this manually to Sourceforge
+
+  *  Set the default package for each OS to the new version using the information icon
+     next to the file list on Sourceforge
+
+*  Upload packages to the GitHub release (essentially for a backup).
+*  Publish the GitHub release. This will create the tag required to generate the DOI.
+*  Update the `download <https://download.mantidproject.org>`__ page,
+   following the instructions
+   `here <https://github.com/mantidproject/download.mantidproject.org>`__. Once the new
+   file in the `releases` directory is pushed Jenkins will publish the new page.
+*  Publish the draft release on GitHub (this will create the tag too).
+*  Kick off the build for ``mantidXY`` on RHEL7 for SNS:
+   https://builds.mantidproject.org/job/release_clean-rhel7/ with suffix
+   ``XY``.
+* **ISIS**: If in cycle add a calendar reminder for when the current cycle ends for 
+  mantid to be updated on IDAaaS and cabin PCs. If out of cycle do this immediately.
+
+**Generate DOI**
+
+This requires that a tag has been created for this release. This is done automatically 
+if a new `release <https://github.com/mantidproject/mantid/releases>`__ has been
 created on GitHub.
 
-* Make sure that you have updated your local copy of git to grab the
-  new tag. ``git fetch -p``
-* If the script below fails you may need to update the authors list and
-  push the updates to master. Look for ``authors.py`` in the
-  ``tools/DOI`` directory. It does not matter that these are not on the
-  release branch.
+*  Make sure that you have updated your local copy of git to grab the new tag. 
+   ``git fetch -p``
+*  If the script below fails you may need to update the authors list and push the 
+   updates to master. Look for ``authors.py`` in the ``tools/DOI`` directory. 
+   It does not matter that these are not on the release branch.
 
 ``python tools/DOI/doi.py  --username=_____  X.Y.Z``
 
-* Major/minor/patch version numbers must be supplied, as well as a
-  username which can be found in the `Protected
-  Information <https://www.mantidproject.org/Protected_Information>`__
-  section. The script will prompt for the password. Note that only
-  MediaWiki admins have access rights to the page.
-* A corresponding version tag must be present in the Mantid repo.
+*  Major/minor/patch version numbers must be supplied, as well as a username which can 
+   be found in the `Protected Information 
+   <https://www.mantidproject.org/Protected_Information>`__ section. The script will 
+   prompt for the password. Note that only MediaWiki admins have access rights to the page.
+*  A corresponding version tag must be present in the Mantid repo.
diff --git a/docs/source/algorithms/AddPeakHKL-v1.rst b/docs/source/algorithms/AddPeakHKL-v1.rst
index 62ec2126ebf41838569f02fa40670091a0d5ee38..f9725135d482e66c059f229bf62600a08e1cd4d5 100644
--- a/docs/source/algorithms/AddPeakHKL-v1.rst
+++ b/docs/source/algorithms/AddPeakHKL-v1.rst
@@ -43,11 +43,11 @@ Usage
    AddPeakHKL(peak_ws, [2, 0, -4])
 
    # Get info on newly added peak
-   peak = peak_ws.getPeak(0)
-   print('Peak wavelength {}'.format(round(peak.getWavelength(), 4)))
-   print('Peak detector id {}'.format(peak.getDetectorID()))
-   print('Peak run number {}'.format(peak.getRunNumber()))
-   print('Peak HKL {}'.format(peak.getHKL()))
+   peak = peak_ws.row(0)
+   print('Peak wavelength {}'.format(round(peak['Wavelength'], 4)))
+   print('Peak detector id {}'.format(peak['DetID']))
+   print('Peak run number {}'.format(peak['RunNumber']))
+   print('Peak HKL [{},{},{}]'.format(int(peak['h']), int(peak['k']), int(peak['l'])))
 
 Output:
 
diff --git a/docs/source/algorithms/CombinePeaksWorkspaces-v1.rst b/docs/source/algorithms/CombinePeaksWorkspaces-v1.rst
index f3f5534d12f27c82e56302ff53f109afb848666d..edf572872e78b274384f40adac38373f672f487e 100644
--- a/docs/source/algorithms/CombinePeaksWorkspaces-v1.rst
+++ b/docs/source/algorithms/CombinePeaksWorkspaces-v1.rst
@@ -11,14 +11,19 @@ Description
 
 This algorithm can be used to combine lists of single crystal peaks,
 possibly obtained by different methods, in to a single list (contained
-in a :ref:`PeaksWorkspace <PeaksWorkspace>`). 
-With the default options, this will simply append the lists of peaks. 
-If CombineMatchingPeaks is selected then an attempt
-will be made to identify identical peaks by matching them in Q within
-the specified tolerance. The peaks in each workspace are traversed in
-the order they are found in the workspace (RHSWorkspace first) and if a
-match is found (the search stops at the first match for each
-RHSWorkspace peak) then the peak in the LHSWorkspace is retained.
+in a :ref:`PeaksWorkspace <PeaksWorkspace>` or
+:ref:`LeanElasticPeaksWorkspace <LeanElasticPeaksWorkspace>`).  With
+the default options, this will simply append the lists of peaks.  If
+CombineMatchingPeaks is selected then an attempt will be made to
+identify identical peaks by matching them in Q within the specified
+tolerance. The peaks in each workspace are traversed in the order they
+are found in the workspace (RHSWorkspace first) and if a match is
+found (the search stops at the first match for each RHSWorkspace peak)
+then the peak in the LHSWorkspace is retained.
+
+A PeaksWorkspace can be combined with a LeanElasticPeaksWorkspace only
+if the LHSWorkspace is the LeanElasticPeaksWorkspace in which case all
+peaks are converted into LeanElasticPeak.
 
 .. categories::
 
diff --git a/docs/source/algorithms/ConvertCWSDExpToMomentum-v1.rst b/docs/source/algorithms/ConvertCWSDExpToMomentum-v1.rst
index b004008570e81abe68f1d567a5d3832e57149273..5f39e03aa0b264f089f24eab2dc3369d7407a3e4 100644
--- a/docs/source/algorithms/ConvertCWSDExpToMomentum-v1.rst
+++ b/docs/source/algorithms/ConvertCWSDExpToMomentum-v1.rst
@@ -170,10 +170,10 @@ Usage
   print('Output MDEventWorkspace has {} events.'.format(mdws.getNEvents()))
   peakws = mtd['PeakTable']
   print('There are {} peaks found in output MDWorkspace'.format(peakws.getNumberPeaks()))
-  peak = peakws.getPeak(0)
-  qsample = peak.getQSampleFrame()
+  peak = peakws.row(0)
+  qsample = peak['QSample']
   print('In Q-sample frame, center of peak 0 is at ({:.5f}, {:.5f}, {:.5f}) at detector with ID {}'.
-      format(qsample.X(), qsample.Y(), qsample.Z(), peak.getDetectorID()))
+      format(qsample.X(), qsample.Y(), qsample.Z(), peak['DetID']))
     
 .. testcleanup::  ExConvertHB3AToMDVirtualInstrument
 
@@ -214,10 +214,10 @@ Output:
   print('Output MDEventWorkspace has {} events.'.format(mdws.getNEvents()))
   peakws = mtd['PeakTable']
   print('There are {} peaks found in output MDWorkspace'.format(peakws.getNumberPeaks()))
-  peak = peakws.getPeak(0)
-  qsample = peak.getQSampleFrame()
+  peak = peakws.row(0)
+  qsample = peak['QSample']
   print('In Q-sample frame, center of peak 0 is at ({:.5f}, {:.5f}, {:.5f}) at detector with ID {}'.
-      format(qsample.X(), qsample.Y(), qsample.Z(), peak.getDetectorID()))
+      format(qsample.X(), qsample.Y(), qsample.Z(), peak['DetID']))
     
 .. testcleanup::  ExConvertHB3AToMDCopyInstrument
 
diff --git a/docs/source/algorithms/CreatePeaksWorkspace-v1.rst b/docs/source/algorithms/CreatePeaksWorkspace-v1.rst
index 396077244f70f993ad73995ee641f0abff47c048..a78790ddbd5cc5239e44ce62d5d8c0bac88fc554 100644
--- a/docs/source/algorithms/CreatePeaksWorkspace-v1.rst
+++ b/docs/source/algorithms/CreatePeaksWorkspace-v1.rst
@@ -20,21 +20,25 @@ for example.
 If the input workspace is a MDWorkspace then the instrument from the
 first experiment info is used.
 
+If the `InstrumentWorkspace` is not provided then a
+:ref:`LeanElasticPeaksWorkspace <LeanElasticPeaksWorkspace>` is
+created instead of a :ref:`PeaksWorkspace <PeaksWorkspace>`
+
 Usage
 -----
 
-**Example: An empty table, not tied to an instrument**
+**Example: Create an empty LeanElasticPeaksWorkspace, not tied to an instrument**
 
 .. testcode:: ExEmptyTable
 
-    ws = CreatePeaksWorkspace()
+    ws = CreatePeaksWorkspace(NumberOfPeaks=0)
     print("Created a {} with {} rows".format(ws.id(), ws.rowCount()))
 
 Output:
 
 .. testoutput:: ExEmptyTable
 
-    Created a PeaksWorkspace with 0 rows
+    Created a LeanElasticPeaksWorkspace with 0 rows
 
 **Example: With a few peaks in place**
 
diff --git a/docs/source/algorithms/LoadILLTOF-v2.rst b/docs/source/algorithms/LoadILLTOF-v2.rst
index eec9d1bcc2eef4b97797cd7b518ff703231c7edb..7a610e6b138f36099f82e6d3c28ae84b0cb6bdea 100644
--- a/docs/source/algorithms/LoadILLTOF-v2.rst
+++ b/docs/source/algorithms/LoadILLTOF-v2.rst
@@ -12,7 +12,7 @@ Description
 Loads an ILL TOF NeXus file into a :ref:`Workspace2D <Workspace2D>` with
 the given name.
 
-To date this algorithm only supports: IN4, IN5, IN6 and PANTHER.
+To date this algorithm only supports: IN4, IN5, IN6, PANTHER, and SHARP.
 
 By default, this algorithm loads the data indexed by channels. To convert to time-of-flight, use the ConvertToTOF option.
 
diff --git a/docs/source/algorithms/PaalmanPingsMonteCarloAbsorption-v1.rst b/docs/source/algorithms/PaalmanPingsMonteCarloAbsorption-v1.rst
index 17035e158e6b278ea701f9548dff1260013630fb..fe49f14dcd1509e2b43ee1bbd9886144a490b958 100644
--- a/docs/source/algorithms/PaalmanPingsMonteCarloAbsorption-v1.rst
+++ b/docs/source/algorithms/PaalmanPingsMonteCarloAbsorption-v1.rst
@@ -222,10 +222,6 @@ Usage
 
 **Example - Preset Shape**
 
-.. testsetup:: Preset
-   # Currently an issue with multithreading in this test 
-   FrameworkManager.Instance().setNumOMPThreads(1)
-
 .. testcode:: Preset
 
     sample_ws = CreateSampleWorkspace(Function="Quasielastic",
@@ -243,7 +239,8 @@ Usage
     SetSample(sample_ws, Geometry={'Shape': 'Cylinder', 'Height': 4.0, 'Radius': 2.0, 'Center': [0.,0.,0.]},
                 Material={'ChemicalFormula': 'Ni', 'MassDensity': 7.0},
                 ContainerGeometry={'Shape': 'HollowCylinder', 'Height': 4.0, 'InnerRadius': 2.0,
-                'OuterRadius': 3.5})
+                'OuterRadius': 3.5},
+                ContainerMaterial={'ChemicalFormula': 'V'})
 
     corrections = PaalmanPingsMonteCarloAbsorption(
             InputWorkspace=sample_ws,
@@ -275,7 +272,6 @@ Usage
     print("Y-Unit Label of " + str(acc_ws.getName()) + ": " + str(acc_ws.YUnitLabel()))
 
 .. testcleanup:: Preset
-    FrameworkManager.Instance().setNumOMPThreadsToConfigValue()
     mtd.clear()
 
 .. testoutput:: Preset
diff --git a/docs/source/algorithms/SANSILLAutoProcess-v1.rst b/docs/source/algorithms/SANSILLAutoProcess-v1.rst
index 81525b4e52fc3d75b258fab5d11a9b2acb405eab..435321915fc6af87f1a8e4f2d8bbccfc0531caf3 100644
--- a/docs/source/algorithms/SANSILLAutoProcess-v1.rst
+++ b/docs/source/algorithms/SANSILLAutoProcess-v1.rst
@@ -63,9 +63,12 @@ When multiple runs are summed, the run number of the first run is attributed to
             OutputWorkspace='iq_s' + str(i + 1)
         )
 
-    print('Distance 1 Q-range:{0:4f}-{1:4f} AA'.format(mtd['iq_s1_1'].readX(0)[0], mtd['iq_s1_1'].readX(0)[-1]))
-    print('Distance 2 Q-range:{0:4f}-{1:4f} AA'.format(mtd['iq_s1_2'].readX(0)[0], mtd['iq_s1_2'].readX(0)[-1]))
-    print('Distance 3 Q-range:{0:4f}-{1:4f} AA'.format(mtd['iq_s1_3'].readX(0)[0], mtd['iq_s1_3'].readX(0)[-1]))
+    print('Distance 1 Q-range:{0:4f}-{1:4f} AA'.format(mtd['iq_s1_1_d39.0m_c40.5m_w5.6A'].readX(0)[0],
+                                                       mtd['iq_s1_1_d39.0m_c40.5m_w5.6A'].readX(0)[-1]))
+    print('Distance 2 Q-range:{0:4f}-{1:4f} AA'.format(mtd['iq_s1_2_d8.0m_c8.0m_w5.6A'].readX(0)[0],
+                                                       mtd['iq_s1_2_d8.0m_c8.0m_w5.6A'].readX(0)[-1]))
+    print('Distance 3 Q-range:{0:4f}-{1:4f} AA'.format(mtd['iq_s1_3_d2.0m_c5.5m_w5.6A'].readX(0)[0],
+                                                       mtd['iq_s1_3_d2.0m_c5.5m_w5.6A'].readX(0)[-1]))
 
 Output:
 
diff --git a/docs/source/concepts/KafkaLiveStreams.rst b/docs/source/concepts/KafkaLiveStreams.rst
index 521b9be2654a6fe37dda5d2aeb22f23a2b3f7491..ecf3aa0db8f4ba3b8daae0cd521ee37950d4ecf3 100644
--- a/docs/source/concepts/KafkaLiveStreams.rst
+++ b/docs/source/concepts/KafkaLiveStreams.rst
@@ -43,5 +43,66 @@ This following python script shows how this listener is used:
       Listener='KafkaHistoListener', Address='kafkabroker:address', AccumulationMethod='Replace',
       RunTransitionBehavior='Stop', OutputWorkspace='testout')
 
+Topic Configuration
+###################
+
+The topics that the listener will subscribe to are defined for a particular instrument in Mantid's "Facilities.xml" file. Here is the entry for the V20 beamline for example:
+
+.. code-block:: xml
+
+    <instrument name="V20" shortname="V20" >
+      <technique>ESS Test Beamline</technique>
+      <livedata default="event">
+        <connection name="event" address="192.168.1.80:9092" listener="KafkaEventListener" />
+     <topic name="V20_choppers" type="chopper" />
+     <topic name="V20_motion" type="sample" />
+     <topic name="V20_runInfo" type="run" />
+     <topic name="denex_detector" type="event" />
+     <topic name="monitor" type="monitor" />
+      </livedata>
+    </instrument>
+
+Data in the Kafka topics is serialised using Google FlatBuffers, according to schema which can be found in the `ESS Streaming-Data-Types repository <https://github.com/ess-dmsc/streaming-data-types>`.
+The FlatBuffer Compiler tool generates C++ code for each schema file to provide an implementation for serialising and deserialising data to the format it is communicated in, over the network, through Kafka.
+These generated C++ files are included in the Mantid source.
+Each schema is identified by a four character string, for example "ev42" which identifies the schema defining the serialised data format for detection event data.
+The schema identifiers are defined in their corresponding schema file, and are included in the schema file name and the generated C++ code filenames.
+Particular serialised data are expected to be found in different topics on Kafka. The schema identifier, or identifiers, for data in each topic "type" are documented in the table below.
+
+.. list-table:: Topic configuration
+   :widths: 15 15 30 30
+   :header-rows: 1
+
+   * - Type
+     - Schema (see https://github.com/ess-dmsc/streaming-data-types)
+     - Required
+     - Description
+   * - chopper
+     - tdct
+     - No (topic doesn't have to exist)
+     - Neutron chopper top-dead-centre timestamps
+   * - sample
+     - f142
+     - Yes (but topic can be empty)
+     - Used to populate workspace logs. "sample" from "sample environment" which is the typical source of these data.
+   * - run
+     - pl72, 6s4t
+     - Yes (there must be a pl72 run start message on the topic for the listener to start successfully)
+     - Row 1, column 3
+   * - event
+     - ev42
+     - Yes (but topic can be empty)
+     - Detection event data
+   * - monitor
+     - ev42
+     - Yes (but topic can be empty)
+     - Detection event data from monitors (single pixel detectors). This just allows using a separate topic for these data, alternatively they can be published to the "event" topic with other data from other detectors.
+
+Note, there must be a run start message (schema pl72) available in the "run" topic for the listener to start.
+If the "nexus_structure" field of this message contains geometry information in NeXus format (NXoff_geometry or NXcylindrical_geometry) then Mantid will parse this to get the instrument geometry and expected detector ids etc.
+Otherwise it uses the "instrument_name" to look up a Mantid Instrument Definition File (IDF) for the instrument. This behaviour is consistent with the :ref:`LoadInstrument <algm-LoadInstrument>` algorithm.
+Comments in the pl72 schema file may be useful, in particular it documents which fields need to be populated to use the Mantid streamer and which are required by other software:
+https://github.com/ess-dmsc/streaming-data-types/blob/master/schemas/pl72_run_start.fbs
+
 
 .. categories:: Concepts
diff --git a/docs/source/concepts/LeanElasticPeaksWorkspace.rst b/docs/source/concepts/LeanElasticPeaksWorkspace.rst
new file mode 100644
index 0000000000000000000000000000000000000000..70a8894e138236d949e92b8152354eb5f3948e86
--- /dev/null
+++ b/docs/source/concepts/LeanElasticPeaksWorkspace.rst
@@ -0,0 +1,96 @@
+.. _LeanElasticPeaksWorkspace:
+
+LeanElasticPeaks Workspace
+==========================
+
+The LeanElasticPeaksWorkspace is a special Workspace that holds a list
+of single crystal LeanElasticPeak objects. It is the equivalent to the
+:ref:`PeaksWorkspace <PeaksWorkspace>` for Peak objects.
+
+Creating a LeanElasticPeaksWorkspace
+------------------------------------
+
+* :ref:`CreatePeaksWorkspace <algm-CreatePeaksWorkspace>` will create an empty LeanElasticPeaksWorkspace that you can then edit.
+
+Viewing a LeanElasticPeaksWorkspace
+-----------------------------------
+
+* Double-click a LeanElasticPeaksWorkspace to see the full list of data of each Peak object.
+* The LeanElasticPeaksWorkspace can be overlay onto of data using the Mantid Workbench Sliceviewer
+
+The LeanElasticPeak Object
+--------------------------
+
+Each peak object contains several pieces of information. Not all of them are necessary:
+
+* Q position (in q-sample frame)
+* H K L indices (optional)
+* Goniometer rotation matrix (for finding Q in the lab frame)
+* Wavelength
+* Integrated intensity and error (optional)
+* An integration shape (see below)
+
+The LeanElasticPeak Shape
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is the same as the Peak object, see :ref:`PeaksWorkspace #The Peak Shape
+<the-peak-shape>`.
+
+Using LeanElasticPeaksWorkspaces in Python
+------------------------------------------
+
+The LeanElasticPeaksWorkspace and LeanElasticPeak objects are exposed to python.
+
+LeanElasticPeaksWorkspace Python Interface
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See :class:`IPeaksWorkspace <mantid.api.IPeaksWorkspace>` for
+complete API.
+
+.. code-block:: python
+
+    pws = mtd['name_of_peaks_workspace']
+    pws.getNumberPeaks()
+    p = pws.getPeak(12)
+    pws.removePeak(34)
+
+LeanElasticPeak Python Interface
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See :class:`IPeak <mantid.api.IPeak>` for complete API.
+
+You can get a handle to an existing peak with:
+
+.. code-block:: python
+
+    p = pws.getPeak(12)
+
+Or you can create a new peak in this way:
+
+.. code-block:: python
+
+    qsample = V3D(1.23, 3.45, 2.22) # Q in the lab frame of the peak
+    p = pws.createPeak(qsample)
+    # The peak can later be added to the workspace
+    pws.addPeak(p)
+
+Once you have a handle on a peak "p" you have several methods to query/modify its values:
+
+.. code-block:: python
+
+    hkl = p.getHKL()
+    p.setHKL(-5, 4, 3)
+
+    q = p.getQSampleFrame()
+    q = p.getQLabFrame()
+
+    p.setIntensity(1000.0)
+    p.setSigmaIntensity(31.6)
+    counts = p.getIntensity()
+
+    wl = p.getWavelength()
+    d = p.getDSpacing()
+    shape = p.getPeakShape()
+
+
+.. categories:: Concepts
diff --git a/docs/source/concepts/PeaksWorkspace.rst b/docs/source/concepts/PeaksWorkspace.rst
index 0f85bd5ea75a899d2af297b974bd1009de78d9e3..1683ad32db0d5222e1453fa7abcf3466d391bc3c 100644
--- a/docs/source/concepts/PeaksWorkspace.rst
+++ b/docs/source/concepts/PeaksWorkspace.rst
@@ -35,6 +35,8 @@ Each peak object contains several pieces of information. Not all of them are nec
 * Row/column of detector (only for :ref:`RectangularDetectors <RectangularDetector>` )
 * An integration shape (see below)
 
+.. _the-peak-shape:
+
 The Peak Shape
 ~~~~~~~~~~~~~~~
 
diff --git a/docs/source/images/SettingsGeneral.png b/docs/source/images/SettingsGeneral.png
index 77e7d18131cc01021cdfb1410c327aa2f24dad39..4d37d56188673f4915904c5754516231319750a2 100644
Binary files a/docs/source/images/SettingsGeneral.png and b/docs/source/images/SettingsGeneral.png differ
diff --git a/docs/source/images/window_ontop_linux.png b/docs/source/images/window_ontop_linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..78aa8b5ef99d393997a0dccee6a504b5b937f47c
Binary files /dev/null and b/docs/source/images/window_ontop_linux.png differ
diff --git a/docs/source/release/v6.1.0/diffraction.rst b/docs/source/release/v6.1.0/diffraction.rst
index 646ccfd844f95444b8879ac46b31f29fa392ecc4..770efcdba48d306c504a84d676949c8cd60a40ac 100644
--- a/docs/source/release/v6.1.0/diffraction.rst
+++ b/docs/source/release/v6.1.0/diffraction.rst
@@ -28,6 +28,7 @@ Improvements
 Bugfixes
 ########
 
+- Fix the format inconsistency (with data saved from autoreduction workflow) issue for saving GSAS data using :ref:`HB2AReduce <algm-HB2AReduce>` - both are now using :ref:`SaveGSSCW <algm-SaveGSSCW>` for saving GSAS data.
 - Fix out-of-range bug in :ref:`FitPeaks <algm-FitPeaks>` for histogram data.
 - Fix bug in :ref:`FitPeaks <algm-FitPeaks>` not correctly checking right window for an individual peak
 - Fix bug to actually implement intended sequential fit of DIFC, DIFA, TZERO in :ref:`PDCalibration <algm-PDCalibration>`.
@@ -43,16 +44,18 @@ Bugfixes
 - Improve algorithm :ref:`FitPeaks <algm-FitPeaks>` to enable it to fit with multiple peaks in same spectrum with Back-to-back Exponential function starting from user specified parameters.
 - :ref:`SNSPowderReduction <algm-SNSPowderReduction>` has additional property, ``DeltaRagged``, which allows using :ref:`RebinRagged <algm-RebinRagged>` to bin each spectrum differently.
 - Allow a different number of spectra for absorption correction division of PEARL data. This allows ``create_vanadium`` to work for a non-standard dataset.
-
+- Saved filenames for summed empty workspaces now include spline properties to avoid long_mode confusion when focussing.
 
 Engineering Diffraction
 -----------------------
+
 - New IDF for upgraded VULCAN instrument
 
 Improvements
 ############
 
 - BackToBackExponential fitting parameters read from .xml file and output to .prm file for GSAS-II.
+- The Engineering Diffraction interface can now be saved as part of a project file, and can save/restore in the event of a crash as part of the general project save system.
 
 Single Crystal Diffraction
 --------------------------
@@ -83,4 +86,32 @@ Bugfixes
 ########
 - :ref:`SCDCalibratePanels <algm-SCDCalibratePanels-v2>` no longer returns null calibration outputs.
 
+LeanElasticPeak
+###############
+
+A new Peak concept has been create, a LeanElasticPeak where the
+instrument is not included as part of Peak. The only requirement for
+this peak is a Q-sample vector. There are a number of modifications
+made to facilitate this.
+
+- New LeanElasticPeak and LeanElasticPeakWorkspace has been created :ref:`LeanElasticPeaksWorkspace <LeanElasticPeaksWorkspace>`
+- :ref:`CreatePeaksWorkspace <algm-CreatePeaksWorkspace>` has been modified to optionally create a  :ref:`LeanElasticPeaksWorkspace <LeanElasticPeaksWorkspace>`.
+- These following other algorithms have either been made to work or confirmed to already work with the LeanElasticPeak:
+
+   - :ref:`algm-AddPeakHKL`
+   - :ref:`algm-CalculatePeaksHKL`
+   - :ref:`algm-CentroidPeaksMD`
+   - :ref:`algm-CombinePeaksWorkspaces`
+   - :ref:`algm-FilterPeaks`
+   - :ref:`algm-FindUBUsingFFT`
+   - :ref:`algm-FindUBUsingIndexedPeaks`
+   - :ref:`algm-FindUBUsingLatticeParameters`
+   - :ref:`algm-FindUBUsingMinMaxD`
+   - :ref:`algm-IndexPeaks`
+   - :ref:`algm-IntegratePeaksMD`
+   - :ref:`algm-SelectCellOfType`
+   - :ref:`algm-SelectCellWithForm`
+   - :ref:`algm-ShowPossibleCells`
+   - :ref:`algm-TransformHKL`
+
 :ref:`Release 6.1.0 <v6.1.0>`
diff --git a/docs/source/release/v6.1.0/direct_geometry.rst b/docs/source/release/v6.1.0/direct_geometry.rst
index 7eb11fc6637051de15ed47940361b658791d3167..7c915c65894ed81e47541b0197d5a10a376ecc17 100644
--- a/docs/source/release/v6.1.0/direct_geometry.rst
+++ b/docs/source/release/v6.1.0/direct_geometry.rst
@@ -9,9 +9,11 @@ Direct Geometry Changes
     putting new features at the top of the section, followed by
     improvements, followed by bug fixes.
   
-
+New
+###
 
 * Updated instrument geometry for CHESS
+* Loading of SHARP TOF and single-channel data has been added to :ref:`LoadILLTOF <algm-LoadILLTOF-v2>`
 
 MSlice
 ------
diff --git a/docs/source/release/v6.1.0/framework.rst b/docs/source/release/v6.1.0/framework.rst
index 2de551c23332ef28b15041af129014fc8764a364..da18ca50e88ce120c51db195caf92692d149a60a 100644
--- a/docs/source/release/v6.1.0/framework.rst
+++ b/docs/source/release/v6.1.0/framework.rst
@@ -58,5 +58,6 @@ Bugfixes
 ########
 
 - Fix problem with dictionary parameters on :ref:`SetSample <algm-SetSample>` algorithm when running from the algorithm dialog
+- Fix segmentation fault when running :ref:`MonteCarloAbsorption <algm-MonteCarloAbsorption>` algorithm on Ubuntu without a material defined on one of the sample\environment shapes
 
 :ref:`Release 6.1.0 <v6.1.0>`
diff --git a/docs/source/release/v6.1.0/mantidworkbench.rst b/docs/source/release/v6.1.0/mantidworkbench.rst
index 0a2bb69aeeae22372ca30d4d01375fb30e58f734..48012792269b866fb0825dd9a04cda8a50e440e8 100644
--- a/docs/source/release/v6.1.0/mantidworkbench.rst
+++ b/docs/source/release/v6.1.0/mantidworkbench.rst
@@ -7,6 +7,7 @@ Mantid Workbench Changes
 
 New and Improved
 ----------------
+Added Floating/On Top setting for all the windows that are opened by workbench (plots, interfaces, etc.)
 
 - New plot interactions: Double click a legend to hide it, double click a curve to open it in the plot config dialog.
 - It is now possible to overplot bin data from the matrix workspace view.
@@ -21,6 +22,8 @@ New and Improved
 - Added an algorithm ProfileChiSquared1D to profile chi squared after a fit. This can be used
   to find better estimates of parameter errors.
 
+- Instrument view: when in tube selection mode, the sum of pixel counts is now output to the selection pane.
+  
 Bugfixes
 --------
 
@@ -29,6 +32,7 @@ Bugfixes
 - For the elliptical shell of integrated peaks, the background is correct when plotting with varying background thicknesses
 - Fixed a bug which occurred when switching to a log scale in sliceviewer with negative data.
 - Fixed a bug that use wrong help links in certain interfaces
+- Fixed a bug that would not let the user input the bounding box of a shape in the instrument viewer.
 
 - If the facility in Mantid.user.properties is empty, it is consistently reflected as empty in the GUI
 - First time dialog box will not appear recurrently, if user selected their choice of facility
diff --git a/docs/source/release/v6.1.0/muon.rst b/docs/source/release/v6.1.0/muon.rst
index bc63ede40beaba7a0b299ba410d8c1afe235357a..59d85e5276fac57444dcc0045b46f1c80d6924f2 100644
--- a/docs/source/release/v6.1.0/muon.rst
+++ b/docs/source/release/v6.1.0/muon.rst
@@ -23,6 +23,7 @@ Bug fixes
 #########
 - Fixed a bug where removing a pair in use would cause a crash.
 - Fixed a bug where an error message would appear in workbench after loading a run in both MA and FDA.
+- Fixed a bug where rows in the difference table were not being highlighted correctly. 
 
 ALC
 ---
diff --git a/docs/source/release/v6.1.0/sans.rst b/docs/source/release/v6.1.0/sans.rst
index 884b23aa90c5c08f8ff4c3ce5f6b4e9e6427a19e..68aee51c81cc05b593e380057bb9e685653cf4bd 100644
--- a/docs/source/release/v6.1.0/sans.rst
+++ b/docs/source/release/v6.1.0/sans.rst
@@ -14,4 +14,9 @@ Bugs fixes
 
 - Fix a bug that made it impossible to process flux in SANSILLAutoprocess.
 
-:ref:`Release 6.1.0 <v6.1.0>`
\ No newline at end of file
+Improvements
+------------
+
+- With SANSILLAutoProcess, the detector distance, the collimation position and the wavelength are appended to the names of the output workspaces (values are taken from the sample logs).
+
+:ref:`Release 6.1.0 <v6.1.0>`
diff --git a/docs/source/techniques/ISISPowder-Pearl-v1.rst b/docs/source/techniques/ISISPowder-Pearl-v1.rst
index e47dd88737435940df82d019199467145ec5ec8e..e0b8fd78eea4fc2539eed6cf913e9a2d69899952 100644
--- a/docs/source/techniques/ISISPowder-Pearl-v1.rst
+++ b/docs/source/techniques/ISISPowder-Pearl-v1.rst
@@ -459,7 +459,7 @@ When *long_mode* is **False** the TOF window processed is
 between 0-20,000 μs
 
 When *long_mode* is **True** the TOF window processed is
-between 0-40,000 μs
+between 20,000-40,000 μs
 
 This also affects the :ref:`advanced_parameters_pearl_isis-powder-diffraction-ref`
 used. More detail can be found for each individual parameter
diff --git a/docs/source/workbench/settings.rst b/docs/source/workbench/settings.rst
index 0ada84b23cad100c1921fdf489f6aee3f61ea642..4299265118e9cc6de04ad07459010d1b0f14e9cb 100644
--- a/docs/source/workbench/settings.rst
+++ b/docs/source/workbench/settings.rst
@@ -25,6 +25,9 @@ General
   and projects and error notifications.
 - Project Recovery: These settings enable project recovery in case of a crash (recommended).
 - Main Font: Edit the font type and size used for all of Workbench, except for the script editor and messages box.
+- Additional Window Behavior: This setting controls the behavior of additional windows in Workbench.
+  When it is set to ontop, additional windows such as plot will be kept ontop of the workbench main window.
+  For further details of this setting, such as OS specific behavior, see :ref:`Window Behavior <WorkbenchWindowBehavior>`.
 - Layouts: You can save and load layouts from this area. Save layout will save the current
   arrangement of widgets in the main window that can be loaded using load layout.
 
diff --git a/docs/source/workbench/windowbehaviour.rst b/docs/source/workbench/windowbehaviour.rst
new file mode 100644
index 0000000000000000000000000000000000000000..977d456fe122825335da957e3de049d0c3461225
--- /dev/null
+++ b/docs/source/workbench/windowbehaviour.rst
@@ -0,0 +1,42 @@
+.. _WorkbenchWindowBehavior:
+
+=====================================
+Workbench Additional Window Behavior
+=====================================
+Within workbench, there is a setting (`Additional Window Behavior`) to control the behavior of additional windows, e.g. plots.
+This setting can take two values, floating or ontop, the effect of each can be summarised as follows.
+
+- **Ontop**: The new windows (e.g plots) will stay ontop of the workbench main window
+- **Floating**: The new windows (e.g plots) will be treated as separate windows, not tied to the main workbench window.
+  This means they can go behind the workbench mainwindow, but may be raised again using the task bar icon.
+
+OS Behavior
+***********
+
+These two settings have some subtle differences on each OS due to the window managers employed in each instance.
+Below we summarise the behavior of these settings on each OS.
+
+Linux
+~~~~~~
+
+.. |WindowOnTopLinux| image:: ../images/window_ontop_linux.png
+    :scale: 70%
+    :align: middle
+    :alt: WindowOnTopLinux
+
+On Linux, the ontop setting will cause the minimize and maximize buttons on plots to be lost, which is due to the window manager
+employed. To circumvent this issue, the :ref:`plot toolbox <WorkbenchPlotsToolbox>` can be used to hide/show individual plots.
+
+Alternatively, the user may opt for floating windows and choose which ones they would like to keep ontop using
+the setting provided by the desktop environment, allowing users to set the priority of each window, |WindowOnTopLinux|.
+
+This floating behavior of windows is identical to other software packages, such as Matlab and Spyder.
+
+Mac
+~~~~~~
+Likewise on Mac, the ontop setting will also remove the minimize button. If the user wishes to use floating windows,
+the priority of windows can be controlled through packages such as afloat (https://github.com/rwu823/afloat).
+
+Windows
+~~~~~~~
+On windows the ontop setting will keep plots ontop of the workbench window, while preserving both the minimize and maximize buttons.
diff --git a/qt/applications/workbench/workbench/app/mainwindow.py b/qt/applications/workbench/workbench/app/mainwindow.py
index 87116779593fd1eccf64ba292483b872b6adc0f0..d376a13aa49a53f71c35dcadc029b48a6a5652b8 100644
--- a/qt/applications/workbench/workbench/app/mainwindow.py
+++ b/qt/applications/workbench/workbench/app/mainwindow.py
@@ -43,6 +43,7 @@ from workbench.projectrecovery.projectrecovery import ProjectRecovery  # noqa
 from workbench.utils.recentlyclosedscriptsmenu import RecentlyClosedScriptsMenu # noqa
 from mantidqt.utils.asynchronous import BlockingAsyncTaskWithCallback  # noqa
 from mantidqt.utils.qt.qappthreadcall import QAppThreadCall  # noqa
+from workbench.config import get_window_config
 
 # -----------------------------------------------------------------------------
 # Splash screen
@@ -324,7 +325,6 @@ class MainWindow(QMainWindow):
         add_actions(self.file_menu, self.file_menu_actions)
         add_actions(self.view_menu, self.view_menu_actions)
         add_actions(self.help_menu, self.help_menu_actions)
-        self.populate_interfaces_menu()
 
     def launch_custom_python_gui(self, filename):
         self.interface_executor.execute(open(filename).read(), filename)
@@ -338,9 +338,12 @@ class MainWindow(QMainWindow):
             interface = self.interface_manager.createSubWindow(interface_name)
             interface.setObjectName(object_name)
             interface.setAttribute(Qt.WA_DeleteOnClose, True)
-            # make indirect interfaces children of workbench
+            parent, flags = get_window_config()
             if submenu == "Indirect":
+                # always make indirect interfaces children of workbench
                 interface.setParent(self, interface.windowFlags())
+            else:
+                interface.setParent(parent, flags)
             interface.show()
         else:
             if window.windowState() == Qt.WindowMinimized:
@@ -352,9 +355,8 @@ class MainWindow(QMainWindow):
         """Populate then Interfaces menu with all Python and C++ interfaces"""
         self.interfaces_menu.clear()
         interface_dir = ConfigService['mantidqt.python_interfaces_directory']
-        self.interface_list = self._discover_python_interfaces(interface_dir)
+        self.interface_list, registers_to_run = self._discover_python_interfaces(interface_dir)
         self._discover_cpp_interfaces(self.interface_list)
-
         hidden_interfaces = ConfigService['interfaces.categories.hidden'].split(';')
 
         keys = list(self.interface_list.keys())
@@ -374,6 +376,13 @@ class MainWindow(QMainWindow):
                         action = submenu.addAction(name)
                         action.triggered.connect(lambda checked_cpp, name=name, key=key: self.
                                                  launch_custom_cpp_gui(name, key))
+        # these register scripts contain code to register encoders and decoders to work with project save before the
+        # corresponding interface has been initialised. This is a temporary measure pending harmonisation of cpp/python
+        # interfaces
+        for reg_list in registers_to_run.values():
+            for register in reg_list:
+                file_path = os.path.join(interface_dir, register)
+                self.interface_executor.execute(open(file_path).read(), file_path)
 
     def redirect_python_warnings(self):
         """By default the warnings module writes warnings to sys.stderr. stderr is assumed to be
@@ -390,21 +399,27 @@ class MainWindow(QMainWindow):
     def _discover_python_interfaces(self, interface_dir):
         """Return a dictionary mapping a category to a set of named Python interfaces"""
         items = ConfigService['mantidqt.python_interfaces'].split()
-
+        try:
+            register_items = ConfigService['mantidqt.python_interfaces_io_registry'].split()
+        except KeyError:
+            register_items = []
         # detect the python interfaces
         interfaces = {}
+        registers_to_run = {}
         for item in items:
             key, scriptname = item.split('/')
+            reg_name = scriptname[:-3] + '_register.py'
+            if reg_name in register_items and os.path.exists(os.path.join(interface_dir, reg_name)):
+                registers_to_run.setdefault(key, []).append(reg_name)
             if not os.path.exists(os.path.join(interface_dir, scriptname)):
-                logger.warning('Failed to find script "{}" in "{}"'.format(
-                    scriptname, interface_dir))
+                logger.warning('Failed to find script "{}" in "{}"'.format(scriptname, interface_dir))
                 continue
             if scriptname in self.PYTHON_GUI_BLACKLIST:
                 logger.information('Not adding gui "{}"'.format(scriptname))
                 continue
             interfaces.setdefault(key, []).append(scriptname)
 
-        return interfaces
+        return interfaces, registers_to_run
 
     def _discover_cpp_interfaces(self, interfaces):
         """Return a dictionary mapping a category to a set of named C++ interfaces"""
diff --git a/qt/applications/workbench/workbench/config/__init__.py b/qt/applications/workbench/workbench/config/__init__.py
index 91d1aa6ef74493430d1f8f1e46e33cb0ded512df..02d2150ce21ac7b5dd419ec64a2a2ee3f0e7ddae 100644
--- a/qt/applications/workbench/workbench/config/__init__.py
+++ b/qt/applications/workbench/workbench/config/__init__.py
@@ -73,7 +73,7 @@ DEFAULTS = {
         'position': (10, 10),
     },
     'AdditionalWindows': {
-        'ontop': True
+        'behaviour': "On top"
     },
     'project': {
         'prompt_save_on_close': True,
@@ -94,10 +94,10 @@ def get_window_config():
     :return: A WindowConfig object describing the desired window configuration based on the current settings
     """
     try:
-        windows_on_top = CONF.get("AdditionalWindows", "ontop", type=bool)
+        windows_behaviour = CONF.get("AdditionalWindows", "behaviour", type=str)
+        windows_on_top = True if windows_behaviour == "On top" else False
     except KeyError:
         windows_on_top = False
-
     if windows_on_top:
         parent = _ADDITIONAL_MAINWINDOWS_PARENT
         flags = WINDOW_ONTOP_FLAGS
diff --git a/qt/applications/workbench/workbench/plugins/workspacewidget.py b/qt/applications/workbench/workbench/plugins/workspacewidget.py
index ccfcb2bf983863efdf92c1d27f3967afe3a32589..649dccfb7cc3bb055b35ba783dc247a50493990e 100644
--- a/qt/applications/workbench/workbench/plugins/workspacewidget.py
+++ b/qt/applications/workbench/workbench/plugins/workspacewidget.py
@@ -26,10 +26,12 @@ from mantidqt.widgets.samplematerialdialog.samplematerial_presenter import Sampl
 from mantidqt.widgets.workspacewidget.workspacetreewidget import WorkspaceTreeWidget
 from workbench.config import CONF
 from workbench.plugins.base import PluginWidget
+from workbench.config import get_window_config
 
 
 class WorkspaceWidget(PluginWidget):
     """Provides a Workspace Widget for workspace manipulation"""
+
     def __init__(self, parent):
         super(WorkspaceWidget, self).__init__(parent)
 
@@ -189,9 +191,10 @@ class WorkspaceWidget(PluginWidget):
 
         :param names: A list of workspace names
         """
+        parent, flags = get_window_config()
         for ws in self._ads.retrieveWorkspaces(names, unrollGroups=True):
             try:
-                SampleLogs(ws=ws, parent=self)
+                SampleLogs(ws=ws, parent=parent, window_flags=flags)
             except Exception as exception:
                 logger.warning("Could not open sample logs for workspace '{}'."
                                "".format(ws.name()))
@@ -203,9 +206,10 @@ class WorkspaceWidget(PluginWidget):
 
         :param names: A list of workspace names
         """
+        parent, flags = get_window_config()
         for ws in self._ads.retrieveWorkspaces(names, unrollGroups=True):
             try:
-                presenter = SliceViewer(ws=ws, parent=self, conf=CONF)
+                presenter = SliceViewer(ws=ws, conf=CONF, parent=parent, window_flags=flags)
                 presenter.view.show()
             except Exception as exception:
                 logger.warning("Could not open slice viewer for workspace '{}'."
@@ -218,10 +222,11 @@ class WorkspaceWidget(PluginWidget):
 
         :param names: A list of workspace names
         """
+        parent, flags = get_window_config()
         for ws in self._ads.retrieveWorkspaces(names, unrollGroups=True):
             if ws.getInstrument().getName():
                 try:
-                    presenter = InstrumentViewPresenter(ws, parent=self)
+                    presenter = InstrumentViewPresenter(ws, parent=parent, window_flags=flags)
                     presenter.show_view()
                 except Exception as exception:
                     logger.warning("Could not show instrument for workspace "
@@ -234,17 +239,19 @@ class WorkspaceWidget(PluginWidget):
     def _do_show_data(self, names):
         # local import to allow this module to be imported without pyplot being imported
         import matplotlib.pyplot
+        parent, flags = get_window_config()
         for ws in self._ads.retrieveWorkspaces(names, unrollGroups=True):
             try:
                 MatrixWorkspaceDisplay.supports(ws)
                 # the plot function is being injected in the presenter
                 # this is done so that the plotting library is mockable in testing
-                presenter = MatrixWorkspaceDisplay(ws, plot=plot, parent=self)
+                presenter = MatrixWorkspaceDisplay(ws, plot=plot, parent=parent, window_flags=flags)
                 presenter.show_view()
             except ValueError:
                 try:
                     TableWorkspaceDisplay.supports(ws)
-                    presenter = TableWorkspaceDisplay(ws, plot=matplotlib.pyplot, parent=self, batch=True)
+                    presenter = TableWorkspaceDisplay(ws, plot=matplotlib.pyplot, parent=parent, window_flags=flags,
+                                                      batch=True)
                     presenter.show_view()
                 except ValueError:
                     logger.error("Could not open workspace: {0} with neither "
@@ -308,7 +315,7 @@ class WorkspaceWidget(PluginWidget):
             self._do_show_data([name])
         except ValueError:
             if hasattr(ws, 'getMaxNumberBins') and ws.getMaxNumberBins() == 1:
-                # If this ws is just a single value show the data, else plot the bin
+                # If this is ws is just a single value show the data, else plot the bin
                 if hasattr(ws, 'getNumberHistograms') and ws.getNumberHistograms() == 1:
                     self._do_show_data([name])
                 else:
diff --git a/qt/applications/workbench/workbench/test/mainwindowtest.py b/qt/applications/workbench/workbench/test/mainwindowtest.py
index 975afbb3aada2c37e8b23ee8d1a03daa5a82e638..2f9b1e7fe1f4f55da2b333c09ea32bb4ce049b28 100644
--- a/qt/applications/workbench/workbench/test/mainwindowtest.py
+++ b/qt/applications/workbench/workbench/test/mainwindowtest.py
@@ -20,7 +20,7 @@ import matplotlib
 from mantidqt.utils.qt.testing import start_qapplication
 from mantid.api import FrameworkManager
 from qtpy.QtWidgets import QMessageBox, QAction, QMenu
-from workbench.utils.recentlyclosedscriptsmenu import RecentlyClosedScriptsMenu # noqa
+from workbench.utils.recentlyclosedscriptsmenu import RecentlyClosedScriptsMenu  # noqa
 from io import StringIO
 
 
@@ -51,7 +51,7 @@ class MainWindowTest(unittest.TestCase):
 
     @patch("workbench.app.mainwindow.FrameworkManager")
     @patch("workbench.app.mainwindow.QMessageBox")
-    def test_clear_all_memory_calls_frameworkmanager_when_user_presses_ok(self,mock_msg_box, mock_fm):
+    def test_clear_all_memory_calls_frameworkmanager_when_user_presses_ok(self, mock_msg_box, mock_fm):
         mock_msg_box_instance = MagicMock(spec=QMessageBox)
         mock_msg_box.return_value = mock_msg_box_instance
         mock_msg_box_instance.exec.return_value = mock_msg_box.Ok
@@ -118,9 +118,9 @@ class MainWindowTest(unittest.TestCase):
         self.main_window.editor = Mock()
         self.main_window.populate_interfaces_menu = Mock()
         expected_file_menu_items = [
-            'Open Script', 'Open Project', None, 'Save Script', 'Save Script as...', RecentlyClosedScriptsMenu,
-            'Generate Recovery Script', None, 'Save Project', 'Save Project as...', None, 'Settings', None,
-            'Manage User Directories', None, 'Script Repository', None, 'Clear All Memory', None, '&Quit'
+            'Open Script', 'Open Project', None, 'Save Script', 'Save Script as...', RecentlyClosedScriptsMenu, 'Generate Recovery Script',
+            None, 'Save Project', 'Save Project as...', None, 'Settings', None, 'Manage User Directories', None, 'Script Repository', None,
+            'Clear All Memory', None, '&Quit'
         ]
         # There are no wigets on this instance of MainWindow, so they will not appear on the view menu.
         expected_view_menu_items = ['Restore Default Layout', None]
@@ -133,7 +133,6 @@ class MainWindowTest(unittest.TestCase):
         self.main_window.create_actions()
         self.main_window.populate_menus()
 
-        self.main_window.populate_interfaces_menu.assert_called()
         actual_file_menu_items = list(map(convert_action_to_text, self.main_window.file_menu_actions))
         actual_view_menu_items = list(map(convert_action_to_text, self.main_window.view_menu_actions))
         actual_help_menu_items = list(map(convert_action_to_text, self.main_window.help_menu_actions))
@@ -155,8 +154,11 @@ class MainWindowTest(unittest.TestCase):
         }
 
         with patch('workbench.app.mainwindow.ConfigService',
-                   new={'interfaces.categories.hidden': '', 'mantidqt.python_interfaces_directory': interface_dir}):
-            self.main_window._discover_python_interfaces = Mock(return_value=example_interfaces)
+                   new={
+                       'interfaces.categories.hidden': '',
+                       'mantidqt.python_interfaces_directory': interface_dir
+                   }):
+            self.main_window._discover_python_interfaces = Mock(return_value=(example_interfaces, {}))
             self.main_window._discover_cpp_interfaces = Mock()
 
         self.main_window.create_menus()
@@ -176,33 +178,31 @@ class MainWindowTest(unittest.TestCase):
     @patch('workbench.app.mainwindow.add_actions')
     def test_that_populate_interfaces_menu_discovers_interfaces(self, _):
         interface_dir = './interfaces/'
+        interfaces = {'category': ['interface.py']}
 
-        self.main_window._discover_python_interfaces = Mock(return_value={'category': ['interface.py']})
+        self.main_window._discover_python_interfaces = Mock(return_value=(interfaces, {}))
         self.main_window._discover_cpp_interfaces = Mock()
 
         with patch('workbench.app.mainwindow.ConfigService',
-                   new={'interfaces.categories.hidden': '', 'mantidqt.python_interfaces_directory': interface_dir}):
+                   new={
+                       'interfaces.categories.hidden': '',
+                       'mantidqt.python_interfaces_directory': interface_dir
+                   }):
             self.main_window.create_menus()
             self.main_window.populate_interfaces_menu()
 
-        self.main_window._discover_python_interfaces.assert_called_with(
-            interface_dir
-        )
-        self.main_window._discover_cpp_interfaces.assert_called_with(
-            self.main_window._discover_python_interfaces.return_value
-        )
+        self.main_window._discover_python_interfaces.assert_called_with(interface_dir)
+        self.main_window._discover_cpp_interfaces.assert_called_with(interfaces)
 
     def test_that_populate_interfaces_menu_ignores_hidden_interfaces(self):
         interface_dir = './interfaces/'
-        self.main_window._discover_python_interfaces = Mock(return_value={
-            'category1': ['interface1.py'], 'category2': ['interface2.py']
-        })
+        self.main_window._discover_python_interfaces = Mock(return_value=({
+            'category1': ['interface1.py'],
+            'category2': ['interface2.py']
+        }, {}))
         self.main_window._discover_cpp_interfaces = Mock()
         self.main_window.interfaces_menu = Mock()
-        ConfigService_dict = {
-            'interfaces.categories.hidden': 'category1;category2',
-            'mantidqt.python_interfaces_directory': interface_dir
-        }
+        ConfigService_dict = {'interfaces.categories.hidden': 'category1;category2', 'mantidqt.python_interfaces_directory': interface_dir}
 
         with patch.object(self.main_window, 'interfaces_menu') as mock_interfaces_menu:
             with patch('workbench.app.mainwindow.ConfigService', new=ConfigService_dict):
@@ -247,12 +247,8 @@ class MainWindowTest(unittest.TestCase):
     @patch('workbench.app.mainwindow.ConfigService')
     @patch('workbench.app.mainwindow.QApplication')
     @patch('matplotlib.pyplot.close')
-    def test_main_window_close_behavior_correct_when_workbench_able_to_be_closed(
-            self,
-            mock_plt_close,
-            mock_QApplication,
-            mock_ConfigService
-    ):
+    def test_main_window_close_behavior_correct_when_workbench_able_to_be_closed(self, mock_plt_close, mock_QApplication,
+                                                                                 mock_ConfigService):
         mock_event = Mock()
         mock_project = Mock()
         mock_project.is_saving, mock_project.is_loading, mock_project.saved = False, False, True
@@ -288,10 +284,11 @@ class MainWindowTest(unittest.TestCase):
         self.main_window.PYTHON_GUI_BLACKLIST = []
 
         with patch('workbench.app.mainwindow.ConfigService', new={'mantidqt.python_interfaces': interfaces_str}):
-            returned_interfaces = self.main_window._discover_python_interfaces('')
+            returned_interfaces, registration_files = self.main_window._discover_python_interfaces('')
 
         expected_interfaces = {'Muon': ['Frequency_Domain_Analysis.py'], 'ILL': ['Drill.py']}
         self.assertDictEqual(expected_interfaces, returned_interfaces)
+        self.assertDictEqual({}, registration_files)
 
     @patch('workbench.app.mainwindow.logger')
     @patch('os.path.exists')
@@ -301,9 +298,10 @@ class MainWindowTest(unittest.TestCase):
         self.main_window.PYTHON_GUI_BLACKLIST = []
 
         with patch('workbench.app.mainwindow.ConfigService', new={'mantidqt.python_interfaces': interface_str}):
-            returned_interfaces = self.main_window._discover_python_interfaces('')
+            returned_interfaces, registration_files = self.main_window._discover_python_interfaces('')
 
         self.assertDictEqual({}, returned_interfaces)
+        self.assertDictEqual({}, registration_files)
         mock_logger.warning.assert_called()
 
     @patch('workbench.app.mainwindow.logger')
@@ -314,9 +312,10 @@ class MainWindowTest(unittest.TestCase):
         mock_os_path_exists.return_value = True
 
         with patch('workbench.app.mainwindow.ConfigService', new={'mantidqt.python_interfaces': interface_str}):
-            returned_interfaces = self.main_window._discover_python_interfaces('')
+            returned_interfaces, registration_files = self.main_window._discover_python_interfaces('')
 
         self.assertDictEqual({}, returned_interfaces)
+        self.assertDictEqual({}, registration_files)
         mock_logger.information.assert_called()
 
     @patch('workbench.app.mainwindow.UserSubWindowFactory')
diff --git a/qt/applications/workbench/workbench/widgets/settings/general/presenter.py b/qt/applications/workbench/workbench/widgets/settings/general/presenter.py
index 6c8cfb207f0779a322433bc0e7e2b332cdb7675e..839c66699edb6428d591428fde252689849a066b 100644
--- a/qt/applications/workbench/workbench/widgets/settings/general/presenter.py
+++ b/qt/applications/workbench/workbench/widgets/settings/general/presenter.py
@@ -32,6 +32,7 @@ class GeneralProperties(Enum):
     PROMPT_SAVE_ON_CLOSE = 'project/prompt_save_on_close'
     USE_NOTIFICATIONS = 'Notifications.Enabled'
     USER_LAYOUT = "MainWindow/user_layouts"
+    WINDOW_BEHAVIOUR = "AdditionalWindows/behaviour"
 
 
 class GeneralSettings(object):
@@ -43,6 +44,8 @@ class GeneralSettings(object):
     be handled here.
     """
 
+    WINDOW_BEHAVIOUR = ["On top", "Floating"]
+
     def __init__(self, parent, view=None, settings_presenter=None):
         self.view = view if view else GeneralSettingsView(parent, self)
         self.parent = parent
@@ -88,6 +91,12 @@ class GeneralSettings(object):
             self.view.instrument.blockSignals(False)
 
     def setup_general_group(self):
+        self.view.window_behaviour.addItems(self.WINDOW_BEHAVIOUR)
+        window_behaviour = CONF.get(GeneralProperties.WINDOW_BEHAVIOUR.value)
+        if window_behaviour in self.WINDOW_BEHAVIOUR:
+            self.view.window_behaviour.setCurrentIndex(self.view.window_behaviour.findText(window_behaviour))
+
+        self.view.window_behaviour.currentTextChanged.connect(self.action_window_behaviour_changed)
         self.view.main_font.clicked.connect(self.action_main_font_button_clicked)
 
     def action_main_font_button_clicked(self):
@@ -108,6 +117,9 @@ class GeneralSettings(object):
             if self.settings_presenter is not None:
                 self.settings_presenter.register_change_needs_restart("Main Font Selection")
 
+    def action_window_behaviour_changed(self, text):
+        CONF.set(GeneralProperties.WINDOW_BEHAVIOUR.value, text)
+
     def setup_checkbox_signals(self):
         self.view.show_invisible_workspaces.stateChanged.connect(self.action_show_invisible_workspaces)
         self.view.project_recovery_enabled.stateChanged.connect(self.action_project_recovery_enabled)
diff --git a/qt/applications/workbench/workbench/widgets/settings/general/section_general.ui b/qt/applications/workbench/workbench/widgets/settings/general/section_general.ui
index 839f98f7e5c1f7cc545cc65e12c4ec2f72cd14bc..6c33d251788e908a17098d9a2c70399a3c3b6852 100644
--- a/qt/applications/workbench/workbench/widgets/settings/general/section_general.ui
+++ b/qt/applications/workbench/workbench/widgets/settings/general/section_general.ui
@@ -33,8 +33,8 @@
        <rect>
         <x>0</x>
         <y>0</y>
-        <width>732</width>
-        <height>623</height>
+        <width>728</width>
+        <height>656</height>
        </rect>
       </property>
       <property name="sizePolicy">
@@ -399,6 +399,33 @@
               </item>
              </layout>
             </item>
+            <item>
+             <layout class="QHBoxLayout" name="horizontalLayout">
+              <item>
+               <widget class="QLabel" name="window_behaviour_label">
+                <property name="text">
+                 <string>Additional Windows Behaviour</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QComboBox" name="window_behaviour">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="maximumSize">
+                 <size>
+                  <width>202</width>
+                  <height>16777215</height>
+                 </size>
+                </property>
+               </widget>
+              </item>
+             </layout>
+            </item>
            </layout>
           </widget>
          </item>
diff --git a/qt/applications/workbench/workbench/widgets/settings/general/test/test_general_settings.py b/qt/applications/workbench/workbench/widgets/settings/general/test/test_general_settings.py
index eefb010dde5cab4d7f680474f9d7400b8cd771f0..d1979763730d6fdd6e150f47967143af88f63240 100644
--- a/qt/applications/workbench/workbench/widgets/settings/general/test/test_general_settings.py
+++ b/qt/applications/workbench/workbench/widgets/settings/general/test/test_general_settings.py
@@ -93,6 +93,8 @@ class GeneralSettingsTest(unittest.TestCase):
 
         self.assert_connected_once(presenter.view.main_font,
                                    presenter.view.main_font.clicked)
+        self.assert_connected_once(presenter.view.window_behaviour,
+                                   presenter.view.window_behaviour.currentTextChanged)
 
     @patch(CONFIG_SERVICE_CLASSPATH, new_callable=MockConfigService)
     def test_action_facility_changed(self, mock_ConfigService):
@@ -129,6 +131,19 @@ class GeneralSettingsTest(unittest.TestCase):
 
         mock_conf.set.assert_called_once_with(GeneralProperties.PROMPT_SAVE_ON_CLOSE.value, False)
 
+    @patch(WORKBENCH_CONF_CLASSPATH)
+    def test_action_window_behaviour_changed(self, mock_conf):
+        presenter = GeneralSettings(None)
+        values = presenter.WINDOW_BEHAVIOUR
+        presenter.action_window_behaviour_changed(values[0])
+
+        mock_conf.set.assert_called_once_with(GeneralProperties.WINDOW_BEHAVIOUR.value, values[0])
+        mock_conf.set.reset_mock()
+
+        presenter.action_window_behaviour_changed(values[1])
+
+        mock_conf.set.assert_called_once_with(GeneralProperties.WINDOW_BEHAVIOUR.value, values[1])
+
     @patch(WORKBENCH_CONF_CLASSPATH)
     def test_action_prompt_save_editor_modified(self, mock_CONF):
         presenter = GeneralSettings(None)
diff --git a/qt/python/mantidqt/project/encoderfactory.py b/qt/python/mantidqt/project/encoderfactory.py
index c7718d7ea3f6f32468b6049b512fc38951c80dda..d0695eeb8819d0ca3791f56f1c393c4d892ab9d3 100644
--- a/qt/python/mantidqt/project/encoderfactory.py
+++ b/qt/python/mantidqt/project/encoderfactory.py
@@ -6,6 +6,7 @@
 # SPDX - License - Identifier: GPL - 3.0 +
 #  This file is part of the mantidqt package
 #
+from mantid.kernel import logger
 from mantidqt.usersubwindowfactory import UserSubWindowFactory
 
 
@@ -17,7 +18,7 @@ def default_encoder_compatability_check(obj, encoder_cls):
 
 
 class EncoderFactory(object):
-    encoder_list = set([])
+    encoder_dict = dict()  # cls name: (encoder, compatible check)
 
     @classmethod
     def find_encoder(cls, obj):
@@ -26,7 +27,7 @@ class EncoderFactory(object):
         :param obj: The object for encoding
         :return: Encoder or None; Returns the Encoder of the obj or None.
         """
-        obj_encoders = [encoder for encoder, compatible in cls.encoder_list if compatible(obj, encoder)]
+        obj_encoders = [encoder for (encoder, compatible) in cls.encoder_dict.values() if compatible(obj, encoder)]
 
         if len(obj_encoders) > 1:
             raise RuntimeError("EncoderFactory: One or more encoder type claims to work with the passed obj: "
@@ -47,4 +48,7 @@ class EncoderFactory(object):
         """
         if compatible_check is None:
             compatible_check = default_encoder_compatability_check
-        cls.encoder_list.add((encoder, compatible_check))
+        # check that an encoder of this class is not already registered
+        if cls.__name__ in cls.encoder_dict:
+            logger.debug("Overriding existing encoder")
+        cls.encoder_dict[cls.__name__] = (encoder, compatible_check)
diff --git a/qt/python/mantidqt/project/projectsaver.py b/qt/python/mantidqt/project/projectsaver.py
index 1ddf2a8f2015b86600a6d8d2536b03d3386d3feb..ff78680c568b63c8454ee5504a177297f7d65c8a 100644
--- a/qt/python/mantidqt/project/projectsaver.py
+++ b/qt/python/mantidqt/project/projectsaver.py
@@ -82,7 +82,7 @@ class ProjectSaver(object):
                 # Catch any exception and log it
                 if isinstance(e, KeyboardInterrupt):
                     raise
-                logger.warning("Project Saver: An interface could not be saver error: " + str(e))
+                logger.warning("Project Saver: An interface could not be saved error: " + str(e))
         return interfaces
 
 
diff --git a/qt/python/mantidqt/widgets/instrumentview/CMakeLists.txt b/qt/python/mantidqt/widgets/instrumentview/CMakeLists.txt
index 28c4e3eeacec3d6ed0c8077198831a701666d48b..57dffc054d4aa87ac74ef4ca8974e557125c207a 100644
--- a/qt/python/mantidqt/widgets/instrumentview/CMakeLists.txt
+++ b/qt/python/mantidqt/widgets/instrumentview/CMakeLists.txt
@@ -12,6 +12,7 @@ if(ENABLE_WORKBENCH)
                      SIP_SRCS _instrumentview.sip
                      PYQT_VERSION 5
                      LINK_LIBS
+                       DataObjects
                        ${common_link_libs}
                        MantidQtWidgetsInstrumentViewQt5
                        MantidQtWidgetsCommonQt5
diff --git a/qt/python/mantidqt/widgets/instrumentview/presenter.py b/qt/python/mantidqt/widgets/instrumentview/presenter.py
index c4620697f8be4d37ac02ae1a2b89e238d919cbbd..6f00818d8d6edc852e4d2553c8f09e3b3c1c0b34 100644
--- a/qt/python/mantidqt/widgets/instrumentview/presenter.py
+++ b/qt/python/mantidqt/widgets/instrumentview/presenter.py
@@ -10,6 +10,8 @@
 """
 Contains the presenter for displaying the InstrumentWidget
 """
+from qtpy.QtCore import Qt
+
 from mantidqt.widgets.observers.ads_observer import WorkspaceDisplayADSObserver
 from mantidqt.widgets.observers.observing_presenter import ObservingPresenter
 from .view import InstrumentView
@@ -21,10 +23,11 @@ class InstrumentViewPresenter(ObservingPresenter):
     It has no model as its an old widget written in C++ with out MVP
     """
 
-    def __init__(self, ws, parent=None, ads_observer=None):
+    def __init__(self, ws, parent=None, window_flags=Qt.Window, ads_observer=None):
         super(InstrumentViewPresenter, self).__init__()
         self.ws_name = str(ws)
-        self.container = InstrumentView(parent, self, self.ws_name)
+
+        self.container = InstrumentView(parent, self, self.ws_name, window_flags=window_flags)
 
         if ads_observer:
             self.ads_observer = ads_observer
diff --git a/qt/python/mantidqt/widgets/instrumentview/view.py b/qt/python/mantidqt/widgets/instrumentview/view.py
index f7c19a4ed69a337a06e0d757ca5842668d374c3e..f7d630ddc5781d3ac52cb8b1096629d455848132 100644
--- a/qt/python/mantidqt/widgets/instrumentview/view.py
+++ b/qt/python/mantidqt/widgets/instrumentview/view.py
@@ -36,7 +36,7 @@ class InstrumentView(QWidget, ObservingView):
 
     close_signal = Signal()
 
-    def __init__(self, parent, presenter, name):
+    def __init__(self, parent, presenter, name, window_flags=Qt.Window):
         super(InstrumentView, self).__init__(parent)
 
         self.widget = InstrumentWidget(name)
@@ -45,8 +45,8 @@ class InstrumentView(QWidget, ObservingView):
         self.presenter = presenter
 
         self.setWindowTitle(name)
-        self.setWindowFlags(Qt.Window)
         self.setAttribute(Qt.WA_DeleteOnClose, True)
+        self.setWindowFlags(window_flags)
 
         layout = QVBoxLayout()
         layout.setContentsMargins(0, 0, 0, 0)
diff --git a/qt/python/mantidqt/widgets/samplelogs/presenter.py b/qt/python/mantidqt/widgets/samplelogs/presenter.py
index b53ef567e2789eaf395c102930ce8bd371ba87d3..0ff58cf7655efd62cf1933c6e7fca4aeb5ef7ad8 100644
--- a/qt/python/mantidqt/widgets/samplelogs/presenter.py
+++ b/qt/python/mantidqt/widgets/samplelogs/presenter.py
@@ -7,18 +7,21 @@
 #  This file is part of the mantid workbench.
 #
 #
+from qtpy.QtCore import Qt
+
 from .model import SampleLogsModel
 from .view import SampleLogsView
 
 
-class SampleLogs():
+class SampleLogs(object):
     """
     """
-    def __init__(self, ws, parent=None, model=None, view=None):
+    def __init__(self, ws, parent=None, window_flags=Qt.Window, model=None, view=None):
         # Create model and view, or accept mocked versions
         self.model = model if model else SampleLogsModel(ws)
         self.view = view if view else SampleLogsView(self,
                                                      parent,
+                                                     window_flags,
                                                      self.model.get_name(),
                                                      self.model.isMD(),
                                                      self.model.getNumExperimentInfo())
diff --git a/qt/python/mantidqt/widgets/samplelogs/view.py b/qt/python/mantidqt/widgets/samplelogs/view.py
index 6c2e77042271be6d7f2b311f4e02684e602fd056..708cff94d567cfd1f8a52c305af6d851090a2cbd 100644
--- a/qt/python/mantidqt/widgets/samplelogs/view.py
+++ b/qt/python/mantidqt/widgets/samplelogs/view.py
@@ -24,13 +24,13 @@ class SampleLogsView(QSplitter):
     This contains a table of the logs, a plot of the currently
     selected logs, and the statistics of the selected log.
     """
-    def __init__(self, presenter, parent = None, name = '', isMD=False, noExp = 0):
+    def __init__(self, presenter, parent=None, window_flags=Qt.Window, name='', isMD=False, noExp = 0):
         super(SampleLogsView, self).__init__(parent)
 
         self.presenter = presenter
 
         self.setWindowTitle("{} sample logs".format(name))
-        self.setWindowFlags(Qt.Window)
+        self.setWindowFlags(window_flags)
         self.setAttribute(Qt.WA_DeleteOnClose, True)
 
         # left hand side
diff --git a/qt/python/mantidqt/widgets/sliceviewer/presenter.py b/qt/python/mantidqt/widgets/sliceviewer/presenter.py
index 3163a56888b91ec7edcce4856fb5131da58c3cf1..ec5f47f180ad92c540c4a485348d6d67ea7f34b3 100644
--- a/qt/python/mantidqt/widgets/sliceviewer/presenter.py
+++ b/qt/python/mantidqt/widgets/sliceviewer/presenter.py
@@ -7,6 +7,8 @@
 #  This file is part of the mantid workbench.
 
 # 3rdparty imports
+from qtpy.QtCore import Qt
+
 import mantid.api
 import mantid.kernel
 import sip
@@ -25,18 +27,18 @@ from ..observers.observing_presenter import ObservingPresenter
 class SliceViewer(ObservingPresenter):
     TEMPORARY_STATUS_TIMEOUT = 2000
 
-    def __init__(self, ws, parent=None, model=None, view=None, conf=None):
+    def __init__(self, ws, parent=None, window_flags=Qt.Window, model=None, view=None, conf=None):
         """
         Create a presenter for controlling the slice display for a workspace
         :param ws: Workspace containing data to display and slice
         :param parent: An optional parent widget
+        :param window_flags: An optional set of window flags
         :param model: A model to define slicing operations. If None uses SliceViewerModel
         :param view: A view to display the operations. If None uses SliceViewerView
         """
         self._logger = mantid.kernel.Logger("SliceViewer")
         self._peaks_presenter = None
         self.model = model if model else SliceViewerModel(ws)
-        self.parent = parent
         self.conf = conf
 
         # Acts as a 'time capsule' to the properties of the model at this
@@ -49,7 +51,7 @@ class SliceViewer(ObservingPresenter):
 
         self.view = view if view else SliceViewerView(self, self.model.get_dimensions_info(),
                                                       self.model.can_normalize_workspace(), parent,
-                                                      conf)
+                                                      window_flags, conf)
         self.view.setWindowTitle(self.model.get_title())
         self.view.data_view.create_axes_orthogonal(
             redraw_on_zoom=not self.model.can_support_dynamic_rebinning())
diff --git a/qt/python/mantidqt/widgets/sliceviewer/view.py b/qt/python/mantidqt/widgets/sliceviewer/view.py
index 28186d7b6c0632b48ee4bc966a6aef1ab49c7263..70bcb4295b3af89c3f6e78ecb7affb70bcc2c565 100644
--- a/qt/python/mantidqt/widgets/sliceviewer/view.py
+++ b/qt/python/mantidqt/widgets/sliceviewer/view.py
@@ -538,12 +538,12 @@ class SliceViewerView(QWidget, ObservingView):
     close_signal = Signal()
     rename_signal = Signal(str)
 
-    def __init__(self, presenter, dims_info, can_normalise, parent=None, conf=None):
+    def __init__(self, presenter, dims_info, can_normalise, parent=None, window_flags=Qt.Window, conf=None):
         super().__init__(parent)
 
         self.presenter = presenter
 
-        self.setWindowFlags(Qt.Window)
+        self.setWindowFlags(window_flags)
         self.setAttribute(Qt.WA_DeleteOnClose, True)
 
         self._splitter = QSplitter(self)
diff --git a/qt/python/mantidqt/widgets/workspacedisplay/matrix/presenter.py b/qt/python/mantidqt/widgets/workspacedisplay/matrix/presenter.py
index 725faa75cee412e00aef200603c67a55f41e9dd5..605edda133601f6528dc002f74db64080c549e56 100644
--- a/qt/python/mantidqt/widgets/workspacedisplay/matrix/presenter.py
+++ b/qt/python/mantidqt/widgets/workspacedisplay/matrix/presenter.py
@@ -7,6 +7,8 @@
 #  This file is part of the mantid workbench.
 #
 #
+from qtpy.QtCore import Qt
+
 from mantid.plots.utility import MantidAxType
 from mantidqt.widgets.observers.ads_observer import WorkspaceDisplayADSObserver
 from mantidqt.widgets.workspacedisplay.data_copier import DataCopier
@@ -22,7 +24,7 @@ class MatrixWorkspaceDisplay(ObservingPresenter, DataCopier):
     A_LOT_OF_THINGS_TO_PLOT_MESSAGE = "You selected {} spectra to plot. Are you sure you want to plot that many?"
     NUM_SELECTED_FOR_CONFIRMATION = 10
 
-    def __init__(self, ws, plot=None, parent=None, model=None, view=None, ads_observer=None, container=None,
+    def __init__(self, ws, plot=None, parent=None, window_flags=Qt.Window, model=None, view=None, ads_observer=None, container=None,
                  window_width=600, window_height=400):
         """
         Creates a display for the provided workspace.
@@ -39,7 +41,7 @@ class MatrixWorkspaceDisplay(ObservingPresenter, DataCopier):
 
         # Create model and view, or accept mocked versions
         self.model = model if model else MatrixWorkspaceDisplayModel(ws)
-        self.view = view if view else MatrixWorkspaceDisplayView(self, parent)
+        self.view = view if view else MatrixWorkspaceDisplayView(self, parent, window_flags)
         self.container = container if container else StatusBarView(parent, self.view, self.model.get_name(),
                                                                    window_width=window_width,
                                                                    window_height=window_height,
diff --git a/qt/python/mantidqt/widgets/workspacedisplay/matrix/view.py b/qt/python/mantidqt/widgets/workspacedisplay/matrix/view.py
index b6f7ed6c5fa53e1b410519d17ad9a92a8f6835b6..103e44cfa6bfba32807e85b44bda16a12a5f7215 100644
--- a/qt/python/mantidqt/widgets/workspacedisplay/matrix/view.py
+++ b/qt/python/mantidqt/widgets/workspacedisplay/matrix/view.py
@@ -40,7 +40,7 @@ class MatrixWorkspaceTableView(QTableView):
 
 class MatrixWorkspaceDisplayView(QTabWidget):
 
-    def __init__(self, presenter, parent=None):
+    def __init__(self, presenter, parent=None, window_flags=Qt.Window):
         super(MatrixWorkspaceDisplayView, self).__init__(parent)
 
         self.presenter = presenter
@@ -55,6 +55,7 @@ class MatrixWorkspaceDisplayView(QTabWidget):
         self.setPalette(palette)
 
         self.setAttribute(Qt.WA_DeleteOnClose, True)
+        self.setWindowFlags(window_flags)
 
         self.active_tab_index = 0
         self.currentChanged.connect(self.set_scroll_position_on_new_focused_tab)
diff --git a/qt/python/mantidqt/widgets/workspacedisplay/table/presenter.py b/qt/python/mantidqt/widgets/workspacedisplay/table/presenter.py
index 455c5be06e8ea52314f217b586981035e5e82fd9..b1c4d21b7811e9e6a82b209bfc5cea7b8d9a2572 100644
--- a/qt/python/mantidqt/widgets/workspacedisplay/table/presenter.py
+++ b/qt/python/mantidqt/widgets/workspacedisplay/table/presenter.py
@@ -7,6 +7,8 @@
 #  This file is part of mantidqt package.
 from functools import partial
 
+from qtpy.QtCore import Qt
+
 from mantid.kernel import logger
 from mantid.plots.utility import legend_set_draggable
 from mantidqt.widgets.observers.ads_observer import WorkspaceDisplayADSObserver
@@ -47,8 +49,9 @@ class TableWorkspaceDisplay(ObservingPresenter, DataCopier):
     def __init__(
             self,
             ws,
-            plot=None,
             parent=None,
+            window_flags=Qt.Window,
+            plot=None,
             model=None,
             view=None,
             name=None,
@@ -63,6 +66,7 @@ class TableWorkspaceDisplay(ObservingPresenter, DataCopier):
 
         :param ws: Workspace to be displayed
         :param parent: Parent of the widget
+        :param window_flags: An optional set of window flags
         :param plot: Plotting function that will be used to plot workspaces. This requires Matplotlib directly.
                      Passed in as parameter to allow mocking
         :param model: Model to be used by the widget. Passed in as parameter to allow mocking
@@ -71,7 +75,7 @@ class TableWorkspaceDisplay(ObservingPresenter, DataCopier):
         :param ads_observer: ADS observer to be used by the presenter. If not provided the default
                              one is used. Mainly intended for testing.
         """
-        view, model = self.create_table(ws, parent, model, view, batch)
+        view, model = self.create_table(ws, parent, window_flags, model, view, batch)
         self.view = view
         self.model = model
         self.name = name if name else model.get_name()
@@ -96,24 +100,25 @@ class TableWorkspaceDisplay(ObservingPresenter, DataCopier):
     def show_view(self):
         self.container.show()
 
-    def create_table(self, ws, parent, model, view, batch):
+    def create_table(self, ws, parent, window_flags, model, view, batch):
         if batch:
-            view, model = self._create_table_batch(ws, parent, view, model)
+            view, model = self._create_table_batch(ws, parent, window_flags, view, model)
         else:
-            view, model = self._create_table_standard(ws, parent, view, model)
+            view, model = self._create_table_standard(ws, parent, window_flags, view, model)
         view.set_context_menu_actions(view)
         return view, model
 
-    def _create_table_standard(self, ws, parent, view, model):
+    def _create_table_standard(self, ws, parent, window_flags, view, model):
         model = model if model is not None else TableWorkspaceDisplayModel(ws)
-        view = view if view else TableWorkspaceDisplayView(presenter=self, parent=parent)
+        view = view if view else TableWorkspaceDisplayView(presenter=self, parent=parent, window_flags=window_flags)
         self.presenter = TableWorkspaceDataPresenterStandard(model, view)
         return view, model
 
-    def _create_table_batch(self, ws, parent, view, model):
+    def _create_table_batch(self, ws, parent, window_flags, view, model):
         model = model if model is not None else TableWorkspaceDisplayModel(ws)
         table_model = TableModel(parent=parent, data_model=model)
-        view = view if view else TableWorkspaceDisplayView(presenter=self, parent=parent, table_model=table_model)
+        view = view if view else TableWorkspaceDisplayView(presenter=self, parent=parent, window_flags=window_flags,
+                                                           table_model=table_model)
         self.presenter = TableWorkspaceDataPresenterBatch(model, view)
         return view, model
 
diff --git a/qt/python/mantidqt/widgets/workspacedisplay/table/view.py b/qt/python/mantidqt/widgets/workspacedisplay/table/view.py
index 66505da5de2fd840a155b6f8c56a5b31593ff792..4754af0552bbc163d8e52f176d87b5d8343815b4 100644
--- a/qt/python/mantidqt/widgets/workspacedisplay/table/view.py
+++ b/qt/python/mantidqt/widgets/workspacedisplay/table/view.py
@@ -35,7 +35,7 @@ class PreciseDoubleFactory(QItemEditorFactory):
 class TableWorkspaceDisplayView(QTableView):
     repaint_signal = Signal()
 
-    def __init__(self, presenter=None, parent=None, table_model=QStandardItemModel()):
+    def __init__(self, presenter=None, parent=None, window_flags=Qt.Window, table_model=QStandardItemModel()):
         super().__init__(parent)
         self.table_model = table_model
         self.setModel(self.table_model)
@@ -55,6 +55,8 @@ class TableWorkspaceDisplayView(QTableView):
         header = self.horizontalHeader()
         header.sectionDoubleClicked.connect(self.handle_double_click)
 
+        self.setWindowFlags(window_flags)
+
     def columnCount(self):
         return self.table_model.columnCount()
 
diff --git a/qt/scientific_interfaces/Direct/CMakeLists.txt b/qt/scientific_interfaces/Direct/CMakeLists.txt
index feeec84351e90601186ecb013ff8189f4de7f7a7..210b7ab78588fceb2765a3c583034c8fec0a9550 100644
--- a/qt/scientific_interfaces/Direct/CMakeLists.txt
+++ b/qt/scientific_interfaces/Direct/CMakeLists.txt
@@ -28,6 +28,7 @@ mtd_add_qt_library(TARGET_NAME MantidScientificInterfacesDirect
                    SYSTEM_INCLUDE_DIRS
                      ${Boost_INCLUDE_DIRS}
                    LINK_LIBS
+                     DataObjects
                      ${CORE_MANTIDLIBS}
                      ${POCO_LIBRARIES}
                      ${Boost_LIBRARIES}
@@ -60,6 +61,7 @@ mtd_add_qt_library(TARGET_NAME MantidScientificInterfacesDirect
                    DEFS
                      IN_MANTIDQT_DIRECT
                    LINK_LIBS
+                     DataObjects
                      ${CORE_MANTIDLIBS}
                      PythonInterfaceCore
                      ${POCO_LIBRARIES}
diff --git a/qt/widgets/common/src/QtPropertyBrowser/DoubleEditorFactory.cpp b/qt/widgets/common/src/QtPropertyBrowser/DoubleEditorFactory.cpp
index 6ec3028105bc7c03c53982a0378d4860de66e8b4..ee1d2a0dfd33c0c041f1ab4fb11de2829de26efa 100644
--- a/qt/widgets/common/src/QtPropertyBrowser/DoubleEditorFactory.cpp
+++ b/qt/widgets/common/src/QtPropertyBrowser/DoubleEditorFactory.cpp
@@ -27,7 +27,6 @@ DoubleEditor::DoubleEditor(QtProperty *property, QWidget *parent)
   setValidator(new QDoubleValidator(mgr->minimum(property),
                                     mgr->maximum(property), 20, this));
   connect(this, SIGNAL(editingFinished()), this, SLOT(updateProperty()));
-  // double val = mgr->value(property);
   setValue(mgr->value(property));
 }
 
@@ -36,11 +35,16 @@ DoubleEditor::~DoubleEditor() {}
 void DoubleEditor::setValue(const double &d) { setText(formatValue(d)); }
 
 void DoubleEditor::updateProperty() {
+  if (!this->isModified())
+    return;
+  // Ignore second signal if two were triggered by pressing the Enter key
+  // see https://bugreports.qt.io/browse/QTBUG-40
+  this->setModified(false);
+
   auto mgr =
       dynamic_cast<QtDoublePropertyManager *>(m_property->propertyManager());
-  if (mgr) {
+  if (mgr)
     mgr->setValue(m_property, text().toDouble());
-  }
 }
 
 /**
diff --git a/qt/widgets/instrumentview/CMakeLists.txt b/qt/widgets/instrumentview/CMakeLists.txt
index 29a6cbb62f642ec752eeb89d26c00259ca4f902d..a86695ce92c45f5d620a24622319668b101af2a4 100644
--- a/qt/widgets/instrumentview/CMakeLists.txt
+++ b/qt/widgets/instrumentview/CMakeLists.txt
@@ -164,10 +164,12 @@ mtd_add_qt_library(TARGET_NAME MantidQtWidgetsInstrumentView
                      IN_MANTIDQT_INSTRUMENTVIEW
                    INCLUDE_DIRS
                      inc
+                     ../../../Framework/DataObjects/inc
                    SYSTEM_INCLUDE_DIRS
                      ${Boost_INCLUDE_DIRS}
                    LINK_LIBS
                      ${CORE_MANTIDLIBS}
+                     DataObjects
                      ${POCO_LIBRARIES}
                      ${Boost_LIBRARIES}
                      ${PYTHON_LIBRARIES}
@@ -206,7 +208,9 @@ mtd_add_qt_library(TARGET_NAME MantidQtWidgetsInstrumentView
                      IN_MANTIDQT_INSTRUMENTVIEW
                    INCLUDE_DIRS
                      inc
+                     ../../../Framework/DataObjects/inc
                    LINK_LIBS
+                     DataObjects
                      ${CORE_MANTIDLIBS}
                      PythonInterfaceCore
                      ${POCO_LIBRARIES}
diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h
index b9f2fdb0434f7722a61e35a63c211ed4082127b4..7c6b4e346bba3677624e03c76ab30d408bcd6045 100644
--- a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h
+++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h
@@ -11,6 +11,7 @@
 #include "MantidQtWidgets/InstrumentView/MiniPlot.h"
 
 #include "MantidAPI/MatrixWorkspace_fwd.h"
+#include "MantidDataObjects/Peak.h"
 #include "MantidGeometry/Crystal/IPeak.h"
 #include "MantidGeometry/ICompAssembly.h"
 #include "MantidGeometry/IDTypes.h"
@@ -90,6 +91,7 @@ public:
   void loadSettings(const QSettings &settings) override;
   bool addToDisplayContextMenu(QMenu & /*unused*/) const override;
   void selectTool(const ToolType tool);
+  SelectionType getSelectionType() const { return m_selectionType; }
   std::shared_ptr<ProjectionSurface> getSurface() const;
   const InstrumentWidget *getInstrumentWidget() const;
   void clearWidgets();
diff --git a/qt/widgets/instrumentview/src/InstrumentWidgetMaskTab.cpp b/qt/widgets/instrumentview/src/InstrumentWidgetMaskTab.cpp
index 489603a4ae43750929dacc7b60bb60c1257a8747..5ed2b35dc2048ff9ca9d8d28b22a4d5f60b1e14b 100644
--- a/qt/widgets/instrumentview/src/InstrumentWidgetMaskTab.cpp
+++ b/qt/widgets/instrumentview/src/InstrumentWidgetMaskTab.cpp
@@ -624,10 +624,11 @@ void InstrumentWidgetMaskTab::shapeChanged() {
   m_userEditing =
       false; // this prevents resetting shape properties by doubleChanged(...)
   RectF rect = m_instrWidget->getSurface()->getCurrentBoundingRect();
-  m_doubleManager->setValue(m_left, rect.x0());
-  m_doubleManager->setValue(m_top, rect.y1());
-  m_doubleManager->setValue(m_right, rect.x1());
-  m_doubleManager->setValue(m_bottom, rect.y0());
+
+  m_doubleManager->setValue(m_left, std::min(rect.x0(), rect.x1()));
+  m_doubleManager->setValue(m_top, std::max(rect.y0(), rect.y1()));
+  m_doubleManager->setValue(m_right, std::max(rect.x0(), rect.x1()));
+  m_doubleManager->setValue(m_bottom, std::min(rect.y0(), rect.y1()));
   for (QMap<QtProperty *, QString>::iterator it = m_doublePropertyMap.begin();
        it != m_doublePropertyMap.end(); ++it) {
     m_doubleManager->setValue(
@@ -731,12 +732,21 @@ void InstrumentWidgetMaskTab::saveShapesToTable() const {
 void InstrumentWidgetMaskTab::doubleChanged(QtProperty *prop) {
   if (!m_userEditing)
     return;
+
   if (prop == m_left || prop == m_top || prop == m_right || prop == m_bottom) {
-    QRectF rect(
-        QPointF(m_doubleManager->value(m_left), m_doubleManager->value(m_top)),
-        QPointF(m_doubleManager->value(m_right),
-                m_doubleManager->value(m_bottom)));
+    m_userEditing = false;
+    double x0 = std::min(m_doubleManager->value(m_left),
+                         m_doubleManager->value(m_right));
+    double x1 = std::max(m_doubleManager->value(m_left),
+                         m_doubleManager->value(m_right));
+    double y0 = std::min(m_doubleManager->value(m_top),
+                         m_doubleManager->value(m_bottom));
+    double y1 = std::max(m_doubleManager->value(m_top),
+                         m_doubleManager->value(m_bottom));
+
+    QRectF rect(QPointF(x0, y0), QPointF(x1, y1));
     m_instrWidget->getSurface()->setCurrentBoundingRect(RectF(rect));
+
   } else {
     QString name = m_doublePropertyMap[prop];
     if (!name.isEmpty()) {
@@ -754,10 +764,9 @@ void InstrumentWidgetMaskTab::doubleChanged(QtProperty *prop) {
         m_instrWidget->getSurface()->setCurrentPoint(name, p);
       }
     }
-    // when the user validates the edit of the field, the view is immediatly
-    // updated this way
-    m_instrWidget->updateInstrumentView();
   }
+  // when the user validates the field's edit, the view is immediatly updated
+  m_instrWidget->updateInstrumentView();
   m_instrWidget->update();
 }
 
diff --git a/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp b/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp
index 4920fc3177dbbfea0db86b3bddf9b330649dd2d3..70a8c7e30b770bc686b74fd5737aca300f1821c1 100644
--- a/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp
+++ b/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp
@@ -23,6 +23,7 @@
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/Sample.h"
 #include "MantidAPI/WorkspaceFactory.h"
+#include "MantidDataObjects/Peak.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
 #include "MantidGeometry/Instrument/ComponentInfo.h"
 #include "MantidGeometry/Instrument/DetectorInfo.h"
@@ -973,7 +974,26 @@ QString ComponentInfoController::displayDetectorInfo(size_t index) {
     const QString counts = integrated == InstrumentActor::INVALID_VALUE
                                ? "N/A"
                                : QString::number(integrated);
-    text += "Counts: " + counts + '\n';
+    text += "Pixel counts: " + counts + '\n';
+
+    // Display tube counts if the tube selection tool is active.
+    if (m_tab->getSelectionType() == InstrumentWidgetPickTab::Tube) {
+      int64_t tubeCounts = 0;
+
+      auto tube = componentInfo.parent(index);
+      auto tubeDetectors = componentInfo.detectorsInSubtree(tube);
+
+      for (auto detector : tubeDetectors) {
+        if (componentInfo.isDetector(detector)) {
+          const double pixelCounts = actor.getIntegratedCounts(detector);
+          if (pixelCounts != InstrumentActor::INVALID_VALUE) {
+            tubeCounts += static_cast<int64_t>(pixelCounts);
+          }
+        }
+      }
+      text += "Tube counts: " + QString::number(tubeCounts) + '\n';
+    }
+
     // display info about peak overlays
     text += actor.getParameterInfo(index);
   }
@@ -1004,8 +1024,9 @@ QString ComponentInfoController::displayNonDetectorInfo(
 }
 
 QString
-ComponentInfoController::displayPeakInfo(Mantid::Geometry::IPeak *peak) {
+ComponentInfoController::displayPeakInfo(Mantid::Geometry::IPeak *ipeak) {
   std::stringstream text;
+  auto peak = dynamic_cast<Mantid::DataObjects::Peak *>(ipeak);
   auto instrument = peak->getInstrument();
   auto sample = instrument->getSample()->getPos();
   auto source = instrument->getSource()->getPos();
@@ -1067,7 +1088,7 @@ void ComponentInfoController::displayComparePeaksInfo(
 
 void ComponentInfoController::displayAlignPeaksInfo(
     const std::vector<Mantid::Kernel::V3D> &planePeaks,
-    const Mantid::Geometry::IPeak *peak) {
+    const Mantid::Geometry::IPeak *ipeak) {
 
   using Mantid::Kernel::V3D;
 
@@ -1079,6 +1100,7 @@ void ComponentInfoController::displayAlignPeaksInfo(
 
   // find projection of beam direction onto plane
   // this is so we always orientate to a common reference direction
+  auto peak = dynamic_cast<const Mantid::DataObjects::Peak *>(ipeak);
   const auto instrument = peak->getInstrument();
   const auto samplePos = instrument->getSample()->getPos();
   const auto sourcePos = instrument->getSource()->getPos();
diff --git a/qt/widgets/instrumentview/src/PeakMarker2D.cpp b/qt/widgets/instrumentview/src/PeakMarker2D.cpp
index a92f9a387b32b35b3558159685cf1751898087b3..3f561211a74aefb1d35aba1c5fc0cc2241521c2c 100644
--- a/qt/widgets/instrumentview/src/PeakMarker2D.cpp
+++ b/qt/widgets/instrumentview/src/PeakMarker2D.cpp
@@ -5,6 +5,7 @@
 //   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidQtWidgets/InstrumentView/PeakMarker2D.h"
+#include "MantidDataObjects/Peak.h"
 #include "MantidQtWidgets/InstrumentView/PeakOverlay.h"
 
 #include <QFontMetrics>
@@ -20,6 +21,8 @@
 namespace MantidQt {
 namespace MantidWidgets {
 
+using Mantid::DataObjects::Peak;
+using Mantid::Geometry::IPeak;
 /// Default size in screen pixels of the marker's symbol
 const int PeakMarker2D::g_defaultMarkerSize = 5;
 
@@ -122,14 +125,16 @@ void PeakMarker2D::drawSquare(QPainter &painter) const {
 /**
  * Save some peak information.
  */
-void PeakMarker2D::setPeak(const Mantid::Geometry::IPeak &peak, int row) {
-  m_h = peak.getH();
-  m_k = peak.getK();
-  m_l = peak.getL();
+void PeakMarker2D::setPeak(const IPeak &ipeak, int row) {
+  m_h = ipeak.getH();
+  m_k = ipeak.getK();
+  m_l = ipeak.getL();
   m_label = QString("%1 %2 %3")
                 .arg(QString::number(m_h, 'g', 2), QString::number(m_k, 'g', 2),
                      QString::number(m_l, 'g', 2));
-  m_detID = peak.getDetectorID();
+  auto peak = dynamic_cast<const Peak *>(&ipeak);
+  if (peak)
+    m_detID = peak->getDetectorID();
   m_row = row;
 }
 
diff --git a/scripts/CrystalTools/PeakReport.py b/scripts/CrystalTools/PeakReport.py
index 8afcda27fe78e2d2293c683bbf277ee286a19a0e..5c3fc677dd8f3b4ec2bf11abc27f80861a1b8f26 100644
--- a/scripts/CrystalTools/PeakReport.py
+++ b/scripts/CrystalTools/PeakReport.py
@@ -148,20 +148,23 @@ class PeakReport(object):
             svw.saveImage(filename)
 
             # Create an image
-            img = Image(filename, width=150, height = 100)
+            img = Image(filename, width=150, height=100)
             # Get the peak object
             peak = peaks_workspace.getPeak(i)
 
-            infoData = [['PeakNumber:', i],['Run Number:', peak.getRunNumber()], ['Intensity:', peak.getIntensity()],
+            infoData = [['PeakNumber:', i], ['Run Number:', peak.getRunNumber()], ['Intensity:', peak.getIntensity()],
                         ['TOF:', peak.getTOF()]]
-            coordData = [['Detector Id:', peak.getDetectorID()], ['Q Lab:', peak.getQLabFrame()],
-                         ['Q Sample:', peak.getQSampleFrame()], ['HKL:', peak.getHKL()]]
-            data = [[ img , Table(infoData), Table(coordData)]]
+            coordData = [['Q Lab:', peak.getQLabFrame()], ['Q Sample:', peak.getQSampleFrame()],
+                         ['HKL:', peak.getHKL()]]
+            if 'DetID' in peaks_workspace.getColumnNames():
+                detector_id = peaks_workspace.row(i)['DetID']
+                coordData = [['Detector Id:', detector_id]] + coordData
+            data = [[img , Table(infoData), Table(coordData)]]
 
             colwidths = (150, 160, 160)
 
             table = Table(data, colwidths, hAlign='LEFT')
             parts.append(table)
-            parts.append(Spacer(0,10))
+            parts.append(Spacer(0, 10))
 
         doc.build(parts)
diff --git a/scripts/DGSPlanner.py b/scripts/DGSPlanner.py
index 2151e9e2ede349da28b03ca69d182d73792e7bfc..2bc0f5bd3d4b520a91209bd3ad74680a627353d6 100644
--- a/scripts/DGSPlanner.py
+++ b/scripts/DGSPlanner.py
@@ -10,7 +10,13 @@ from DGSPlanner import DGSPlannerGUI
 from mantidqt.gui_helper import get_qapplication
 
 app, within_mantid = get_qapplication()
-planner = DGSPlannerGUI.DGSPlannerGUI()
+if 'workbench' in sys.modules:
+    from workbench.config import get_window_config
+
+    parent, flags = get_window_config()
+else:
+    parent, flags = None, None
+planner = DGSPlannerGUI.DGSPlannerGUI(parent, flags)
 planner.show()
 if not within_mantid:
     sys.exit(app.exec_())
diff --git a/scripts/DGSPlanner/DGSPlannerGUI.py b/scripts/DGSPlanner/DGSPlannerGUI.py
index 77c6f65332e96e7e7523f0d0ff30b243e5c73b6c..4d9a3494d6b262b654d58dc430ccd1c35269a4fd 100644
--- a/scripts/DGSPlanner/DGSPlannerGUI.py
+++ b/scripts/DGSPlanner/DGSPlannerGUI.py
@@ -39,9 +39,11 @@ class CustomNavigationToolbar(NavigationToolbar2QT):
 
 
 class DGSPlannerGUI(QtWidgets.QWidget):
-    def __init__(self, ol=None, parent=None):
+    def __init__(self, parent=None, window_flags=None, ol=None):
         # pylint: disable=unused-argument,super-on-old-class
         super(DGSPlannerGUI, self).__init__(parent)
+        if window_flags:
+            self.setWindowFlags(window_flags)
         # OrientedLattice
         if ValidateOL(ol):
             self.ol = ol
diff --git a/scripts/DGS_Reduction.py b/scripts/DGS_Reduction.py
index 2557ac17852d91fc1b24663f3304a7d85e8f863a..3bc6a829c2e0d079e473882df156615744483cce 100644
--- a/scripts/DGS_Reduction.py
+++ b/scripts/DGS_Reduction.py
@@ -9,8 +9,16 @@
     Script used to start the DGS reduction GUI from MantidPlot
 """
 from reduction_application import ReductionGUI
+import sys
 
-reducer = ReductionGUI(instrument_list=["ARCS", "CNCS", "HYSPEC", "MAPS",
-                                        "MARI", "MERLIN", "SEQUOIA", "TOFTOF"])
+if 'workbench' in sys.modules:
+    from workbench.config import get_window_config
+
+    parent, flags = get_window_config()
+else:
+    parent, flags = None, None
+
+reducer = ReductionGUI(parent, flags, instrument_list=["ARCS", "CNCS", "HYSPEC", "MAPS",
+                                                       "MARI", "MERLIN", "SEQUOIA", "TOFTOF"])
 if reducer.setup_layout(load_last=True):
     reducer.show()
diff --git a/scripts/Diffraction/isis_powder/pearl.py b/scripts/Diffraction/isis_powder/pearl.py
index a3b64fe51cbeee71c7a407b1e581a7b328e922b1..954b82d8fc841f2add78af8fee9ca790acb61f3d 100644
--- a/scripts/Diffraction/isis_powder/pearl.py
+++ b/scripts/Diffraction/isis_powder/pearl.py
@@ -44,6 +44,7 @@ class Pearl(AbstractInst):
     def create_vanadium(self, **kwargs):
         kwargs[
             "perform_attenuation"] = None  # Hard code this off as we do not need an attenuation file
+
         with self._apply_temporary_inst_settings(kwargs, kwargs.get("run_in_cycle")):
             if str(self._inst_settings.tt_mode).lower() == "all":
                 for new_tt_mode in ["tt35", "tt70", "tt88"]:
@@ -105,7 +106,7 @@ class Pearl(AbstractInst):
             add_spline = [self._inst_settings.tt_mode, "long"] if self._inst_settings.long_mode else \
                 [self._inst_settings.tt_mode]
 
-            self._cached_run_details[run_number_string_key].update_spline(
+            self._cached_run_details[run_number_string_key].update_file_paths(
                 self._inst_settings, add_spline)
         yield
         # reset instrument settings
@@ -115,8 +116,7 @@ class Pearl(AbstractInst):
         add_spline = [self._inst_settings.tt_mode, "long"] if self._inst_settings.long_mode else \
             [self._inst_settings.tt_mode]
 
-        self._cached_run_details[run_number_string_key].update_spline(self._inst_settings,
-                                                                      add_spline)
+        self._cached_run_details[run_number_string_key].update_file_paths(self._inst_settings, add_spline)
 
     def _run_create_vanadium(self):
         # Provides a minimal wrapper so if we have tt_mode 'all' we can loop round
diff --git a/scripts/Diffraction/isis_powder/routines/common.py b/scripts/Diffraction/isis_powder/routines/common.py
index 15ee3901dc7549a4bf6808abaef10311fce84368..7fc53657ea078706784f6a122ebce6c53ab2f7e5 100644
--- a/scripts/Diffraction/isis_powder/routines/common.py
+++ b/scripts/Diffraction/isis_powder/routines/common.py
@@ -253,7 +253,7 @@ def generate_unsplined_name(vanadium_string, *args):
     return _generate_vanadium_name(vanadium_string, False, *args)
 
 
-def generate_summed_empty_name(empty_runs_string):
+def generate_summed_empty_name(empty_runs_string, *args):
     """
     Generates a name for the summed empty instrument runs
     so that the file can be later loaded.
@@ -261,9 +261,17 @@ def generate_summed_empty_name(empty_runs_string):
     :return: The generated file name
     """
     out_name = 'summed_empty'
-
     out_name += '_' + str(empty_runs_string)
 
+    # Only arg that may be added is long_mode. tt_mode and cal file are not needed.
+    for passed_arg in args:
+        if isinstance(passed_arg, list):
+            if 'long' in passed_arg:
+                out_name += '_long'
+        else:
+            if passed_arg == 'long':
+                out_name += '_long'
+
     out_name += ".nxs"
     return out_name
 
diff --git a/scripts/Diffraction/isis_powder/routines/run_details.py b/scripts/Diffraction/isis_powder/routines/run_details.py
index 67ab9ac238c6aefb186a1f4fd394e064bcf4297d..9dfe9233001ff47284354eb5b319a9a7d0a50771 100644
--- a/scripts/Diffraction/isis_powder/routines/run_details.py
+++ b/scripts/Diffraction/isis_powder/routines/run_details.py
@@ -39,7 +39,8 @@ def create_run_details_object(run_number_string, inst_settings, is_vanadium_run,
 
     splined_van_name = common.generate_splined_name(vanadium_string, new_splined_list)
     unsplined_van_name = common.generate_unsplined_name(vanadium_string, new_splined_list)
-    summed_empty_name = common.generate_summed_empty_name(empty_run_number)
+
+    summed_empty_name = common.generate_summed_empty_name(empty_run_number, new_splined_list)
 
     if is_vanadium_run:
         # The run number should be the vanadium number in this case
@@ -122,9 +123,9 @@ class _RunDetails(object):
         self.output_suffix = output_suffix
         self.van_paths = van_paths
 
-    def update_spline(self, inst_settings, new_splined_name_list):
-        """Updates the spline path using a new splined name list, this is necessary on instruments where the spline path
-        may change e.g. Pearl due to long-mode
+    def update_file_paths(self, inst_settings, new_splined_name_list):
+        """Updates the file path for splined, unsplined and summed_empty files using a new splined name list,
+        this is necessary on instruments where the path may change e.g. Pearl due to long-mode
         :param inst_settings The current Instrument settings
         :param new_splined_name_list  List of unique properties to generate a splined vanadium name from
         """
@@ -133,9 +134,15 @@ class _RunDetails(object):
                                             cal_mapping_path=inst_settings.cal_mapping_path)
         offset_file_name = common.cal_map_dictionary_key_helper(dictionary=cal_map_dict, key="offset_file_name")
 
-        # Prepend the properties used for creating a van spline so we can fingerprint the file
+        # Prepend the properties used for creating a van spline so we can fingerprint the output files
         splined_list = new_splined_name_list if new_splined_name_list else []
         splined_list.append(os.path.basename(offset_file_name))
 
         splined_van_name = common.generate_splined_name(self.vanadium_run_numbers, splined_list)
         self.splined_vanadium_file_path = os.path.join(self.van_paths, splined_van_name)
+
+        unsplined_van_name = common.generate_unsplined_name(self.vanadium_run_numbers, splined_list)
+        self.unsplined_vanadium_file_path = os.path.join(self.van_paths, unsplined_van_name)
+
+        summed_empty_name = common.generate_summed_empty_name(self.empty_runs, splined_list)
+        self.summed_empty_file_path = os.path.join(self.van_paths, summed_empty_name)
diff --git a/scripts/Diffraction/isis_powder/test/ISISPowderRunDetailsTest.py b/scripts/Diffraction/isis_powder/test/ISISPowderRunDetailsTest.py
index f67694c792f9413a00cb4f436ba626c84c743325..0851c2c46da92fae796d56e3832bf02dad170d72 100644
--- a/scripts/Diffraction/isis_powder/test/ISISPowderRunDetailsTest.py
+++ b/scripts/Diffraction/isis_powder/test/ISISPowderRunDetailsTest.py
@@ -70,7 +70,8 @@ class ISISPowderInstrumentRunDetailsTest(unittest.TestCase):
         self.assertEqual(output_obj.vanadium_run_numbers, expected_vanadium_runs)
         self.assertEqual(output_obj.summed_empty_file_path,
                          os.path.join(mock_inst.calibration_dir, expected_label,
-                                      common.generate_summed_empty_name(expected_empty_runs)))
+                                      common.generate_summed_empty_name(expected_empty_runs,
+                                                                        expected_offset_file_name)))
 
     def test_create_run_details_object_when_van_cal(self):
         # When we are running the vanadium calibration we expected the run number to take the vanadium
diff --git a/scripts/Drill.py b/scripts/Drill.py
index 4e8e610bd6a510e64fcff576f8efe9a90679626d..45c8831ebbf25b321c003b455d5674be5eccc9bb 100644
--- a/scripts/Drill.py
+++ b/scripts/Drill.py
@@ -22,8 +22,21 @@ else:
         from Interface.ui.drill.view.DrillView import DrillView
 
         app, within_mantid = get_qapplication()
+        if 'workbench' in sys.modules:
+            from workbench.config import get_window_config
+
+            parent, flags = get_window_config()
+        else:
+            parent, flags = None, None
+
         if 'drillInterface' not in globals():
-            drillInterface = DrillView()
+            drillInterface = DrillView(parent, flags)
+        if 'drillInterface' in globals():
+            # 'unresolved reference drillInterface' if we use an else statement.
+            # This is necessary to ensure settings for 'On top'/'Floating' window behaviour are propagated to DRILL
+            # if they are changed by the user after DRILL has been opened.
+            drillInterface.setParent(parent)
+            drillInterface.setWindowFlags(flags)
         drillInterface.show()
         if not within_mantid:
             sys.exit(app.exec_())
diff --git a/scripts/Elemental_Analysis.py b/scripts/Elemental_Analysis.py
index 51c824c0f67d9894b22e67995355350a50d32fd4..900918adc3f894eb734313b62a61003fea3b3334 100644
--- a/scripts/Elemental_Analysis.py
+++ b/scripts/Elemental_Analysis.py
@@ -7,10 +7,17 @@
 from qtpy import QtCore
 from Muon.GUI.ElementalAnalysis.elemental_analysis import ElementalAnalysisGui
 from Muon.GUI.Common.usage_report import report_interface_startup
-
+import sys
 
 Name = "Elemental_Analysis"
 
+if 'workbench' in sys.modules:
+    from workbench.config import get_window_config
+
+    parent, flags = get_window_config()
+else:
+    parent, flags = None, None
+
 if 'Elemental_Analysis' in globals():
     Elemental_Analysis = globals()['Elemental_Analysis']
     if not Elemental_Analysis.isHidden():
@@ -19,12 +26,12 @@ if 'Elemental_Analysis' in globals():
             ) & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
         Elemental_Analysis.activateWindow()
     else:
-        Elemental_Analysis = ElementalAnalysisGui()
+        Elemental_Analysis = ElementalAnalysisGui(parent, flags)
         report_interface_startup(Name)
         Elemental_Analysis.resize(700, 700)
         Elemental_Analysis.show()
 else:
-    Elemental_Analysis = ElementalAnalysisGui()
+    Elemental_Analysis = ElementalAnalysisGui(parent, flags)
     report_interface_startup(Name)
     Elemental_Analysis.resize(700, 700)
     Elemental_Analysis.show()
diff --git a/scripts/Engineering/gui/CMakeLists.txt b/scripts/Engineering/gui/CMakeLists.txt
index cc1950f8dc7e216eea9197e2e82574e0a5bd8746..4ee9bb8b2a08563b4739dc8a694767b7f1300370 100644
--- a/scripts/Engineering/gui/CMakeLists.txt
+++ b/scripts/Engineering/gui/CMakeLists.txt
@@ -19,6 +19,8 @@ set(TEST_PY_FILES
     # Focus
     engineering_diffraction/tabs/focus/test/test_focus_model.py
     engineering_diffraction/tabs/focus/test/test_focus_presenter.py
+    # IO
+    engineering_diffraction/test/engineering_diffraction_io_test.py
 )
 
 check_tests_valid(${CMAKE_CURRENT_SOURCE_DIR} ${TEST_PY_FILES})
diff --git a/scripts/Engineering/gui/engineering_diffraction/engineering_diffraction.py b/scripts/Engineering/gui/engineering_diffraction/engineering_diffraction.py
index 543f2545026d40eafbb733eaf3e603fcc7a2af15..2a8ae05664c247f43e60a95b84b305f7904def1c 100644
--- a/scripts/Engineering/gui/engineering_diffraction/engineering_diffraction.py
+++ b/scripts/Engineering/gui/engineering_diffraction/engineering_diffraction.py
@@ -7,23 +7,11 @@
 # pylint: disable=invalid-name
 from qtpy import QtCore, QtWidgets
 
-from .tabs.calibration.model import CalibrationModel
-from .tabs.calibration.view import CalibrationView
-from .tabs.calibration.presenter import CalibrationPresenter
-from .tabs.common import CalibrationObserver, SavedirObserver
-from .tabs.common.path_handling import get_run_number_from_path
-from .tabs.focus.model import FocusModel
-from .tabs.focus.view import FocusView
-from .tabs.focus.presenter import FocusPresenter
-from .tabs.fitting.view import FittingView
-from .tabs.fitting.presenter import FittingPresenter
-from .settings.settings_model import SettingsModel
-from .settings.settings_view import SettingsView
-from .settings.settings_presenter import SettingsPresenter
 from mantidqt.icons import get_icon
-
-from mantidqt.interfacemanager import InterfaceManager
+from mantidqt.utils.observer_pattern import GenericObserverWithArgPassing
 from mantidqt.utils.qt import load_ui
+from Engineering.gui.engineering_diffraction.presenter import EngineeringDiffractionPresenter
+from .tabs.common import SavedirObserver
 
 Ui_main_window, _ = load_ui(__file__, "main_window.ui")
 
@@ -43,35 +31,30 @@ class EngineeringDiffractionGui(QtWidgets.QMainWindow, Ui_main_window):
 
         # Main Window
         self.setupUi(self)
-        self.doc = "Engineering Diffraction"
         self.tabs = self.tab_main
         self.setFocusPolicy(QtCore.Qt.StrongFocus)
-        self.calibration_presenter = None
-        self.focus_presenter = None
-        self.fitting_presenter = None
-        self.settings_presenter = None
-        self.calibration_observer = CalibrationObserver(self)
-        self.savedir_observer = SavedirObserver(self)
-        self.set_on_help_clicked(self.open_help_window)
 
-        self.set_on_settings_clicked(self.open_settings)
         self.btn_settings.setIcon(get_icon("mdi.settings", "black", 1.2))
 
-        # Setup Elements
-        self.setup_settings()
-        self.setup_calibration()
-        self.setup_focus()
-        self.setup_fitting()
-
-        # Setup status bar
+        # Create status bar widgets
         self.status_label = QtWidgets.QLabel()
         self.savedir_label = QtWidgets.QLabel()
         self.savedir_label.setMaximumWidth(self.status_savdirMaxwidth)
-        self.setup_statusbar()
 
-        # Setup notifiers
-        self.setup_calibration_notifier()
-        self.setup_savedir_notifier()
+        # observers
+        self.update_statusbar_text_observable = GenericObserverWithArgPassing(self.set_statusbar_text)
+        self.update_savedir_observable = GenericObserverWithArgPassing(self.update_savedir)
+        self.savedir_observer = SavedirObserver(self)
+
+        # this presenter needs to be accessible to this view so that it can be accessed by project save
+        self.presenter = self.setup_presenter()
+
+        # setup that can only happen with presenter created
+        self.setup_statusbar()
+        self.set_on_instrument_changed(self.presenter.calibration_presenter.set_instrument_override)
+        self.set_on_rb_num_changed(self.presenter.calibration_presenter.set_rb_num)
+        self.set_on_instrument_changed(self.presenter.focus_presenter.set_instrument_override)
+        self.set_on_rb_num_changed(self.presenter.focus_presenter.set_rb_num)
 
         # Usage Reporting
         try:
@@ -83,51 +66,38 @@ class EngineeringDiffractionGui(QtWidgets.QMainWindow, Ui_main_window):
         except ImportError:
             pass
 
-    def closeEvent(self, event):
-        self.fitting_presenter.data_widget.ads_observer.unsubscribe()
-        self.fitting_presenter.plot_widget.view.ensure_fit_dock_closed()
-
-    def setup_settings(self):
-        model = SettingsModel()
-        view = SettingsView(self)
-        self.settings_presenter = SettingsPresenter(model, view)
-        self.settings_presenter.load_settings_from_file_or_default()
-
-    def setup_calibration(self):
-        cal_model = CalibrationModel()
-        cal_view = CalibrationView(parent=self.tabs)
-        self.calibration_presenter = CalibrationPresenter(cal_model, cal_view)
-        self.set_on_instrument_changed(self.calibration_presenter.set_instrument_override)
-        self.set_on_rb_num_changed(self.calibration_presenter.set_rb_num)
-        self.tabs.addTab(cal_view, "Calibration")
-
-    def setup_focus(self):
-        focus_model = FocusModel()
-        focus_view = FocusView()
-        self.focus_presenter = FocusPresenter(focus_model, focus_view)
-        self.set_on_instrument_changed(self.focus_presenter.set_instrument_override)
-        self.set_on_rb_num_changed(self.focus_presenter.set_rb_num)
-        self.tabs.addTab(focus_view, "Focus")
-
-    def setup_fitting(self):
-        fitting_view = FittingView()
-        self.fitting_presenter = FittingPresenter(fitting_view)
-        self.focus_presenter.add_focus_subscriber(self.fitting_presenter.data_widget.presenter.focus_run_observer)
-        self.tabs.addTab(fitting_view, "Fitting")
-
-    def setup_calibration_notifier(self):
-        self.calibration_presenter.calibration_notifier.add_subscriber(
-            self.focus_presenter.calibration_observer)
-        self.calibration_presenter.calibration_notifier.add_subscriber(self.calibration_observer)
-
-    def setup_savedir_notifier(self):
-        self.settings_presenter.savedir_notifier.add_subscriber(self.savedir_observer)
+    def setup_presenter(self):
+        presenter = EngineeringDiffractionPresenter()
+        presenter.setup_calibration(self)
+        presenter.setup_focus(self)
+        presenter.setup_fitting(self)
+        presenter.setup_settings(self)
+        presenter.setup_calibration_notifier()
+        presenter.statusbar_observable.add_subscriber(self.update_statusbar_text_observable)
+        presenter.savedir_observable.add_subscriber(self.update_savedir_observable)
+        self.set_on_settings_clicked(presenter.open_settings)
+        self.set_on_help_clicked(presenter.open_help_window)
+        return presenter
 
     def setup_statusbar(self):
         self.statusbar.addWidget(self.status_label)
         self.set_statusbar_text("No Calibration Loaded.")
         self.statusbar.addWidget(self.savedir_label)
-        self.update_savedir(self.settings_presenter.settings["save_location"])
+        self.update_savedir(self.presenter.settings_presenter.settings["save_location"])
+
+    def set_statusbar_text(self, text):
+        self.status_label.setText(text)
+
+    def update_savedir(self, savedir):
+        savedir_text = "SaveDir: " + savedir
+        self.savedir_label.setToolTip(savedir_text)
+        self.savedir_label.setText(savedir_text)
+
+    def closeEvent(self, _):
+        self.presenter.handle_close()
+
+    def get_rb_no(self):
+        return self.lineEdit_RBNumber.text()
 
     def set_on_help_clicked(self, slot):
         self.pushButton_help.clicked.connect(slot)
@@ -140,26 +110,3 @@ class EngineeringDiffractionGui(QtWidgets.QMainWindow, Ui_main_window):
 
     def set_on_instrument_changed(self, slot):
         self.comboBox_instrument.currentIndexChanged.connect(slot)
-
-    def open_help_window(self):
-        InterfaceManager().showCustomInterfaceHelp(self.doc,'diffraction')
-
-    def open_settings(self):
-        self.settings_presenter.show()
-
-    def get_rb_no(self):
-        return self.lineEdit_RBNumber.text()
-
-    def update_calibration(self, calibration):
-        instrument = calibration.get_instrument()
-        van_no = get_run_number_from_path(calibration.get_vanadium(), instrument)
-        sample_no = get_run_number_from_path(calibration.get_sample(), instrument)
-        self.set_statusbar_text(f"V: {van_no}, CeO2: {sample_no}, Instrument: {instrument}")
-
-    def set_statusbar_text(self, text):
-        self.status_label.setText(text)
-
-    def update_savedir(self, savedir):
-        savedir_text = "SaveDir: " + savedir
-        self.savedir_label.setToolTip(savedir_text)
-        self.savedir_label.setText(savedir_text)
diff --git a/scripts/Engineering/gui/engineering_diffraction/engineering_diffraction_io.py b/scripts/Engineering/gui/engineering_diffraction/engineering_diffraction_io.py
new file mode 100644
index 0000000000000000000000000000000000000000..d08227fc128c95fc726fc0de75915deb9c6be6ae
--- /dev/null
+++ b/scripts/Engineering/gui/engineering_diffraction/engineering_diffraction_io.py
@@ -0,0 +1,93 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+#   NScD Oak Ridge National Laboratory, European Spallation Source,
+#   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+# SPDX - License - Identifier: GPL - 3.0 +
+from mantid.api import AnalysisDataService as ADS  # noqa
+from mantid.simpleapi import logger
+from Engineering.gui.engineering_diffraction.engineering_diffraction import EngineeringDiffractionGui
+
+IO_VERSION = 1
+
+SETTINGS_KEYS_TYPES = {"save_location": str, "full_calibration": str, "recalc_vanadium": bool, "logs": str,
+                       "primary_log": str, "sort_ascending": bool}
+
+
+class EngineeringDiffractionUIAttributes(object):
+    # WARNING: If you delete a tag from here instead of adding a new one, it will make old project files obsolete so
+    # just add an extra tag to the list e.g. ["InstrumentWidget", "IWidget"]
+    _tags = ["EngineeringDiffractionGui"]
+
+
+class EngineeringDiffractionEncoder(EngineeringDiffractionUIAttributes):
+    def __init__(self):
+        super(EngineeringDiffractionEncoder, self).__init__()
+
+    def encode(self, obj, _=None):  # what obj = EngineeringDiffractionGui
+        presenter = obj.presenter
+        data_widget = presenter.fitting_presenter.data_widget  # data widget
+        plot_widget = presenter.fitting_presenter.plot_widget  # plot presenter
+        obj_dic = dict()
+        obj_dic["encoder_version"] = IO_VERSION
+        obj_dic["current_tab"] = obj.tabs.currentIndex()
+        if presenter.settings_presenter.settings:
+            obj_dic["settings_dict"] = presenter.settings_presenter.settings
+        else:
+            obj_dic["settings_dict"] = presenter.settings_presenter.model.get_settings_dict(SETTINGS_KEYS_TYPES)
+        if data_widget.presenter.get_loaded_workspaces():
+            obj_dic["data_loaded_workspaces"] = [*data_widget.presenter.get_loaded_workspaces().keys()]
+            obj_dic["plotted_workspaces"] = [*data_widget.presenter.plotted]
+            obj_dic["background_params"] = data_widget.model.get_bg_params()
+            if plot_widget.view.fit_browser.read_current_fitprop():
+                obj_dic["fit_properties"] = plot_widget.view.fit_browser.read_current_fitprop()
+                obj_dic["plot_diff"] = str(plot_widget.view.fit_browser.plotDiff())
+            else:
+                obj_dic["fit_properties"] = None
+        return obj_dic
+
+    @classmethod
+    def tags(cls):
+        return cls._tags
+
+
+class EngineeringDiffractionDecoder(EngineeringDiffractionUIAttributes):
+    def __init__(self):
+        super(EngineeringDiffractionDecoder, self).__init__()
+
+    @staticmethod
+    def decode(obj_dic, _=None):
+        if obj_dic["encoder_version"] != IO_VERSION:
+            logger.error("Engineering Diffraction Interface encoder used different version, restoration may fail")
+
+        ws_names = obj_dic["data_loaded_workspaces"]  # workspaces are in ADS, need restoring into interface
+        gui = EngineeringDiffractionGui()
+        presenter = gui.presenter
+        gui.tabs.setCurrentIndex(obj_dic["current_tab"])
+        presenter.settings_presenter.model.set_settings_dict(obj_dic["settings_dict"])
+        presenter.settings_presenter.settings = obj_dic["settings_dict"]
+        fit_data_widget = presenter.fitting_presenter.data_widget
+        fit_data_widget.model._bg_params = obj_dic["background_params"]
+        fit_data_widget.model.restore_files(ws_names)
+        fit_data_widget.presenter.plotted = set(obj_dic["plotted_workspaces"])
+        fit_data_widget.presenter.restore_table()
+
+        if obj_dic["fit_properties"]:
+            fit_browser = presenter.fitting_presenter.plot_widget.view.fit_browser
+            presenter.fitting_presenter.plot_widget.view.fit_toggle()  # show the fit browser, default is off
+            fit_props = obj_dic["fit_properties"]["properties"]
+            fit_function = fit_props["Function"]
+            output_name = fit_props["Output"]
+            is_plot_diff = obj_dic["plot_diff"]
+            fit_browser.setWorkspaceName(output_name)
+            fit_browser.setStartX(fit_props["StartX"])
+            fit_browser.setEndX(fit_props["EndX"])
+            fit_browser.loadFunction(fit_function)
+            fit_browser.setOutputName(output_name)
+            ws_name = output_name + '_Workspace'
+            fit_browser.do_plot(ADS.retrieve(ws_name), is_plot_diff)
+        return gui
+
+    @classmethod
+    def tags(cls):
+        return cls._tags
diff --git a/scripts/Engineering/gui/engineering_diffraction/presenter.py b/scripts/Engineering/gui/engineering_diffraction/presenter.py
new file mode 100644
index 0000000000000000000000000000000000000000..00c8f54678949941510ffa0bbe0ede7fdc637887
--- /dev/null
+++ b/scripts/Engineering/gui/engineering_diffraction/presenter.py
@@ -0,0 +1,94 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+#   NScD Oak Ridge National Laboratory, European Spallation Source,
+#   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+# SPDX - License - Identifier: GPL - 3.0 +
+
+from .tabs.common import CalibrationObserver
+from .tabs.common.path_handling import get_run_number_from_path
+from .tabs.calibration.model import CalibrationModel
+from .tabs.calibration.view import CalibrationView
+from .tabs.calibration.presenter import CalibrationPresenter
+from .tabs.focus.model import FocusModel
+from .tabs.focus.view import FocusView
+from .tabs.focus.presenter import FocusPresenter
+from .tabs.fitting.view import FittingView
+from .tabs.fitting.presenter import FittingPresenter
+from .settings.settings_model import SettingsModel
+from .settings.settings_view import SettingsView
+from .settings.settings_presenter import SettingsPresenter
+
+from mantidqt.interfacemanager import InterfaceManager
+from mantidqt.utils.observer_pattern import GenericObservable
+
+
+class EngineeringDiffractionPresenter(object):
+    def __init__(self):
+        self.calibration_presenter = None
+        self.focus_presenter = None
+        self.fitting_presenter = None
+        self.settings_presenter = None
+
+        self.doc = "Engineering Diffraction"
+
+        # Setup observers
+        self.calibration_observer = CalibrationObserver(self)
+
+        # Setup observables
+        self.statusbar_observable = GenericObservable()
+        self.savedir_observable = GenericObservable()
+
+    # the following setup functions should only be called from the view, this ensures both that the presenter object
+    # itself doesn't own the view (vice versa in this instance) and that the 'child' tabs of the presenter can be mocked
+    # /subbed in for other purposes i.e. testing, agnostic of the view
+
+    def setup_calibration(self, view):
+        cal_model = CalibrationModel()
+        cal_view = CalibrationView(parent=view.tabs)
+        self.calibration_presenter = CalibrationPresenter(cal_model, cal_view)
+        view.tabs.addTab(cal_view, "Calibration")
+
+    def setup_calibration_notifier(self):
+        self.calibration_presenter.calibration_notifier.add_subscriber(
+            self.focus_presenter.calibration_observer)
+        self.calibration_presenter.calibration_notifier.add_subscriber(self.calibration_observer)
+
+    def setup_focus(self, view):
+        focus_model = FocusModel()
+        focus_view = FocusView()
+        self.focus_presenter = FocusPresenter(focus_model, focus_view)
+        view.tabs.addTab(focus_view, "Focus")
+
+    def setup_fitting(self, view):
+        fitting_view = FittingView()
+        self.fitting_presenter = FittingPresenter(fitting_view)
+        self.focus_presenter.add_focus_subscriber(self.fitting_presenter.data_widget.presenter.focus_run_observer)
+        view.tabs.addTab(fitting_view, "Fitting")
+
+    def setup_settings(self, view):
+        settings_model = SettingsModel()
+        settings_view = SettingsView(view)
+        settings_presenter = SettingsPresenter(settings_model, settings_view)
+        settings_presenter.load_settings_from_file_or_default()
+        self.settings_presenter = settings_presenter
+        self.setup_savedir_notifier(view)
+
+    def setup_savedir_notifier(self, view):
+        self.settings_presenter.savedir_notifier.add_subscriber(view.savedir_observer)
+
+    def handle_close(self):
+        self.fitting_presenter.data_widget.ads_observer.unsubscribe()
+        self.fitting_presenter.plot_widget.view.ensure_fit_dock_closed()
+
+    def open_help_window(self):
+        InterfaceManager().showCustomInterfaceHelp(self.doc)
+
+    def open_settings(self):
+        self.settings_presenter.show()
+
+    def update_calibration(self, calibration):
+        instrument = calibration.get_instrument()
+        van_no = get_run_number_from_path(calibration.get_vanadium(), instrument)
+        sample_no = get_run_number_from_path(calibration.get_sample(), instrument)
+        self.statusbar_observable.notify_subscribers(f"V: {van_no}, CeO2: {sample_no}, Instrument: {instrument}")
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_model.py b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_model.py
index e610ad5a5f9d3fa30e3dcd7875bddd5ca0ec7857..cfee8b4a9fc370cdc2dbabb338d09ef48b898ce3 100644
--- a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_model.py
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_model.py
@@ -6,7 +6,7 @@
 # SPDX - License - Identifier: GPL - 3.0 +
 from os import path
 
-from mantid.simpleapi import Load, logger, EnggEstimateFocussedBackground, ConvertUnits, Plus, Minus, AverageLogData, \
+from mantid.simpleapi import Load, logger, EnggEstimateFocussedBackground, ConvertUnits, Minus, AverageLogData, \
     CreateEmptyTableWorkspace, GroupWorkspaces, DeleteWorkspace, DeleteTableRows, RenameWorkspace, CreateWorkspace
 from Engineering.gui.engineering_diffraction.settings.settings_helper import get_setting
 from Engineering.gui.engineering_diffraction.tabs.common import path_handling
@@ -30,12 +30,33 @@ class FittingDataModel(object):
         self._log_workspaces = None
         self._log_values = dict()  # {ws_name: {log_name: [avg, er]} }
         self._loaded_workspaces = {}  # Map stores using {WorkspaceName: Workspace}
-        self._background_workspaces = {}
+        self._bg_sub_workspaces = {}  # Map as above, this contains the workspaces with the background sub applied
         self._fit_results = {}  # {WorkspaceName: fit_result_dict}
         self._fit_workspaces = None
         self._last_added = []  # List of workspace names loaded in the last load action.
         self._bg_params = dict()  # {ws_name: [isSub, niter, xwindow, doSG]}
 
+    def restore_files(self, ws_names):
+        for ws_name in ws_names:
+            try:
+                ws = ADS.retrieve(ws_name)
+                if ws.getNumberHistograms() == 1:
+                    self._loaded_workspaces[ws_name] = ws
+                    if self._bg_params[ws_name]:
+                        self._bg_sub_workspaces[ws_name] = ADS.retrieve(ws_name + "_bgsub")
+                    else:
+                        self._bg_sub_workspaces[ws_name] = None
+                    if ws_name not in self._bg_params:
+                        self._bg_params[ws_name] = []
+                    self._last_added.append(ws_name)
+                else:
+                    logger.warning(
+                        f"Invalid number of spectra in workspace {ws_name}. Skipping restoration of workspace.")
+            except RuntimeError as e:
+                logger.error(
+                    f"Failed to restore workspace: {ws_name}. Error: {e}. \n Continuing loading of other files.")
+        self.update_log_workspace_group()
+
     def load_files(self, filenames_string, xunit):
         self._last_added = []
         filenames = [name.strip() for name in filenames_string.split(",")]
@@ -51,8 +72,10 @@ class FittingDataModel(object):
                         ws = ADS.retrieve(ws_name)
                     if ws.getNumberHistograms() == 1:
                         self._loaded_workspaces[ws_name] = ws
-                        if ws_name not in self._background_workspaces:
-                            self._background_workspaces[ws_name] = None
+                        if ws_name not in self._bg_sub_workspaces:
+                            self._bg_sub_workspaces[ws_name] = None
+                        if ws_name not in self._bg_params:
+                            self._bg_params[ws_name] = []
                         self._last_added.append(ws_name)
                     else:
                         logger.warning(
@@ -264,6 +287,9 @@ class FittingDataModel(object):
             # axis for labels in workspace
             axis = TextAxis.create(nruns)
             for iws, wsname in enumerate(self._loaded_workspaces.keys()):
+                wsname_bgsub = wsname + "_bgsub"
+                if wsname_bgsub in self._fit_results:
+                    wsname = wsname_bgsub
                 if wsname in self._fit_results and param in self._fit_results[wsname]['results']:
                     fitvals = array(self._fit_results[wsname]['results'][param])
                     # pad to max length (with nans)
@@ -301,8 +327,8 @@ class FittingDataModel(object):
     def update_workspace_name(self, old_name, new_name):
         if new_name not in self._loaded_workspaces:
             self._loaded_workspaces[new_name] = self._loaded_workspaces.pop(old_name)
-            if old_name in self._background_workspaces:
-                self._background_workspaces[new_name] = self._background_workspaces.pop(old_name)
+            if old_name in self._bg_sub_workspaces:
+                self._bg_sub_workspaces[new_name] = self._bg_sub_workspaces.pop(old_name)
             if old_name in self._bg_params:
                 self._bg_params[new_name] = self._bg_params.pop(old_name)
             if old_name in self._log_values:
@@ -316,49 +342,40 @@ class FittingDataModel(object):
     def get_log_workspaces_name(self):
         return [ws.name() for ws in self._log_workspaces] if self._log_workspaces else ''
 
-    def get_background_workspaces(self):
-        return self._background_workspaces
+    def get_bgsub_workspaces(self):
+        return self._bg_sub_workspaces
 
     def get_bg_params(self):
         return self._bg_params
 
-    def do_background_subtraction(self, ws_name, bg_params):
+    def create_or_update_bgsub_ws(self, ws_name, bg_params):
         ws = self._loaded_workspaces[ws_name]
-        ws_bg = self._background_workspaces[ws_name]
-        bg_changed = False
-        if ws_bg and bg_params[1:] != self._bg_params[ws_name][1:]:
-            # add bg back on to data (but don't change bgsub status)
-            self.undo_background_subtraction(ws_name, isBGsub=bg_params[0])
-            bg_changed = True
-        if bg_changed or not ws_bg:
-            # re-evaluate background (or evaluate for first time)
+        ws_bg = self._bg_sub_workspaces[ws_name]
+        if not ws_bg or self._bg_params[ws_name] == [] or bg_params[1:] != self._bg_params[ws_name][1:]:
+            background = self.estimate_background(ws_name, *bg_params[1:])
             self._bg_params[ws_name] = bg_params
-            ws_bg = self.estimate_background(ws_name, *bg_params[1:])
-        # update bg sub status before Minus (updates plot which repopulates table)
-        self._bg_params[ws_name][0] = bg_params[0]
-        Minus(LHSWorkspace=ws, RHSWorkspace=ws_bg, OutputWorkspace=ws_name)
-
-    def undo_background_subtraction(self, ws_name, isBGsub=False):
-        self._bg_params[ws_name][0] = isBGsub  # must do this before plotting which refreshes table
-        Plus(LHSWorkspace=ws_name, RHSWorkspace=self.get_background_workspaces()[ws_name],
-             OutputWorkspace=ws_name)
+            bgsub_ws_name = ws_name + "_bgsub"
+            bgsub_ws = Minus(LHSWorkspace=ws, RHSWorkspace=background, OutputWorkspace=bgsub_ws_name)
+            self._bg_sub_workspaces[ws_name] = bgsub_ws
+            DeleteWorkspace(background)
+        else:
+            logger.notice("Background workspace already calculated")
 
     def estimate_background(self, ws_name, niter, xwindow, doSGfilter):
         ws_bg = EnggEstimateFocussedBackground(InputWorkspace=ws_name, OutputWorkspace=ws_name + "_bg",
                                                NIterations=niter, XWindow=xwindow, ApplyFilterSG=doSGfilter)
-        self._background_workspaces[ws_name] = ws_bg
         return ws_bg
 
     def plot_background_figure(self, ws_name):
         ws = self._loaded_workspaces[ws_name]
-        ws_bg = self._background_workspaces[ws_name]
-        if ws_bg:
+        ws_bgsub = self._bg_sub_workspaces[ws_name]
+        if ws_bgsub:
             fig, ax = subplots(2, 1, sharex=True, gridspec_kw={'height_ratios': [2, 1]},
                                subplot_kw={'projection': 'mantid'})
-            tmp = Plus(LHSWorkspace=ws_name, RHSWorkspace=ws_bg, StoreInADS=False)
-            ax[0].plot(tmp, 'x')
-            ax[0].plot(ws_bg, '-r')
-            ax[1].plot(ws, 'x')
+            bg = Minus(LHSWorkspace=ws_name, RHSWorkspace=ws_bgsub, StoreInADS=False)
+            ax[0].plot(ws, 'x')
+            ax[1].plot(ws_bgsub, 'x')
+            ax[0].plot(bg, '-r')
             fig.show()
 
     def get_last_added(self):
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_presenter.py b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_presenter.py
index d966a682a6bdcde0a626e75d9d9c452dc0043e27..2d120edf25dbc8fae884acf81dd373bf09b1ef12 100644
--- a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_presenter.py
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/data_presenter.py
@@ -100,6 +100,9 @@ class FittingDataPresenter(object):
     def get_loaded_workspaces(self):
         return self.model.get_loaded_workspaces()
 
+    def restore_table(self):  # used when the interface is being restored from a save or crash
+        self._repopulate_table()
+
     def _start_load_worker(self, filenames, xunit):
         """
         Load one to many files into mantid that are tracked by the interface.
@@ -135,7 +138,7 @@ class FittingDataPresenter(object):
                 bank = self.model.get_sample_log_from_ws(name, "bankid")
                 if bank == 0:
                     bank = "cropped"
-                checked = name in self.plotted
+                checked = name in self.plotted or name + "_bgsub" in self.plotted
                 if name in self.model.get_bg_params():
                     self._add_row_to_table(name, i, run_no, bank, checked, *self.model.get_bg_params()[name])
                 else:
@@ -171,9 +174,14 @@ class FittingDataPresenter(object):
     def _handle_table_cell_changed(self, row, col):
         if row in self.row_numbers:
             ws_name = self.row_numbers[row]
+            is_sub = self.view.get_item_checked(row, 3)
             if col == 2:
                 # Plot check box
-                ws = self.model.get_loaded_workspaces()[ws_name]
+                if is_sub:
+                    ws = self.model.get_bgsub_workspaces()[ws_name]
+                    ws_name += "_bgsub"
+                else:
+                    ws = self.model.get_loaded_workspaces()[ws_name]
                 if self.view.get_item_checked(row, col):  # Plot Box is checked
                     self.plot_added_notifier.notify_subscribers(ws)
                     self.plotted.add(ws_name)
@@ -181,19 +189,30 @@ class FittingDataPresenter(object):
                     self.plot_removed_notifier.notify_subscribers(ws)
                     self.plotted.discard(ws_name)
             elif col == 3:
-                # subtract bg
-                if self.view.get_item_checked(row, col):
-                    # subtract bg box checked
+                # subtract bg col
+                if is_sub or self.view.get_item_checked(row, 2):  # this ensures the sub ws isn't made on load
                     bg_params = self.view.read_bg_params_from_table(row)
-                    self.model.do_background_subtraction(ws_name, bg_params)
-                elif self.model.get_background_workspaces()[ws_name]:
-                    # box unchecked and bg exists:
-                    self.model.undo_background_subtraction(ws_name)
+                    self.model.create_or_update_bgsub_ws(ws_name, bg_params)
+                    self._update_plotted_ws_with_sub_state(ws_name, is_sub)
             elif col > 3:
-                if self.view.get_item_checked(row, 3):
+                if is_sub:
                     # bg params changed - revaluate background
                     bg_params = self.view.read_bg_params_from_table(row)
-                    self.model.do_background_subtraction(ws_name, bg_params)
+                    self.model.create_or_update_bgsub_ws(ws_name, bg_params)
+
+    def _update_plotted_ws_with_sub_state(self, ws_name, is_sub):
+        ws = self.model.get_loaded_workspaces()[ws_name]
+        ws_bgsub = self.model.get_bgsub_workspaces()[ws_name]
+        if ws_name in self.plotted and is_sub:
+            self.plot_removed_notifier.notify_subscribers(ws)
+            self.plotted.discard(ws_name)
+            self.plot_added_notifier.notify_subscribers(ws_bgsub)
+            self.plotted.add(ws_name + "_bgsub")
+        elif ws_name + "_bgsub" in self.plotted and not is_sub:
+            self.plot_removed_notifier.notify_subscribers(ws_bgsub)
+            self.plotted.discard(ws_name + "_bgsub")
+            self.plot_added_notifier.notify_subscribers(ws)
+            self.plotted.add(ws_name)
 
     def _handle_selection_changed(self):
         rows = self.view.get_selected_rows()
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/test/test_data_model.py b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/test/test_data_model.py
index 6bbc44c052470c13fff0f4347b7dd92740d26e9b..9716c904cb2fd4e726216002a9ca868aef19acc7 100644
--- a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/test/test_data_model.py
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/test/test_data_model.py
@@ -151,56 +151,54 @@ class TestFittingDataModel(unittest.TestCase):
         mock_load.assert_any_call("/dir/file2.nxs", OutputWorkspace="file2_TOF")
         self.assertEqual(2, mock_logger.error.call_count)
 
+    @patch(data_model_path + ".DeleteWorkspace")
     @patch(data_model_path + ".EnggEstimateFocussedBackground")
     @patch(data_model_path + ".Minus")
-    @patch(data_model_path + ".Plus")
-    def test_do_background_subtraction_first_time(self, mock_plus, mock_minus, mock_estimate_bg):
+    def test_do_background_subtraction_first_time(self, mock_minus, mock_estimate_bg, mock_delete_ws):
         self.model._loaded_workspaces = {"name1": self.mock_ws}
-        self.model._background_workspaces = {"name1": None}
+        self.model._bg_sub_workspaces = {"name1": None}
         self.model._bg_params = dict()
         mock_estimate_bg.return_value = self.mock_ws
 
         bg_params = [True, 40, 800, False]
-        self.model.do_background_subtraction("name1", bg_params)
+        self.model.create_or_update_bgsub_ws("name1", bg_params)
 
         self.assertEqual(self.model._bg_params["name1"], bg_params)
         mock_minus.assert_called_once()
         mock_estimate_bg.assert_called_once()
-        mock_plus.assert_not_called()
+        mock_delete_ws.assert_called_once()
 
+    @patch(data_model_path + ".DeleteWorkspace")
     @patch(data_model_path + ".EnggEstimateFocussedBackground")
     @patch(data_model_path + ".Minus")
-    @patch(data_model_path + ".Plus")
-    def test_do_background_subtraction_bgparams_changed(self, mock_plus, mock_minus, mock_estimate_bg):
+    def test_do_background_subtraction_bgparams_changed(self, mock_minus, mock_estimate_bg, mock_delete_ws):
         self.model._loaded_workspaces = {"name1": self.mock_ws}
-        self.model._background_workspaces = {"name1": self.mock_ws}
+        self.model._bg_sub_workspaces = {"name1": self.mock_ws}
         self.model._bg_params = {"name1": [True, 80, 1000, False]}
         mock_estimate_bg.return_value = self.mock_ws
 
         bg_params = [True, 40, 800, False]
-        self.model.do_background_subtraction("name1", bg_params)
+        self.model.create_or_update_bgsub_ws("name1", bg_params)
 
         self.assertEqual(self.model._bg_params["name1"], bg_params)
         mock_minus.assert_called_once()
         mock_estimate_bg.assert_called_once()
-        mock_plus.assert_called_once()
+        mock_delete_ws.assert_called_once()
 
     @patch(data_model_path + ".EnggEstimateFocussedBackground")
     @patch(data_model_path + ".Minus")
-    @patch(data_model_path + ".Plus")
-    def test_do_background_subtraction_no_change(self, mock_plus, mock_minus, mock_estimate_bg):
+    def test_do_background_subtraction_no_change(self, mock_minus, mock_estimate_bg):
         self.model._loaded_workspaces = {"name1": self.mock_ws}
-        self.model._background_workspaces = {"name1": self.mock_ws}
+        self.model._bg_sub_workspaces = {"name1": self.mock_ws}
         bg_params = [True, 80, 1000, False]
         self.model._bg_params = {"name1": bg_params}
         mock_estimate_bg.return_value = self.mock_ws
 
-        self.model.do_background_subtraction("name1", bg_params)
+        self.model.create_or_update_bgsub_ws("name1", bg_params)
 
         self.assertEqual(self.model._bg_params["name1"], bg_params)
-        mock_minus.assert_called_once()
+        mock_minus.assert_not_called()
         mock_estimate_bg.assert_not_called()
-        mock_plus.assert_not_called()
 
     @patch(data_model_path + '.RenameWorkspace')
     @patch(data_model_path + ".ADS")
@@ -239,14 +237,14 @@ class TestFittingDataModel(unittest.TestCase):
 
     def test_update_workspace_name(self):
         self.model._loaded_workspaces = {"name1": self.mock_ws, "name2": self.mock_ws}
-        self.model._background_workspaces = {"name1": self.mock_ws, "name2": self.mock_ws}
+        self.model._bg_sub_workspaces = {"name1": self.mock_ws, "name2": self.mock_ws}
         self.model._bg_params = {"name1": [True, 80, 1000, False]}
         self.model._log_values = {"name1": 1, "name2": 2}
 
         self.model.update_workspace_name("name1", "new_name")
 
         self.assertEqual({"new_name": self.mock_ws, "name2": self.mock_ws}, self.model._loaded_workspaces)
-        self.assertEqual({"new_name": self.mock_ws, "name2": self.mock_ws}, self.model._background_workspaces)
+        self.assertEqual({"new_name": self.mock_ws, "name2": self.mock_ws}, self.model._bg_sub_workspaces)
         self.assertEqual({"new_name": [True, 80, 1000, False]}, self.model._bg_params)
         self.assertEqual({"new_name": 1, "name2": 2}, self.model._log_values)
 
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/test/test_data_presenter.py b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/test/test_data_presenter.py
index 24654e7ba715ca0ea35f6614f9abb8b08c6bcb09..2d47cd7f71ebd325b06fbeea4672c08054471b10 100644
--- a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/test/test_data_presenter.py
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/data_handling/test/test_data_presenter.py
@@ -13,6 +13,13 @@ from Engineering.gui.engineering_diffraction.tabs.fitting.data_handling import d
 dir_path = "Engineering.gui.engineering_diffraction.tabs.fitting.data_handling"
 
 
+def _get_item_checked_mock(_, arg):
+    if arg == 2:
+        return True
+    elif arg == 3:
+        return False
+
+
 class FittingDataPresenterTest(unittest.TestCase):
     def setUp(self):
         self.model = mock.create_autospec(data_model.FittingDataModel)
@@ -243,6 +250,7 @@ class FittingDataPresenterTest(unittest.TestCase):
 
     def test_handle_table_cell_changed_checkbox_ticked(self):
         mocked_table_item = mock.MagicMock()
+        self.view.get_item_checked.side_effect = _get_item_checked_mock
         mocked_table_item.checkState.return_value = 2
         self.view.get_table_item.return_value = mocked_table_item
         self.presenter.row_numbers = data_presenter.TwoWayRowDict()
@@ -298,10 +306,9 @@ class FittingDataPresenterTest(unittest.TestCase):
         # subtract background for first time
         self.view.get_item_checked.return_value = True  # determines is bgSubtract is checked or not
         self.view.read_bg_params_from_table.return_value = [True, 40, 800, False]
-        self.model.get_background_workspaces.return_value = None
+        self.model.get_bgsub_workspaces.return_value = {"name2": None}
         self.presenter._handle_table_cell_changed(1, 3)
-        self.model.do_background_subtraction.assert_called_with("name2", self.view.read_bg_params_from_table(0))
-        self.model.undo_background_subtraction.assert_not_called()
+        self.model.create_or_update_bgsub_ws.assert_called_with("name2", self.view.read_bg_params_from_table(0))
 
     def test_bgparam_changed_when_bgsub_False(self):
         # setup row
@@ -309,8 +316,7 @@ class FittingDataPresenterTest(unittest.TestCase):
         # activate bg subtraction before background is made (nothing should happen)
         self.view.get_item_checked.return_value = False
         self.presenter._handle_table_cell_changed(1, 4)
-        self.model.do_background_subtraction.assert_not_called()
-        self.model.undo_background_subtraction.assert_not_called()
+        self.model.create_or_update_bgsub_ws.assert_not_called()
 
     def test_bgparam_changed_when_bgsub_True(self):
         # setup row
@@ -318,17 +324,16 @@ class FittingDataPresenterTest(unittest.TestCase):
         self.view.get_item_checked.return_value = True
         self.model.get_bg_params.return_value = {"name2": [True, 40, 800, False]}
         self.view.read_bg_params_from_table.return_value = [True, 200, 800, False]
-        self.model.get_background_workspaces.return_value = self.model.get_loaded_workspaces()
+        self.model.get_bgsub_workspaces.return_value = self.model.get_loaded_workspaces()
         self.presenter._handle_table_cell_changed(1, 4)
-        self.model.do_background_subtraction.assert_called_once_with("name2", self.view.read_bg_params_from_table(0))
+        self.model.create_or_update_bgsub_ws.assert_called_once_with("name2", self.view.read_bg_params_from_table(0))
 
     def test_undo_bgsub(self):
         self._setup_bgsub_test()
         # untick background subtraction
         self.view.get_item_checked.return_value = False
         self.presenter._handle_table_cell_changed(1, 3)
-        self.model.do_background_subtraction.assert_not_called()
-        self.model.undo_background_subtraction.assert_called_once_with("name2")
+        self.model.create_or_update_bgsub_ws.assert_not_called()
 
     def test_inspect_bg_button_enables_and_disables(self):
         self.view.get_item_checked.return_value = False
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/plotting/EngDiff_fitpropertybrowser.py b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/plotting/EngDiff_fitpropertybrowser.py
index fd896999f3a26b817be2e29d467a7a495c794f5c..81dc149063fcfb73671b449805aa757bdf5a44f3 100644
--- a/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/plotting/EngDiff_fitpropertybrowser.py
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/fitting/plotting/EngDiff_fitpropertybrowser.py
@@ -53,17 +53,20 @@ class EngDiffFitPropertyBrowser(FitPropertyBrowser):
         Get algorithm parameters currently displayed in the UI browser (incl. defaults that user cannot change)
         :return: dict in style of self.getFitAlgorithmParameters()
         """
-        fitprop = {'properties': {'InputWorkspace': self.workspaceName(),
-                                  'Output': self.outputName(),
-                                  'StartX': self.startX(),
-                                  'EndX': self.endX(),
-                                  'Function': self.getFunctionString(),
-                                  'ConvolveMembers': True,
-                                  'OutputCompositeMembers': True}}
-        exclude = self.getExcludeRange()
-        if exclude:
-            fitprop['properties']['Exclude'] = [int(s) for s in exclude.split(',')]
-        return fitprop
+        try:
+            fitprop = {'properties': {'InputWorkspace': self.workspaceName(),
+                                      'Output': self.outputName(),
+                                      'StartX': self.startX(),
+                                      'EndX': self.endX(),
+                                      'Function': self.getFunctionString(),
+                                      'ConvolveMembers': True,
+                                      'OutputCompositeMembers': True}}
+            exclude = self.getExcludeRange()
+            if exclude:
+                fitprop['properties']['Exclude'] = [int(s) for s in exclude.split(',')]
+            return fitprop
+        except BaseException:  # The cpp passes up an 'unknown' error if getFunctionString() fails, i.e. if no fit
+            return None
 
     def save_current_setup(self, name):
         self.executeCustomSetupRemove(name)
diff --git a/scripts/Engineering/gui/engineering_diffraction/test/__init__.py b/scripts/Engineering/gui/engineering_diffraction/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c61b2b97ac90c3b7a2173f1a3ea44378718d5de
--- /dev/null
+++ b/scripts/Engineering/gui/engineering_diffraction/test/__init__.py
@@ -0,0 +1,6 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+#   NScD Oak Ridge National Laboratory, European Spallation Source,
+#   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+# SPDX - License - Identifier: GPL - 3.0 +
diff --git a/scripts/Engineering/gui/engineering_diffraction/test/engineering_diffraction_io_test.py b/scripts/Engineering/gui/engineering_diffraction/test/engineering_diffraction_io_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..addefde9a5705ed5256855353f5b0c54d1514fe6
--- /dev/null
+++ b/scripts/Engineering/gui/engineering_diffraction/test/engineering_diffraction_io_test.py
@@ -0,0 +1,165 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+#   NScD Oak Ridge National Laboratory, European Spallation Source,
+#   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+# SPDX - License - Identifier: GPL - 3.0 +
+import unittest
+from os import path
+from unittest import mock
+from unittest.mock import MagicMock
+from mantid.simpleapi import Load, Fit
+from mantidqt.utils.qt.testing import start_qapplication
+from Engineering.gui.engineering_diffraction.engineering_diffraction import EngineeringDiffractionGui
+from Engineering.gui.engineering_diffraction.presenter import EngineeringDiffractionPresenter
+from Engineering.gui.engineering_diffraction.engineering_diffraction_io import EngineeringDiffractionDecoder, \
+    EngineeringDiffractionEncoder
+from Engineering.gui.engineering_diffraction.tabs.calibration.model import CalibrationModel
+from Engineering.gui.engineering_diffraction.tabs.calibration.view import CalibrationView
+from Engineering.gui.engineering_diffraction.tabs.calibration.presenter import CalibrationPresenter
+from Engineering.gui.engineering_diffraction.tabs.focus.model import FocusModel
+from Engineering.gui.engineering_diffraction.tabs.focus.view import FocusView
+from Engineering.gui.engineering_diffraction.tabs.focus.presenter import FocusPresenter
+from Engineering.gui.engineering_diffraction.tabs.fitting.view import FittingView
+from Engineering.gui.engineering_diffraction.tabs.fitting.presenter import FittingPresenter
+from Engineering.gui.engineering_diffraction.tabs.fitting.plotting.plot_view import FittingPlotView
+from Engineering.gui.engineering_diffraction.tabs.fitting.plotting.EngDiff_fitpropertybrowser import \
+    EngDiffFitPropertyBrowser
+from Engineering.gui.engineering_diffraction.settings.settings_model import SettingsModel
+from Engineering.gui.engineering_diffraction.settings.settings_view import SettingsView
+from Engineering.gui.engineering_diffraction.settings.settings_presenter import SettingsPresenter
+
+IO_VERSION = 1
+TEST_FILE = "ENGINX_277208_focused_bank_2.nxs"
+TEST_WS = 'ENGINX_277208_focused_bank_2_TOF'
+FIT_WS = TEST_WS + '_Workspace'
+FIT_DICT = {'properties': {'InputWorkspace': TEST_WS, 'Output': TEST_WS, 'StartX':
+            5731.386819290339, 'EndX': 52554.79335660165, 'Function': 'name=Gaussian,Height=0.0365604,PeakCentre=37490.'
+                                                                      '4,Sigma=1284.55;name=Gaussian,Height=0.0190721,P'
+                                                                      'eakCentre=21506.1,Sigma=1945.78',
+            'ConvolveMembers': True, 'OutputCompositeMembers': True}}
+
+SETTINGS_DICT = {
+    "full_calibration": "",
+    "save_location": path.join(path.expanduser("~"), "Engineering_Mantid"),
+    "recalc_vanadium": False,
+    "logs": ','.join(
+        ['Temp_1', 'W_position', 'X_position', 'Y_position', 'Z_position', 'stress', 'strain', 'stressrig_go']),
+    "primary_log": 'strain',
+    "sort_ascending": False  # this is changed to false to show a deviation from the default
+}
+
+ENCODED_DICT = {'encoder_version': IO_VERSION, 'current_tab': 2, 'data_loaded_workspaces':
+                [TEST_WS], 'plotted_workspaces': [FIT_WS], 'fit_properties': FIT_DICT, 'plot_diff': 'True',
+                'settings_dict': SETTINGS_DICT, 'background_params': {TEST_WS: [True, 70, 4000, True]}}
+
+
+def _create_fit_workspace():
+    fn_input = FIT_DICT['properties']
+    Fit(**fn_input)
+
+
+def _load_test_file():
+    Load(TEST_FILE, OutputWorkspace=TEST_WS)
+    Load(TEST_FILE, OutputWorkspace=(TEST_WS+"_bgsub"))
+
+
+class EngineeringDiffractionEncoderTest(unittest.TestCase):
+
+    def setUp(self):
+        self.fitprop_browser = None
+        self.mock_view = mock.create_autospec(EngineeringDiffractionGui)
+        self.presenter = EngineeringDiffractionPresenter()
+        self.create_test_calibration_presenter()
+        self.create_test_focus_presenter()
+        self.create_test_fitting_presenter()
+        self.create_test_settings_presenter()
+        self.mock_view.presenter = self.presenter
+        self.mock_tabs = MagicMock()
+        self.mock_view.tabs = self.mock_tabs
+        self.mock_tabs.currentIndex.return_value = 0
+        self.encoder = EngineeringDiffractionEncoder()
+        self.decoder = EngineeringDiffractionDecoder()
+        self.io_version = 1
+        self.fit_ws = None
+
+    def create_test_focus_presenter(self):
+        focus_model = FocusModel()
+        focus_view = mock.create_autospec(FocusView)
+        self.presenter.focus_presenter = FocusPresenter(focus_model, focus_view)
+
+    def create_test_calibration_presenter(self):
+        cal_model = CalibrationModel()
+        cal_view = mock.create_autospec(CalibrationView)
+        self.presenter.calibration_presenter = CalibrationPresenter(cal_model, cal_view)
+
+    def create_test_fitting_presenter(self):
+        fitting_view = mock.create_autospec(FittingView)
+        self.presenter.fitting_presenter = FittingPresenter(fitting_view)
+        self.presenter.focus_presenter.add_focus_subscriber(self.presenter.fitting_presenter.data_widget.presenter.
+                                                            focus_run_observer)
+        fitting_plot_view = mock.create_autospec(FittingPlotView)
+        self.fitprop_browser = mock.create_autospec(EngDiffFitPropertyBrowser)
+        fitting_plot_view.fit_browser = self.fitprop_browser
+        self.presenter.fitting_presenter.plot_widget.view = fitting_plot_view
+
+    def create_test_settings_presenter(self):
+        settings_model = SettingsModel()
+        settings_view = mock.create_autospec(SettingsView)
+        settings_presenter = SettingsPresenter(settings_model, settings_view)
+        self.presenter.settings_presenter = settings_presenter
+        settings_presenter.settings = SETTINGS_DICT
+        settings_presenter._save_settings_to_file()
+
+    def test_blank_gui_encodes(self):
+        self.mock_tabs.currentIndex.return_value = 0
+        test_dic = self.encoder.encode(self.mock_view)
+        self.assertEqual({'encoder_version': self.io_version, 'current_tab': 0, 'settings_dict': SETTINGS_DICT},
+                         test_dic)
+
+    def test_loaded_workspaces_encode(self):
+        self.presenter.fitting_presenter.data_widget.presenter.model.load_files(TEST_FILE, 'TOF')
+        self.fitprop_browser.read_current_fitprop.return_value = None
+        test_dic = self.encoder.encode(self.mock_view)
+        self.assertEqual({'encoder_version': self.io_version, 'current_tab': 0, 'data_loaded_workspaces':
+                         [TEST_WS], 'plotted_workspaces': [], 'fit_properties': None, 'settings_dict':
+                         SETTINGS_DICT, 'background_params': {'ENGINX_277208_focused_bank_2_TOF': []}}, test_dic)
+
+    def test_background_params_encode(self):
+        self.presenter.fitting_presenter.data_widget.presenter.model.load_files(TEST_FILE, 'TOF')
+        self.fitprop_browser.read_current_fitprop.return_value = None
+        self.presenter.fitting_presenter.data_widget.model._bg_params = {TEST_WS: [True, 70, 4000, True]}
+        test_dic = self.encoder.encode(self.mock_view)
+        self.assertEqual({'encoder_version': self.io_version, 'current_tab': 0, 'data_loaded_workspaces':
+                         [TEST_WS], 'plotted_workspaces': [], 'fit_properties': None, 'settings_dict':
+                         SETTINGS_DICT, 'background_params': {TEST_WS: [True, 70, 4000, True]}}, test_dic)
+
+    def test_fits_encode(self):
+        self.presenter.fitting_presenter.data_widget.presenter.model.load_files(TEST_FILE, 'TOF')
+        self.fitprop_browser.read_current_fitprop.return_value = FIT_DICT
+        self.fitprop_browser.plotDiff.return_value = True
+        self.presenter.fitting_presenter.data_widget.presenter.plotted = {FIT_WS}
+        test_dic = self.encoder.encode(self.mock_view)
+        self.assertEqual({'encoder_version': self.io_version, 'current_tab': 0, 'data_loaded_workspaces':
+                          [TEST_WS], 'plotted_workspaces': [FIT_WS], 'fit_properties': FIT_DICT, 'plot_diff':
+                          'True', 'settings_dict': SETTINGS_DICT, 'background_params': {
+                                'ENGINX_277208_focused_bank_2_TOF': []}}, test_dic)
+
+
+@start_qapplication
+class EngineeringDiffractionDecoderTest(unittest.TestCase):
+
+    def setUp(self):
+        self.encoder = EngineeringDiffractionEncoder()
+        self.decoder = EngineeringDiffractionDecoder()
+        _load_test_file()
+        _create_fit_workspace()
+
+    def test_decode_produces_gui_returning_same_dict(self):
+        self.gui = self.decoder.decode(ENCODED_DICT)
+        post_decode_dict = self.encoder.encode(self.gui)
+        self.assertEqual(ENCODED_DICT, post_decode_dict)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/scripts/Engineering_Diffraction_register.py b/scripts/Engineering_Diffraction_register.py
new file mode 100644
index 0000000000000000000000000000000000000000..3d0b95ba0dc753280b6ef628961c77f2b619fc25
--- /dev/null
+++ b/scripts/Engineering_Diffraction_register.py
@@ -0,0 +1,19 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+#   NScD Oak Ridge National Laboratory, European Spallation Source,
+#   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+# SPDX - License - Identifier: GPL - 3.0 +
+from mantidqt.project.decoderfactory import DecoderFactory
+from mantidqt.project.encoderfactory import EncoderFactory
+from Engineering.gui.engineering_diffraction.engineering_diffraction import EngineeringDiffractionGui
+from Engineering.gui.engineering_diffraction.engineering_diffraction_io import EngineeringDiffractionEncoder, \
+    EngineeringDiffractionDecoder
+
+
+def compatible_check_for_encoder(obj, _):
+    return isinstance(obj, EngineeringDiffractionGui)
+
+
+DecoderFactory.register_decoder(EngineeringDiffractionDecoder)
+EncoderFactory.register_encoder(EngineeringDiffractionEncoder, compatible_check_for_encoder)
diff --git a/scripts/FilterEvents.py b/scripts/FilterEvents.py
index 51421f005ce2c2793f4b41f2444e6da35ceb355b..c83c66a37161842f05272d916147554bb7dcd63a 100644
--- a/scripts/FilterEvents.py
+++ b/scripts/FilterEvents.py
@@ -12,7 +12,13 @@ from FilterEvents import eventFilterGUI  # noqa
 
 app, within_mantid = get_qapplication()
 
-reducer = eventFilterGUI.MainWindow() #the main ui class in this file is called MainWindow
+if 'workbench' in sys.modules:
+    from workbench.config import get_window_config
+
+    parent, flags = get_window_config()
+else:
+    parent, flags = None, None
+reducer = eventFilterGUI.MainWindow(parent, flags) #the main ui class in this file is called MainWindow
 reducer.show()
 if not within_mantid:
     sys.exit(app.exec_())
diff --git a/scripts/FilterEvents/eventFilterGUI.py b/scripts/FilterEvents/eventFilterGUI.py
index 0472d8b9bafd14b5828c7e70c1d617f08e9e516e..7af20a722077c380d1cb9beb7cd313cf2d32a047 100644
--- a/scripts/FilterEvents/eventFilterGUI.py
+++ b/scripts/FilterEvents/eventFilterGUI.py
@@ -40,12 +40,15 @@ class MainWindow(QMainWindow):
 
     _errMsgWindow = None
 
-    def __init__(self, parent=None):
+    def __init__(self, parent=None, window_flags=None):
         """ Initialization and set up
         """
         # Base class
         QMainWindow.__init__(self, parent)
 
+        if window_flags:
+            self.setWindowFlags(window_flags)
+
         # Mantid configuration
         config = ConfigService.Instance()
         self._instrument = config["default.instrument"]
diff --git a/scripts/Frequency_Domain_Analysis.py b/scripts/Frequency_Domain_Analysis.py
index 30ceaa7173c6a6ed4c0e5dd757c9945e176dbcdd..64e40fc64f4fe213a003565061c4288960035457 100644
--- a/scripts/Frequency_Domain_Analysis.py
+++ b/scripts/Frequency_Domain_Analysis.py
@@ -8,9 +8,15 @@
 from Muon.GUI.FrequencyDomainAnalysis.frequency_domain_analysis_2 import FrequencyAnalysisGui
 from qtpy import QtCore
 from Muon.GUI.Common.usage_report import report_interface_startup
-
+import sys
 
 Name = "Frequency_Domain_Analysis_2"
+if 'workbench' in sys.modules:
+    from workbench.config import get_window_config
+
+    parent, flags = get_window_config()
+else:
+    parent, flags = None, None
 
 if 'muon_freq' in globals():
     muon_freq = globals()['muon_freq']
@@ -26,12 +32,12 @@ if 'muon_freq' in globals():
             ) & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
         muon_freq.activateWindow()
     else:
-        muon_freq = FrequencyAnalysisGui()
+        muon_freq = FrequencyAnalysisGui(parent, flags)
         report_interface_startup(Name)
         muon_freq.resize(700, 700)
         muon_freq.show()
 else:
-    muon_freq = FrequencyAnalysisGui()
+    muon_freq = FrequencyAnalysisGui(parent, flags)
     report_interface_startup(Name)
     muon_freq.resize(700, 700)
     muon_freq.show()
diff --git a/scripts/HFIR_4Circle_Reduction.py b/scripts/HFIR_4Circle_Reduction.py
index a026c1364078d9912c3cb693b770b1eaacdf83b8..aa55fd3404f14c50ce4a6587bd1996a7ba831182 100644
--- a/scripts/HFIR_4Circle_Reduction.py
+++ b/scripts/HFIR_4Circle_Reduction.py
@@ -11,7 +11,13 @@ set_matplotlib_backend()  # must be called before anything tries to use matplotl
 from HFIR_4Circle_Reduction import reduce4circleGUI  # noqa
 
 app, within_mantid = get_qapplication()
-reducer = reduce4circleGUI.MainWindow()  # the main ui class in this file
+if 'workbench' in sys.modules:
+    from workbench.config import get_window_config
+
+    parent, flags = get_window_config()
+else:
+    parent, flags = None, None
+reducer = reduce4circleGUI.MainWindow(parent, flags)  # the main ui class in this file
 reducer.show()
 if not within_mantid:
     sys.exit(app.exec_())
diff --git a/scripts/HFIR_4Circle_Reduction/reduce4circleGUI.py b/scripts/HFIR_4Circle_Reduction/reduce4circleGUI.py
index b1917bfd8e5b1ac51f456a00fae96df802878be5..356d436289cb3a97550967931c3ea482df03f66e 100644
--- a/scripts/HFIR_4Circle_Reduction/reduce4circleGUI.py
+++ b/scripts/HFIR_4Circle_Reduction/reduce4circleGUI.py
@@ -77,12 +77,15 @@ class MainWindow(QMainWindow):
                'Peak Integration': 6,
                'Scans Processing': 5}
 
-    def __init__(self, parent=None):
+    def __init__(self, parent=None, window_flags=None):
         """ Initialization and set up
         """
         # Base class
         QMainWindow.__init__(self,parent)
 
+        if window_flags:
+            self.setWindowFlags(window_flags)
+
         # UI Window (from Qt Designer)
         ui_path = "MainWindow.ui"
         self.ui = load_ui(__file__, ui_path, baseinstance=self)
diff --git a/scripts/ISIS_SANS.py b/scripts/ISIS_SANS.py
index 1cf00b9a7758c09a7dad216053bc78c865b0948e..206fd87d89d6ab584bda8c9ad824324f437e1bd8 100644
--- a/scripts/ISIS_SANS.py
+++ b/scripts/ISIS_SANS.py
@@ -8,12 +8,19 @@
 """
     Script used to start the Test Interface from MantidPlot
 """
+import sys
 from sans.common.enums import SANSFacility
 from sans.gui_logic.presenter.run_tab_presenter import RunTabPresenter
 from ui.sans_isis import sans_data_processor_gui
 
+if 'workbench' in sys.modules:
+    from workbench.config import get_window_config
 
-main_window_view = sans_data_processor_gui.SANSDataProcessorGui()
+    parent, flags = get_window_config()
+else:
+    parent, flags = None, None
+
+main_window_view = sans_data_processor_gui.SANSDataProcessorGui(parent, flags)
 
 run_tab_presenter = RunTabPresenter(SANSFacility.ISIS, view=main_window_view)
 
diff --git a/scripts/Interface/reduction_application.py b/scripts/Interface/reduction_application.py
index f8643250e5e3d055ffc4337030f6f796a0d29f77..ac760c9663f7c97009a3f44d4fa02a9e90661cd8 100644
--- a/scripts/Interface/reduction_application.py
+++ b/scripts/Interface/reduction_application.py
@@ -53,8 +53,10 @@ from reduction_gui.settings.application_settings import GeneralSettings  # noqa
 
 
 class ReductionGUI(QMainWindow):
-    def __init__(self, instrument=None, instrument_list=None):
-        QMainWindow.__init__(self)
+    def __init__(self, parent=None, window_flags=None, instrument=None, instrument_list=None):
+        QMainWindow.__init__(self, parent)
+        if window_flags:
+            self.setWindowFlags(window_flags)
         self.ui = load_ui(__file__, 'ui/reduction_main.ui', baseinstance=self)
 
         if STARTUP_WARNING:
diff --git a/scripts/Interface/ui/drill/model/DrillExportModel.py b/scripts/Interface/ui/drill/model/DrillExportModel.py
index 1fad8f1e48cc002db9a7245fadc74b92e2176d4d..7d5a8348abe7409dcac629c8f136b536b0fea772 100644
--- a/scripts/Interface/ui/drill/model/DrillExportModel.py
+++ b/scripts/Interface/ui/drill/model/DrillExportModel.py
@@ -14,6 +14,7 @@ from .DrillAlgorithmPool import DrillAlgorithmPool
 from .DrillTask import DrillTask
 
 import re
+import os
 
 
 class DrillExportModel:
@@ -110,8 +111,8 @@ class DrillExportModel:
         if not criteria:
             return True
 
-        processingAlgo = mtd[ws].getHistory().lastAlgorithm()
         try:
+            processingAlgo = mtd[ws].getHistory().lastAlgorithm()
             params = re.findall("%[a-zA-Z]*%", criteria)
             for param in params:
                 value = processingAlgo.getPropertyValue(param[1:-1])
@@ -178,34 +179,53 @@ class DrillExportModel:
 
     def run(self, sample):
         """
-        Run the export algorithms on a workspace. If the provided workspace is
-        a groupworkspace, the export algorithms will be run on each member of
-        the group.
+        Run the export algorithms on a sample. For each export algorithm, the
+        function will try to validate the criteria (using _validCriteria()) on
+        the output workspace that corresponds to the sample. If the criteria are
+        valid, the export will be run on all workspaces whose name contains the
+        sample name.
 
         Args:
-            workspaceName (str): name of the workspace
+            sample (DrillSample): sample to be exported
         """
         exportPath = config.getString("defaultsave.directory")
+        if not exportPath:
+            exportPath = os.getcwd()
         workspaceName = sample.getOutputName()
 
+        try:
+            outputWs = mtd[workspaceName]
+            if isinstance(outputWs, WorkspaceGroup):
+                names = outputWs.getNames()
+                outputWs = names[0]
+            else:
+                outputWs = workspaceName
+        except:
+            return
+
         tasks = list()
-        for wsName in mtd.getObjectNames():
-            if ((workspaceName in wsName)
-                    and (not isinstance(mtd[wsName], WorkspaceGroup))):
-                for a,s in self._exportAlgorithms.items():
-                    if s:
-                        if not self._validCriteria(wsName, a):
-                            logger.notice("Export of {} with {} was skipped "
-                                          "because the workspace is not "
-                                          "compatible.".format(wsName, a))
-                            continue
-                        filename = exportPath + wsName \
-                                   + RundexSettings.EXPORT_ALGO_EXTENSION[a]
-                        name = wsName + ":" + filename
-                        if wsName not in self._exports:
-                            self._exports[wsName] = set()
-                        self._exports[wsName].add(filename)
-                        task = DrillTask(name, a, InputWorkspace=wsName,
-                                         FileName=filename)
-                        tasks.append(task)
+        for algo,active in self._exportAlgorithms.items():
+            if not active:
+                continue
+            if not self._validCriteria(outputWs, algo):
+                logger.notice("Export of sample {} with {} was skipped "
+                              "because workspaces are not compatible."
+                              .format(outputWs, algo))
+                continue
+
+            for wsName in mtd.getObjectNames(contain=workspaceName):
+                if isinstance(mtd[wsName], WorkspaceGroup):
+                    continue
+
+                filename = os.path.join(
+                        exportPath,
+                        wsName + RundexSettings.EXPORT_ALGO_EXTENSION[algo])
+                name = wsName + ":" + filename
+                if wsName not in self._exports:
+                    self._exports[wsName] = set()
+                self._exports[wsName].add(filename)
+                task = DrillTask(name, algo, InputWorkspace=wsName,
+                                 FileName=filename)
+                tasks.append(task)
+
         self._pool.addProcesses(tasks)
diff --git a/scripts/Interface/ui/drill/test/DrillExportModelTest.py b/scripts/Interface/ui/drill/test/DrillExportModelTest.py
index 90a161aab0327db17883f1f6d2006c374f40e38d..df35463b3de2f006caf2d3de8593603c3872f942 100644
--- a/scripts/Interface/ui/drill/test/DrillExportModelTest.py
+++ b/scripts/Interface/ui/drill/test/DrillExportModelTest.py
@@ -154,9 +154,11 @@ class DrillExportModelTest(unittest.TestCase):
         self.mConfig.getString.return_value = "/default/save/directory/"
         mSample = mock.Mock()
         mSample.getOutputName.return_value = "workspace"
+        mGroup = mock.Mock()
+        mGroup.getNames.return_value = ["workspace"]
+        self.mMtd.__getitem__.return_value = mGroup
         self.mMtd.getObjectNames.return_value = ["workspace_1",
-                                                 "workspace_2",
-                                                 "test_1"]
+                                                 "workspace_2"]
         self.exportModel._validCriteria = mock.Mock()
         self.exportModel._validCriteria.return_value = True
         self.exportModel.run(mSample)
diff --git a/scripts/Interface/ui/drill/view/DrillView.py b/scripts/Interface/ui/drill/view/DrillView.py
index 0906647f3e9343dfd6a07c7fde4e6ecd7d9ebea4..4542ff77a3f938454f4abcec127cd39366e4a04a 100644
--- a/scripts/Interface/ui/drill/view/DrillView.py
+++ b/scripts/Interface/ui/drill/view/DrillView.py
@@ -141,8 +141,10 @@ class DrillView(QMainWindow):
     ERROR_COLOR = "#3fff0000"
     PROCESSING_COLOR = "#3fffff00"
 
-    def __init__(self):
-        super(DrillView, self).__init__(None, Qt.Window)
+    def __init__(self, parent=None, window_flags=None):
+        super(DrillView, self).__init__(parent)
+        if window_flags:
+            self.setWindowFlags(window_flags)
         self.here = os.path.dirname(os.path.realpath(__file__))
 
         # help
diff --git a/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py b/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py
index a4812384cca0cba926af9156f7ac7b04d941af34..75a7c3fc70caa1049d74483802100803ef5377bb 100644
--- a/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py
+++ b/scripts/Interface/ui/sans_isis/sans_data_processor_gui.py
@@ -154,12 +154,13 @@ class SANSDataProcessorGui(QMainWindow,
             """
             pass
 
-    def __init__(self):
+    def __init__(self, parent=None, window_flags=None):
         """
         Initialise the interface
         """
-        super(QMainWindow, self).__init__()
-
+        super(QMainWindow, self).__init__(parent)
+        if window_flags:
+            self.setWindowFlags(window_flags)
         self.setupUi(self)
 
         # Listeners allow us to to notify all presenters
diff --git a/scripts/Muon/GUI/Common/contexts/muon_context.py b/scripts/Muon/GUI/Common/contexts/muon_context.py
index 4ad58e9bf49be35486fbe1bd53b94c14a6d49844..a3e1406bd9ffcc313e69ef6ba18665afc046ad6c 100644
--- a/scripts/Muon/GUI/Common/contexts/muon_context.py
+++ b/scripts/Muon/GUI/Common/contexts/muon_context.py
@@ -24,7 +24,7 @@ from Muon.GUI.Common.contexts.muon_context_ADS_observer import MuonContextADSObs
 from Muon.GUI.Common.ADSHandler.muon_workspace_wrapper import MuonWorkspaceWrapper, WorkspaceGroupDefinition
 from mantidqt.utils.observer_pattern import Observable
 from Muon.GUI.Common.muon_pair import MuonPair
-from Muon.GUI.Common.muon_group import MuonDiff
+from Muon.GUI.Common.muon_diff import MuonDiff
 from typing import List
 
 
@@ -109,14 +109,14 @@ class MuonContext(object):
 
     def calculate_diff(self, diff: MuonDiff, run: List[int], rebin: bool=False):
         try:
-            forward_group_workspace_name = self._group_pair_context[diff.forward_group].get_asymmetry_workspace_for_run(run, rebin)
-            backward_group_workspace_name = self._group_pair_context[diff.backward_group].get_asymmetry_workspace_for_run(run, rebin)
+            positive_workspace_name = self._group_pair_context[diff.positive].get_asymmetry_workspace_for_run(run, rebin)
+            negative_workspace_name = self._group_pair_context[diff.negative].get_asymmetry_workspace_for_run(run, rebin)
         except KeyError:
             # A key error here means the requested workspace does not exist so return None
             return None
         run_as_string = run_list_to_string(run)
         output_workspace_name = get_diff_asymmetry_name(self, diff.name, run_as_string, rebin=rebin)
-        return calculate_diff_data(diff, forward_group_workspace_name, backward_group_workspace_name, output_workspace_name)
+        return calculate_diff_data(diff, positive_workspace_name, negative_workspace_name, output_workspace_name)
 
     def calculate_pair(self, pair: MuonPair, run: List[int], rebin: bool=False):
         try:
diff --git a/scripts/Muon/GUI/Common/contexts/muon_group_pair_context.py b/scripts/Muon/GUI/Common/contexts/muon_group_pair_context.py
index 278db8917953c5359bcd8eca9a23d276b5915f8f..77680d1a18172044b977e0041d63cd8a28938500 100644
--- a/scripts/Muon/GUI/Common/contexts/muon_group_pair_context.py
+++ b/scripts/Muon/GUI/Common/contexts/muon_group_pair_context.py
@@ -7,7 +7,8 @@
 import os
 from math import floor
 import Muon.GUI.Common.utilities.xml_utils as xml_utils
-from Muon.GUI.Common.muon_group import MuonGroup, MuonDiff
+from Muon.GUI.Common.muon_diff import MuonDiff
+from Muon.GUI.Common.muon_group import MuonGroup
 from Muon.GUI.Common.muon_pair import MuonPair
 from Muon.GUI.Common.muon_phasequad import MuonPhasequad
 from Muon.GUI.Common.muon_base_pair import MuonBasePair
diff --git a/scripts/Muon/GUI/Common/difference_table_widget/difference_table_widget_presenter.py b/scripts/Muon/GUI/Common/difference_table_widget/difference_table_widget_presenter.py
index 884428f75f445c3a738db5f236430e937ccfbcbb..ce681812eb38bd53c5866168f0aa29c8445fee22 100644
--- a/scripts/Muon/GUI/Common/difference_table_widget/difference_table_widget_presenter.py
+++ b/scripts/Muon/GUI/Common/difference_table_widget/difference_table_widget_presenter.py
@@ -6,7 +6,7 @@
 # SPDX - License - Identifier: GPL - 3.0 +
 import re
 
-from Muon.GUI.Common.muon_group import MuonDiff
+from Muon.GUI.Common.muon_diff import MuonDiff
 from Muon.GUI.Common.utilities.run_string_utils import valid_name_regex
 from mantidqt.utils.observer_pattern import GenericObservable
 from Muon.GUI.Common.grouping_tab_widget.grouping_tab_widget_model import RowValid
@@ -32,6 +32,11 @@ class DifferenceTablePresenter(object):
 
         self._dataChangedNotifier = lambda: 0
 
+        if group_or_pair == 'pair':
+            self._view.set_table_headers_pairs()
+        else:
+            self._view.set_table_headers_groups()
+
     def show(self):
         self._view.show()
 
@@ -57,10 +62,10 @@ class DifferenceTablePresenter(object):
             update_model = False
         if diff_columns[col] == 'group_1':
             if changed_item_text == self._view.get_table_item_text(row, diff_columns.index('group_2')):
-                table[row][diff_columns.index('group_2')] = self._model.get_diffs(self._group_or_pair)[row].forward_group
+                table[row][diff_columns.index('group_2')] = self._model.get_diffs(self._group_or_pair)[row].positive
         if diff_columns[col] == 'group_2':
             if changed_item_text == self._view.get_table_item_text(row, diff_columns.index('group_1')):
-                table[row][diff_columns.index('group_1')] = self._model.get_diffs(self._group_or_pair)[row].backward_group
+                table[row][diff_columns.index('group_1')] = self._model.get_diffs(self._group_or_pair)[row].negative
         if diff_columns[col] == 'to_analyse':
             update_model = False
             self.to_analyse_data_checkbox_changed(changed_item.checkState(), row, diff_name)
@@ -90,10 +95,10 @@ class DifferenceTablePresenter(object):
         for diff in self._model.diffs:
             if isinstance(diff, MuonDiff) and diff.group_or_pair == self._group_or_pair:
                 to_analyse = True if diff.name in self._model.selected_diffs else False
-                forward_group_periods = self._model._context.group_pair_context[diff.forward_group].periods
-                backward_group_periods = self._model._context.group_pair_context[diff.backward_group].periods
-                forward_period_warning = self._model.validate_periods_list(forward_group_periods)
-                backward_period_warning = self._model.validate_periods_list(backward_group_periods)
+                positive_periods = self._model._context.group_pair_context[diff.positive].periods
+                negative_periods = self._model._context.group_pair_context[diff.negative].periods
+                forward_period_warning = self._model.validate_periods_list(positive_periods)
+                backward_period_warning = self._model.validate_periods_list(negative_periods)
                 if forward_period_warning==RowValid.invalid_for_all_runs or backward_period_warning == RowValid.invalid_for_all_runs:
                     display_period_warning = RowValid.invalid_for_all_runs
                 elif forward_period_warning==RowValid.valid_for_some_runs or backward_period_warning == RowValid.valid_for_some_runs:
@@ -145,7 +150,7 @@ class DifferenceTablePresenter(object):
         self._view.disable_updates()
         self.update_group_selections()
         assert isinstance(diff, MuonDiff)
-        entry = [str(diff.name), to_analyse, str(diff.forward_group), str(diff.backward_group)]
+        entry = [str(diff.name), to_analyse, str(diff.positive), str(diff.negative)]
         self._view.add_entry_to_table(entry, color, tool_tip)
         self._view.enable_updates()
 
diff --git a/scripts/Muon/GUI/Common/difference_table_widget/difference_table_widget_view.py b/scripts/Muon/GUI/Common/difference_table_widget/difference_table_widget_view.py
index 36b2925a970d999dade484c0156f2fc0d6832357..fb6cddbb7cb05112c9b54f5aaea8d3555e021046 100644
--- a/scripts/Muon/GUI/Common/difference_table_widget/difference_table_widget_view.py
+++ b/scripts/Muon/GUI/Common/difference_table_widget/difference_table_widget_view.py
@@ -87,25 +87,11 @@ class DifferenceTableView(QtWidgets.QWidget):
 
     def set_up_table(self):
         self.diff_table.setColumnCount(4)
-        self.diff_table.setHorizontalHeaderLabels(["diff Name", "Analyse (plot/fit)", "Group 1", " Group 2"])
-        header = self.diff_table.horizontalHeader()
-        header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
-        header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
-        header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
-        header.setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch)
-
         vertical_headers = self.diff_table.verticalHeader()
         vertical_headers.setSectionsMovable(False)
         vertical_headers.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
         vertical_headers.setVisible(True)
 
-        self.diff_table.horizontalHeaderItem(0).setToolTip("The name of the diff :"
-                                                           "\n    - The name must be unique across all groups/diffs"
-                                                           "\n    - The name can only use digits, characters and _")
-        self.diff_table.horizontalHeaderItem(2).setToolTip("Group 1 of the diff, selected from the grouping table")
-        self.diff_table.horizontalHeaderItem(3).setToolTip("Group 2 of the diff, selected from the grouping table")
-        self.diff_table.horizontalHeaderItem(1).setToolTip("Whether to include this diff in the analysis")
-
     def num_rows(self):
         return self.diff_table.rowCount()
 
@@ -359,3 +345,35 @@ class DifferenceTableView(QtWidgets.QWidget):
         self.diff_table.blockSignals(True)
         item.setCheckState(checked_state)
         self.diff_table.blockSignals(False)
+
+    def set_table_headers_pairs(self):
+        self.diff_table.setHorizontalHeaderLabels(["Diff Name", "Analyse (plot/fit)", "Pair 1", "Pair 2"])
+
+        header = self.diff_table.horizontalHeader()
+        header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
+        header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
+        header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
+        header.setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch)
+
+        self.diff_table.horizontalHeaderItem(0).setToolTip("The name of the diff :"
+                                                           "\n    - The name must be unique across all groups/diffs"
+                                                           "\n    - The name can only use digits, characters and _")
+        self.diff_table.horizontalHeaderItem(2).setToolTip("Pair 1 of the diff, selected from the pair table")
+        self.diff_table.horizontalHeaderItem(3).setToolTip("Pair 2 of the diff, selected from the pair table")
+        self.diff_table.horizontalHeaderItem(1).setToolTip("Whether to include this diff in the analysis")
+
+    def set_table_headers_groups(self):
+        self.diff_table.setHorizontalHeaderLabels(["Diff Name", "Analyse (plot/fit)", "Group 1", "Group 2"])
+
+        header = self.diff_table.horizontalHeader()
+        header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
+        header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
+        header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
+        header.setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch)
+
+        self.diff_table.horizontalHeaderItem(0).setToolTip("The name of the diff :"
+                                                           "\n    - The name must be unique across all groups/diffs"
+                                                           "\n    - The name can only use digits, characters and _")
+        self.diff_table.horizontalHeaderItem(2).setToolTip("Group 1 of the diff, selected from the group table")
+        self.diff_table.horizontalHeaderItem(3).setToolTip("Group 2 of the diff, selected from the group table")
+        self.diff_table.horizontalHeaderItem(1).setToolTip("Whether to include this diff in the analysis")
diff --git a/scripts/Muon/GUI/Common/difference_table_widget/difference_widget_view.py b/scripts/Muon/GUI/Common/difference_table_widget/difference_widget_view.py
index 507e30b5df7e8917f84db4d63837d03c1ccc682f..c1e1e03b22a93c27cdbcf6b3544f04fd21b7dcf2 100644
--- a/scripts/Muon/GUI/Common/difference_table_widget/difference_widget_view.py
+++ b/scripts/Muon/GUI/Common/difference_table_widget/difference_widget_view.py
@@ -46,6 +46,7 @@ class DifferenceView(QtWidgets.QWidget):
         self.vertical_layout.addLayout(self.horizontal_layout)
         self.vertical_layout.addWidget(self._group_table)
         self.vertical_layout.addWidget(self._pair_table)
+        self.vertical_layout.setContentsMargins(0,0,0,0)
         self._group_table.hide()
         self.setLayout(self.vertical_layout)
 
diff --git a/scripts/Muon/GUI/Common/grouping_tab_widget/grouping_tab_widget_model.py b/scripts/Muon/GUI/Common/grouping_tab_widget/grouping_tab_widget_model.py
index 444dfe9a0da3ca89f6ec83a6e3e65789645a24bc..cd9080257406d98462d11ff83ee84f7560dfabed 100644
--- a/scripts/Muon/GUI/Common/grouping_tab_widget/grouping_tab_widget_model.py
+++ b/scripts/Muon/GUI/Common/grouping_tab_widget/grouping_tab_widget_model.py
@@ -5,7 +5,8 @@
 #   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 # SPDX - License - Identifier: GPL - 3.0 +
 from Muon.GUI.Common.contexts.muon_data_context import construct_empty_group, construct_empty_pair
-from Muon.GUI.Common.muon_group import MuonGroup, MuonDiff
+from Muon.GUI.Common.muon_diff import MuonDiff
+from Muon.GUI.Common.muon_group import MuonGroup
 from Muon.GUI.Common.muon_pair import MuonPair
 from Muon.GUI.Common.muon_group import MuonRun
 from enum import Enum
@@ -96,7 +97,7 @@ class GroupingTabModel(object):
 
     @property
     def selected_groups_and_pairs(self):
-        return self.selected_groups+self.selected_pairs+self.selected_diffs
+        return self._groups_and_pairs.selected_groups_and_pairs
 
     def show_all_groups_and_pairs(self):
         self._context.show_all_groups()
@@ -139,24 +140,27 @@ class GroupingTabModel(object):
         for pair in self._groups_and_pairs.pairs:
             if name == pair.forward_group or name == pair.backward_group:
                 used_by += pair.name + ", "
-        for diff in self._groups_and_pairs.diffs:
-            if name == diff.forward_group or name == diff.backward_group:
-                used_by += diff.name +", "
+        # Check diffs
+        used_by_diffs = self.check_if_used_by_diff(name, True)
+        if used_by_diffs:
+            used_by += used_by_diffs + ", "
         if used_by:
             # the -2 removes the space and comma
-            return name + " is used by: "+ used_by[0:-2]
+            return name + " is used by: " + used_by[0:-2]
         else:
             return used_by
 
-    def check_pair_in_use(self, name):
-        used_by = ""
+    def check_if_used_by_diff(self, name, called_from_check_group=False):
         # check diffs
+        used_by = ""
         for diff in self._groups_and_pairs.diffs:
-            if name == diff.forward_group or name == diff.backward_group:
-                used_by += diff.name +", "
+            if name == diff.positive or name == diff.negative:
+                used_by += diff.name + ", "
         if used_by:
             # the -2 removes the space and comma
-            return name + " is used by: "+ used_by[0:-2]
+            if called_from_check_group:
+                return used_by[0:-2]
+            return name + " is used by: " + used_by[0:-2]
         else:
             return used_by
 
@@ -193,12 +197,6 @@ class GroupingTabModel(object):
     def remove_groups_by_name(self, name_list):
         for name in name_list:
             self._groups_and_pairs.remove_group(name)
-            self.remove_pairs_with_removed_name(name)
-
-    def remove_pairs_with_removed_name(self, group_name):
-        for pair in self._groups_and_pairs.pairs:
-            if pair.forward_group == group_name or pair.backward_group == group_name:
-                self._groups_and_pairs.remove_pair(pair.name)
 
     def remove_pairs_by_name(self, name_list):
         for name in name_list:
diff --git a/scripts/Muon/GUI/Common/grouping_tab_widget/grouping_tab_widget_presenter.py b/scripts/Muon/GUI/Common/grouping_tab_widget/grouping_tab_widget_presenter.py
index 7235a57bbed90624c95ad726fc322dbf38dd5568..72179eb40981644e5a054d99b85794d6131125f8 100644
--- a/scripts/Muon/GUI/Common/grouping_tab_widget/grouping_tab_widget_presenter.py
+++ b/scripts/Muon/GUI/Common/grouping_tab_widget/grouping_tab_widget_presenter.py
@@ -159,9 +159,9 @@ class GroupingTabPresenter(object):
                 self._view.display_warning_box(str(error))
         for diff in diffs:
             try:
-                if diff.forward_group in self._model.group_names and diff.backward_group in self._model.group_names:
+                if diff.positive in self._model.group_names and diff.negative in self._model.group_names:
                     self._model.add_diff(diff)
-                elif diff.forward_group in self._model.pair_names and diff.backward_group in self._model.pair_names:
+                elif diff.positive in self._model.pair_names and diff.negative in self._model.pair_names:
                     self._model.add_diff(diff)
             except ValueError as error:
                 self._view.display_warning_box(str(error))
@@ -174,8 +174,8 @@ class GroupingTabPresenter(object):
                 self._model.add_pair_to_analysis(default)
 
         self.grouping_table_widget.update_view_from_model()
-        self.diff_table.update_view_from_model()
         self.pairing_table_widget.update_view_from_model()
+        self.diff_table.update_view_from_model()
         self.update_description_text(description)
         self._model._context.group_pair_context.selected = default
         self.plot_default_groups_or_pairs()
diff --git a/scripts/Muon/GUI/Common/muon_base_pair.py b/scripts/Muon/GUI/Common/muon_base_pair.py
index 7229bdf53a1d8e6c8541a08f03733f1ee956dd6f..76a029d1d7e0ae390ac6fd2695fc060e7f657fa6 100644
--- a/scripts/Muon/GUI/Common/muon_base_pair.py
+++ b/scripts/Muon/GUI/Common/muon_base_pair.py
@@ -10,7 +10,7 @@ from Muon.GUI.Common.muon_base import MuonBase
 
 class MuonBasePair(MuonBase):
     def __init__(self, pair_name, periods=[1]):
-        super(MuonBasePair,self).__init__(pair_name, periods)
+        super(MuonBasePair, self).__init__(pair_name, periods)
 
     def get_asymmetry_workspace_for_run(self, run, rebin):
         # return the first element as it will always be a single value list
diff --git a/scripts/Muon/GUI/Common/muon_diff.py b/scripts/Muon/GUI/Common/muon_diff.py
new file mode 100644
index 0000000000000000000000000000000000000000..b5fcbb2a7c714e75222b11c756cfcced704e1265
--- /dev/null
+++ b/scripts/Muon/GUI/Common/muon_diff.py
@@ -0,0 +1,36 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
+#   NScD Oak Ridge National Laboratory, European Spallation Source,
+#   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+# SPDX - License - Identifier: GPL - 3.0 +
+# pylint: disable=C0111
+from Muon.GUI.Common.muon_base import MuonBase
+
+
+class MuonDiff(MuonBase):
+    """
+    Simple structure to store information on a difference.
+    - The name is set at initialization and after that cannot be changed.
+    - The difference has two properies positive and negative used in the calculation
+    - The difference can be between two groups or two pairs only
+    - The workspace associated to the difference can be set, but must be of type MuonWorkspaceWrapper.
+    """
+
+    def __init__(self, diff_name, positive, negative, group_or_pair="group", periods=[1]):
+        super(MuonDiff, self).__init__(diff_name, periods)
+        self._positive = positive
+        self._negative = negative
+        self._group_or_pair = group_or_pair
+
+    @property
+    def positive(self):
+        return self._positive
+
+    @property
+    def negative(self):
+        return self._negative
+
+    @property
+    def group_or_pair(self):
+        return self._group_or_pair
diff --git a/scripts/Muon/GUI/Common/muon_group.py b/scripts/Muon/GUI/Common/muon_group.py
index 661204a13eed49f46580ea2e8c1aba1d7b3edb45..2409f0661bc31af73e5c84e60216b047bdc1a0c8 100644
--- a/scripts/Muon/GUI/Common/muon_group.py
+++ b/scripts/Muon/GUI/Common/muon_group.py
@@ -6,31 +6,11 @@
 # SPDX - License - Identifier: GPL - 3.0 +
 # pylint: disable=C0111
 from Muon.GUI.Common.ADSHandler.muon_workspace_wrapper import MuonWorkspaceWrapper
-from Muon.GUI.Common.muon_base import MuonRun, MuonBase
+from Muon.GUI.Common.muon_base import MuonRun
 from typing import List
 import itertools
 
 
-class MuonDiff(MuonBase):
-    def __init__(self, diff_name, positive, negative, group_or_pair="group", periods =[1]):
-        super(MuonDiff, self).__init__(diff_name, periods)
-        self._positive = positive
-        self._negative = negative
-        self._group_or_pair = group_or_pair
-
-    @property
-    def forward_group(self):
-        return self._positive
-
-    @property
-    def backward_group(self):
-        return self._negative
-
-    @property
-    def group_or_pair(self):
-        return self._group_or_pair
-
-
 class MuonGroup(object):
     """
     Simple structure to store information on a detector group.
diff --git a/scripts/Muon/GUI/Common/muon_pair.py b/scripts/Muon/GUI/Common/muon_pair.py
index 93e42cab5fe60be788e3d40ccf4123af932bed9d..8ca69b942cab33997fac2b0d9aa0c9c694dc6336 100644
--- a/scripts/Muon/GUI/Common/muon_pair.py
+++ b/scripts/Muon/GUI/Common/muon_pair.py
@@ -21,8 +21,8 @@ class MuonPair(MuonBasePair):
     def __init__(self, pair_name,
                  forward_group_name="",
                  backward_group_name="",
-                 alpha=1.0, periods = [1]):
-        super().__init__(pair_name,periods)
+                 alpha=1.0, periods=[1]):
+        super().__init__(pair_name, periods)
         self._forward_group_name = forward_group_name
         self._backward_group_name = backward_group_name
         self._alpha = float(alpha)
diff --git a/scripts/Muon/GUI/Common/pairing_table_widget/pairing_table_widget_presenter.py b/scripts/Muon/GUI/Common/pairing_table_widget/pairing_table_widget_presenter.py
index 29b98aaeb7544390ee0031d7ac8392d8925b00c7..67804a8583b42b9dd9d458916c893b81b14e2a62 100644
--- a/scripts/Muon/GUI/Common/pairing_table_widget/pairing_table_widget_presenter.py
+++ b/scripts/Muon/GUI/Common/pairing_table_widget/pairing_table_widget_presenter.py
@@ -210,7 +210,7 @@ class PairingTablePresenter(object):
         safe_to_rm = []
         warnings = ""
         for name, index in pair_names:
-            used_by = self._model.check_pair_in_use(name)
+            used_by = self._model.check_if_used_by_diff(name)
             if used_by:
                 warnings+=used_by+"\n"
             else:
@@ -225,7 +225,7 @@ class PairingTablePresenter(object):
     def remove_last_row_in_view_and_model(self):
         if self._view.num_rows() > 0:
             name = self._view.get_table_contents()[-1][0]
-            warning = self._model.check_pair_in_use(name)
+            warning = self._model.check_if_used_by_diff(name)
             if warning:
                 self._view.warning_popup(warning)
             else:
diff --git a/scripts/Muon/GUI/Common/utilities/algorithm_utils.py b/scripts/Muon/GUI/Common/utilities/algorithm_utils.py
index 113871dc1161c88372f3e364aa3ca8172a9064bd..2f08d8f6703bf562761cdf3727a62d305c5c76d9 100644
--- a/scripts/Muon/GUI/Common/utilities/algorithm_utils.py
+++ b/scripts/Muon/GUI/Common/utilities/algorithm_utils.py
@@ -289,12 +289,12 @@ def apply_deadtime(ws, output, table):
     return alg.getProperty("OutputWorkspace").valueAsStr
 
 
-def calculate_diff_data(diff, forward_group_workspace_name, backward_group_workspace_name, output):
+def calculate_diff_data(diff, positive_workspace_name, negative_workspace_name, output):
     alg = mantid.AlgorithmManager.create("Minus")
     alg.initialize()
     alg.setAlwaysStoreInADS(True)
-    alg.setProperty("LHSWorkspace", forward_group_workspace_name)
-    alg.setProperty("RHSWorkspace", backward_group_workspace_name)
+    alg.setProperty("LHSWorkspace", positive_workspace_name)
+    alg.setProperty("RHSWorkspace", negative_workspace_name)
     alg.setProperty("OutputWorkspace", output)
     alg.execute()
     return alg.getProperty("OutputWorkspace").valueAsStr
diff --git a/scripts/Muon/GUI/Common/utilities/xml_utils.py b/scripts/Muon/GUI/Common/utilities/xml_utils.py
index e9341b55b116930734d66b965225d7ecfb7cdf05..57c2b5df333b8be0c2ad575cbdd8e14a7d074dea 100644
--- a/scripts/Muon/GUI/Common/utilities/xml_utils.py
+++ b/scripts/Muon/GUI/Common/utilities/xml_utils.py
@@ -9,7 +9,8 @@ import xml.etree.ElementTree as ET
 import xml.dom.minidom as MD
 import Muon.GUI.Common.utilities.run_string_utils as run_string_utils
 
-from Muon.GUI.Common.muon_group import MuonGroup, MuonDiff
+from Muon.GUI.Common.muon_diff import MuonDiff
+from Muon.GUI.Common.muon_group import MuonGroup
 from Muon.GUI.Common.muon_pair import MuonPair
 
 
@@ -34,9 +35,11 @@ def _create_XML_subElement_for_pairs(root_node, pairs):
         fwd_group = ET.SubElement(child, 'forward-group', val=pair.forward_group)
         bwd_group = ET.SubElement(child, 'backward-group', val=pair.backward_group)
         alpha = ET.SubElement(child, 'alpha', val=str(pair.alpha))
+        periods = ET.SubElement(child, 'periods', val=pair.periods)
         child.extend(fwd_group)
         child.extend(bwd_group)
         child.extend(alpha)
+        child.extend(periods)
         pair_nodes += [child]
     return pair_nodes
 
@@ -45,12 +48,12 @@ def _create_XML_subElement_for_diffs(root_node, diffs):
     diff_nodes = []
     for diff in diffs:
         child = ET.SubElement(root_node, 'diff', name=diff.name)
-        fwd_group = ET.SubElement(child, 'positive-group', val=diff.forward_group)
-        bwd_group = ET.SubElement(child, 'negative-group', val=diff.backward_group)
+        positive = ET.SubElement(child, 'positive', val=diff.positive)
+        negative = ET.SubElement(child, 'negative', val=diff.negative)
         group_or_pair = ET.SubElement(child, 'group-or-pair', val=diff.group_or_pair)
         periods = ET.SubElement(child, 'periods', val=diff.periods)
-        child.extend(fwd_group)
-        child.extend(bwd_group)
+        child.extend(positive)
+        child.extend(negative)
         child.extend(group_or_pair)
         child.extend(periods)
         diff_nodes += [child]
@@ -116,24 +119,33 @@ def load_grouping_from_XML(filename):
         default = ''
 
     group_names, group_ids, periods = _get_groups_from_XML(root)
-    pair_names, pair_groups, pair_alphas = _get_pairs_from_XML(root)
-    diff_names, diff_groups = _get_diffs_from_XML(root)
+    pair_names, pair_groups, pair_alphas, pair_periods = _get_pairs_from_XML(root)
+    diff_names, diff_groups, diff_periods = _get_diffs_from_XML(root)
     groups, diffs, pairs = [], [], []
 
     for i, group_name in enumerate(group_names):
-        period = periods[i] if periods and i<len(periods) else [1]
+        period = periods[i] if periods and i < len(periods) else [1]
         groups += [MuonGroup(group_name=group_name,
                              detector_ids=group_ids[i], periods=period)]
 
     for i, pair_name in enumerate(pair_names):
+        if pair_periods:
+            pair_periods_converted = _convert_periods_to_int(pair_periods[i])
+        else:
+            pair_periods_converted = [1]
+
         pairs += [MuonPair(pair_name=pair_name,
                            forward_group_name=pair_groups[i][0],
                            backward_group_name=pair_groups[i][1],
-                           alpha=pair_alphas[i])]
+                           alpha=pair_alphas[i],
+                           periods=pair_periods_converted)]
 
     for i, diff_name in enumerate(diff_names):
-        diffs +=[MuonDiff(diff_name,diff_groups[i][0],
-                          diff_groups[i][1], diff_groups[i][2], diff_groups[i][3])]
+        if diff_periods:
+            diff_periods_converted = _convert_periods_to_int(diff_periods[i])
+        else:
+            diff_periods_converted = [1]
+        diffs += [MuonDiff(diff_name, diff_groups[i][0], diff_groups[i][1], diff_groups[i][2], diff_periods_converted)]
 
     return groups, pairs, diffs, description, default
 
@@ -153,20 +165,38 @@ def _get_groups_from_XML(root):
 
 
 def _get_diffs_from_XML(root):
-    names, groups = [], []
+    names, groups, periods = [], [], []
     for child in root:
         if child.tag == "diff":
             names += [child.attrib['name']]
-            groups += [[child.find('positive-group').attrib['val'], child.find('negative-group').attrib['val'],
-                        child.find('group-or-pair').attrib['val'], child.find('periods').attrib['val'] ]]
-    return names, groups
+            groups += [[child.find('positive').attrib['val'], child.find('negative').attrib['val'],
+                        child.find('group-or-pair').attrib['val']]]
+            periods += [child.find('periods').attrib['val']]
+    return names, groups, periods
 
 
 def _get_pairs_from_XML(root):
-    names, groups, alphas = [], [], []
+    names, groups, alphas, periods = [], [], [], []
     for child in root:
         if child.tag == "pair":
             names += [child.attrib['name']]
             groups += [[child.find('forward-group').attrib['val'], child.find('backward-group').attrib['val']]]
             alphas += [child.find('alpha').attrib['val']]
-    return names, groups, alphas
+            try:
+                periods += [child.find('periods').attrib['val']]
+            except AttributeError:
+                pass
+    return names, groups, alphas, periods
+
+
+def _convert_periods_to_int(periods):
+    converted_periods = []
+    if periods:
+        for p in periods:
+            try:
+                converted_periods += [int(p)]
+            except ValueError:
+                continue
+    else:
+        converted_periods = [1]
+    return converted_periods
diff --git a/scripts/Muon/GUI/ElementalAnalysis/elemental_analysis.py b/scripts/Muon/GUI/ElementalAnalysis/elemental_analysis.py
index 770167df2499d393e36acd87cf6519230e9fb9eb..0ef3f91f402bc73fc6a573e00922c0b6e991e110 100644
--- a/scripts/Muon/GUI/ElementalAnalysis/elemental_analysis.py
+++ b/scripts/Muon/GUI/ElementalAnalysis/elemental_analysis.py
@@ -67,8 +67,10 @@ class ElementalAnalysisGui(QtWidgets.QMainWindow):
     ORANGE = 'C1'
     GREEN = 'C2'
 
-    def __init__(self, parent=None):
+    def __init__(self, parent=None, window_flags=None):
         super(ElementalAnalysisGui, self).__init__(parent)
+        if window_flags:
+            self.setWindowFlags(window_flags)
         # set menu
         self.menu = self.menuBar()
         self.menu.addAction("File")
diff --git a/scripts/Muon/GUI/FrequencyDomainAnalysis/frequency_domain_analysis_2.py b/scripts/Muon/GUI/FrequencyDomainAnalysis/frequency_domain_analysis_2.py
index 12800b77f0f8cce627dc6a36c558c0f78083370c..9f542b33a80c842c068818b2f11bfabfc79f1a14 100644
--- a/scripts/Muon/GUI/FrequencyDomainAnalysis/frequency_domain_analysis_2.py
+++ b/scripts/Muon/GUI/FrequencyDomainAnalysis/frequency_domain_analysis_2.py
@@ -61,8 +61,10 @@ class FrequencyAnalysisGui(QtWidgets.QMainWindow):
     def warning_popup(message):
         message_box.warning(str(message))
 
-    def __init__(self, parent=None):
+    def __init__(self, parent=None, window_flags=None):
         super(FrequencyAnalysisGui, self).__init__(parent)
+        if window_flags:
+            self.setWindowFlags(window_flags)
         self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
         self.setFocusPolicy(QtCore.Qt.StrongFocus)
 
diff --git a/scripts/Muon/GUI/MuonAnalysis/muon_analysis_2.py b/scripts/Muon/GUI/MuonAnalysis/muon_analysis_2.py
index 67312ce9af4f34922494cbb14eaf885feb1244cb..3abf948fa76d0943d4ecfe5e836390f966b48eb8 100644
--- a/scripts/Muon/GUI/MuonAnalysis/muon_analysis_2.py
+++ b/scripts/Muon/GUI/MuonAnalysis/muon_analysis_2.py
@@ -58,8 +58,10 @@ class MuonAnalysisGui(QtWidgets.QMainWindow):
     def warning_popup(message):
         message_box.warning(str(message))
 
-    def __init__(self, parent=None):
+    def __init__(self, parent=None, window_flags=None):
         super(MuonAnalysisGui, self).__init__(parent)
+        if window_flags:
+            self.setWindowFlags(window_flags)
         self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
         self.setFocusPolicy(QtCore.Qt.StrongFocus)
         self.setObjectName("MuonAnalysis2")
diff --git a/scripts/Muon_Analysis.py b/scripts/Muon_Analysis.py
index 16eec10ad2762f00809a388247f8398487458a33..aab9ba80b5fa850725e2e30a183b419b7b04c858 100644
--- a/scripts/Muon_Analysis.py
+++ b/scripts/Muon_Analysis.py
@@ -8,9 +8,17 @@
 from Muon.GUI.MuonAnalysis.muon_analysis_2 import MuonAnalysisGui
 from qtpy import QtCore
 from Muon.GUI.Common.usage_report import report_interface_startup
+import sys
 
 Name = "Muon_Analysis_2"
 
+if 'workbench' in sys.modules:
+    from workbench.config import get_window_config
+
+    parent, flags = get_window_config()
+else:
+    parent, flags = None, None
+
 if 'muon_analysis' in globals():
     muon_analysis = globals()['muon_analysis']
     # If the object is deleted in the C++ side it can still exist in the
@@ -25,12 +33,12 @@ if 'muon_analysis' in globals():
             ) & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
         muon_analysis.activateWindow()
     else:
-        muon_analysis = MuonAnalysisGui()
+        muon_analysis = MuonAnalysisGui(parent, flags)
         report_interface_startup(Name)
         muon_analysis.resize(700, 700)
         muon_analysis.show()
 else:
-    muon_analysis = MuonAnalysisGui()
+    muon_analysis = MuonAnalysisGui(parent, flags)
     report_interface_startup(Name)
     muon_analysis.resize(700, 700)
     muon_analysis.show()
diff --git a/scripts/ORNL_SANS.py b/scripts/ORNL_SANS.py
index dbadb86ac2789cf5bbf7e4f0cd0f8b270f80565c..556d40e7a0bbd78615bb02c6e0bf6da12a93c575 100644
--- a/scripts/ORNL_SANS.py
+++ b/scripts/ORNL_SANS.py
@@ -8,8 +8,15 @@
 """
     Script used to start the HFIR SANS reduction gui from Mantidplot
 """
+import sys
 from reduction_application import ReductionGUI
 
-reducer = ReductionGUI(instrument_list=["BIOSANS", "GPSANS", "EQSANS"])
+if 'workbench' in sys.modules:
+    from workbench.config import get_window_config
+
+    parent, flags = get_window_config()
+else:
+    parent, flags = None, None
+reducer = ReductionGUI(parent, flags, instrument_list=["BIOSANS", "GPSANS", "EQSANS"])
 if reducer.setup_layout(load_last=True):
     reducer.show()
diff --git a/scripts/Powder_Diffraction_Reduction.py b/scripts/Powder_Diffraction_Reduction.py
index 5e1862c96b92c95ac40e4f31239adf440c64c073..ae8701abc1a6761720b3f9665893ef8837081687 100644
--- a/scripts/Powder_Diffraction_Reduction.py
+++ b/scripts/Powder_Diffraction_Reduction.py
@@ -9,10 +9,17 @@
     Script used to start the DGS reduction GUI from MantidPlot
 """
 import os
+import sys
 
 from reduction_application import ReductionGUI
 
-reducer = ReductionGUI(instrument_list=["PG3", "NOM", "VULCAN"])
+if 'workbench' in sys.modules:
+    from workbench.config import get_window_config
+
+    parent, flags = get_window_config()
+else:
+    parent, flags = None, None
+reducer = ReductionGUI(parent, flags, instrument_list=["PG3", "NOM", "VULCAN"])
 if reducer.setup_layout(load_last=True):
 
     # Set up reduction configuration from previous usage
diff --git a/scripts/PyChop.py b/scripts/PyChop.py
index 378c977791c500e4c78a11091eb95746b4110911..c1138450db8d50ecee2115ee9d78fde955a2db69 100644
--- a/scripts/PyChop.py
+++ b/scripts/PyChop.py
@@ -16,7 +16,13 @@ from mantidqt.gui_helper import set_matplotlib_backend, get_qapplication
 set_matplotlib_backend()  # must be called before anything tries to use matplotlib
 
 app, within_mantid = get_qapplication()
-window = PyChopGui.PyChopGui()
+if 'workbench' in sys.modules:
+    from workbench.config import get_window_config
+
+    parent, flags = get_window_config()
+else:
+    parent, flags = None, None
+window = PyChopGui.PyChopGui(parent, flags)
 window.show()
 if not within_mantid:
     sys.exit(app.exec_())
diff --git a/scripts/PyChop/PyChopGui.py b/scripts/PyChop/PyChopGui.py
index d989308948f926a87c9ae7824fa4bc118e7bd19a..dead3467072cc941c090d06eafd3f85063b1c1c4 100755
--- a/scripts/PyChop/PyChopGui.py
+++ b/scripts/PyChop/PyChopGui.py
@@ -44,8 +44,10 @@ class PyChopGui(QMainWindow):
     minE = {}
     maxE = {}
 
-    def __init__(self):
-        super(PyChopGui, self).__init__()
+    def __init__(self, parent=None, window_flags=None):
+        super(PyChopGui, self).__init__(parent)
+        if window_flags:
+            self.setWindowFlags(window_flags)
         self.folder = os.path.dirname(sys.modules[self.__module__].__file__)
         for fname in os.listdir(self.folder):
             if fname.endswith('.yaml'):
diff --git a/scripts/QECoverage.py b/scripts/QECoverage.py
index 67670934e8ca0021f72dadb58050f30d8684a970..2a21e445abdd5a5810b024b2265994780ccc7ba7 100644
--- a/scripts/QECoverage.py
+++ b/scripts/QECoverage.py
@@ -9,7 +9,13 @@ from mantidqt.gui_helper import get_qapplication
 from QECoverage.QECoverageGUI import QECoverageGUI
 
 app, within_mantid = get_qapplication()
-mainForm = QECoverageGUI()
+if 'workbench' in sys.modules:
+    from workbench.config import get_window_config
+
+    parent, flags = get_window_config()
+else:
+    parent, flags = None, None
+mainForm = QECoverageGUI(parent, flags)
 mainForm.show()
 if not within_mantid:
     sys.exit(app.exec_())
diff --git a/scripts/QECoverage/QECoverageGUI.py b/scripts/QECoverage/QECoverageGUI.py
index b7afd93a61b1fd561709455b607b5abe5021ca84..482bec8016ff3add561d5763a255de5e073f6540 100644
--- a/scripts/QECoverage/QECoverageGUI.py
+++ b/scripts/QECoverage/QECoverageGUI.py
@@ -51,8 +51,10 @@ class QECoverageGUI(QtWidgets.QWidget):
     # Initial Mantid Algorithm by Helen Walker (2015)
     # Rewritten as a Mantid interface by Duc Le (2016)
 
-    def __init__(self, parent=None):
+    def __init__(self, parent=None, window_flags=None):
         QtWidgets.QWidget.__init__(self, parent)
+        if window_flags:
+            self.setWindowFlags(window_flags)
         self.setWindowTitle("QECoverage")
         self.grid = QtWidgets.QVBoxLayout()
         self.setLayout(self.grid)
diff --git a/scripts/SampleTransmissionCalculator/stc_gui.py b/scripts/SampleTransmissionCalculator/stc_gui.py
index a134e8c97e122929dd59571b0f17335197565425..82310725d6e68920b76e3f8c4a67c849a66819c1 100644
--- a/scripts/SampleTransmissionCalculator/stc_gui.py
+++ b/scripts/SampleTransmissionCalculator/stc_gui.py
@@ -12,8 +12,10 @@ from SampleTransmissionCalculator.stc_presenter import SampleTransmissionCalcula
 
 
 class SampleTransmissionCalculator(QtWidgets.QMainWindow):
-    def __init__(self, parent=None):
+    def __init__(self, parent=None, window_flags=None):
         super(SampleTransmissionCalculator, self).__init__(parent)
+        if window_flags:
+            self.setWindowFlags(window_flags)
 
         view = SampleTransmissionCalculatorView(parent=self)
         self.setCentralWidget(view)
diff --git a/scripts/Sample_Transmission_Calculator.py b/scripts/Sample_Transmission_Calculator.py
index 912e7e04e33b123960eaf9fb875a0b0842ff1256..45099261f9a336da40d7faa77254347e298026b5 100644
--- a/scripts/Sample_Transmission_Calculator.py
+++ b/scripts/Sample_Transmission_Calculator.py
@@ -9,7 +9,13 @@ from SampleTransmissionCalculator import stc_gui
 from mantidqt.gui_helper import get_qapplication
 
 app, within_mantid = get_qapplication()
-planner = stc_gui.SampleTransmissionCalculator()
+if 'workbench' in sys.modules:
+    from workbench.config import get_window_config
+
+    parent, flags = get_window_config()
+else:
+    parent, flags = None, None
+planner = stc_gui.SampleTransmissionCalculator(parent, flags)
 planner.show()
 if not within_mantid:
     sys.exit(app.exec_())
diff --git a/scripts/TofConverter.py b/scripts/TofConverter.py
index b3771cf72ff9d9a924e49321d49fa524043270f1..8232d333d416141c4b5257d608d8fa067a76cdaa 100644
--- a/scripts/TofConverter.py
+++ b/scripts/TofConverter.py
@@ -5,12 +5,19 @@
 #   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 # SPDX - License - Identifier: GPL - 3.0 +
 #pylint: disable=invalid-name
+import sys
 from TofConverter import converterGUI
 from mantidqt.gui_helper import get_qapplication
 
 app, within_mantid = get_qapplication()
 
-reducer = converterGUI.MainWindow()#the main ui class in this file is called MainWindow
+if 'workbench' in sys.modules:
+    from workbench.config import get_window_config
+
+    parent, flags = get_window_config()
+else:
+    parent, flags = None, None
+reducer = converterGUI.MainWindow(parent, flags)#the main ui class in this file is called MainWindow
 reducer.show()
 if not within_mantid:
     app.exec_()
diff --git a/scripts/TofConverter/converterGUI.py b/scripts/TofConverter/converterGUI.py
index 87aa44723cdb986db60b4a8101d2549a073a6b1f..9f38f9f0e698a8b2beb0a33d55fca3bb3a9c91b8 100644
--- a/scripts/TofConverter/converterGUI.py
+++ b/scripts/TofConverter/converterGUI.py
@@ -59,8 +59,10 @@ class MainWindow(QMainWindow):
         if outOption in self.needsFlightPathOutputList:
             self.flightPathEnable(True)
 
-    def __init__(self, parent=None):
+    def __init__(self, parent=None, window_flags=None):
         QMainWindow.__init__(self, parent)
+        if window_flags:
+            self.setWindowFlags(window_flags)
         self.ui = load_ui(__file__, 'converter.ui', baseinstance=self)
         self.ui.InputVal.setValidator(QDoubleValidator(self.ui.InputVal))
         self.ui.totalFlightPathInput.setValidator(QDoubleValidator(self.ui.totalFlightPathInput))
diff --git a/scripts/corelli/calibration/database.py b/scripts/corelli/calibration/database.py
index 874c8a80e9664e5f519a064d83adffb5b32c585b..70d2aa4dbd7d6af16f82617b27e45f7d1a5331fb 100644
--- a/scripts/corelli/calibration/database.py
+++ b/scripts/corelli/calibration/database.py
@@ -495,6 +495,9 @@ def load_calibration_set(input_workspace: Union[str, Workspace],
                 # the correct calibration is the one with a date previous to `run_start`
                 filename = date_to_file[available_dates[available_dates_index - 1]]
                 break
+        # final boundary case: run_start > latest available_date
+        if len(available_dates) > 0 and available_dates[-1] < run_start:
+            filename = date_to_file[available_dates[-1]]
         if filename is not None:
             logger.notice(f'Found {filename} for {str(input_workspace)} with run start {run_start}')
             instrument_tables[table_type] = LoadNexusProcessed(Filename=filename,
diff --git a/scripts/test/Muon/grouping_tab/difference_table_presenter_test.py b/scripts/test/Muon/grouping_tab/difference_table_presenter_test.py
index c82e017f080b25fc36e19b859012dbc1e0031ee4..f33d100cf85922cbfab7d0a94002137ef0a2fa39 100644
--- a/scripts/test/Muon/grouping_tab/difference_table_presenter_test.py
+++ b/scripts/test/Muon/grouping_tab/difference_table_presenter_test.py
@@ -12,7 +12,8 @@ from qtpy.QtWidgets import QWidget
 
 from Muon.GUI.Common.grouping_tab_widget.grouping_tab_widget_model import GroupingTabModel
 from Muon.GUI.Common.difference_table_widget.difference_widget_presenter import DifferencePresenter
-from Muon.GUI.Common.muon_group import MuonDiff, MuonGroup
+from Muon.GUI.Common.muon_diff import MuonDiff
+from Muon.GUI.Common.muon_group import MuonGroup
 from Muon.GUI.Common.muon_pair import MuonPair
 from mantidqt.utils.observer_pattern import Observer
 from Muon.GUI.Common.test_helpers.context_setup import setup_context_for_tests
@@ -102,6 +103,12 @@ class DifferenceTablePresenterTest(unittest.TestCase):
     def test_that_view_is_initialized_as_empty(self):
         self.assert_view_empty()
 
+    def test_header_labels_set_correctly(self):
+        self.assertEqual('Group 1', self.presenter.group_view.diff_table.horizontalHeaderItem(2).text())
+        self.assertEqual('Group 2', self.presenter.group_view.diff_table.horizontalHeaderItem(3).text())
+        self.assertEqual('Pair 1', self.presenter.pair_view.diff_table.horizontalHeaderItem(2).text())
+        self.assertEqual('Pair 2', self.presenter.pair_view.diff_table.horizontalHeaderItem(3).text())
+
     # ------------------------------------------------------------------------------------------------------------------
     # TESTS : Adding and removing diffs
     # ------------------------------------------------------------------------------------------------------------------
diff --git a/scripts/test/Muon/grouping_tab/difference_table_selector_test.py b/scripts/test/Muon/grouping_tab/difference_table_selector_test.py
index ea638ca3a456bd7246e0f5120de074f6ec484af4..26bb6897f938ede41828c99d3dc216b11766d50a 100644
--- a/scripts/test/Muon/grouping_tab/difference_table_selector_test.py
+++ b/scripts/test/Muon/grouping_tab/difference_table_selector_test.py
@@ -12,7 +12,8 @@ from qtpy.QtWidgets import QWidget
 
 from Muon.GUI.Common.grouping_tab_widget.grouping_tab_widget_model import GroupingTabModel
 from Muon.GUI.Common.difference_table_widget.difference_widget_presenter import DifferencePresenter
-from Muon.GUI.Common.muon_group import MuonDiff, MuonGroup
+from Muon.GUI.Common.muon_diff import MuonDiff
+from Muon.GUI.Common.muon_group import MuonGroup
 from Muon.GUI.Common.muon_pair import MuonPair
 from mantidqt.utils.observer_pattern import Observer
 from Muon.GUI.Common.test_helpers.context_setup import setup_context_for_tests
diff --git a/scripts/test/Muon/grouping_tab/difference_widget_presenter_test.py b/scripts/test/Muon/grouping_tab/difference_widget_presenter_test.py
index 72fb3bf3906b4058f5a895817edccfa2ff260985..80b08b0aa39e391ef9229e287d9a0a9919c61f0a 100644
--- a/scripts/test/Muon/grouping_tab/difference_widget_presenter_test.py
+++ b/scripts/test/Muon/grouping_tab/difference_widget_presenter_test.py
@@ -12,7 +12,8 @@ from qtpy.QtWidgets import QWidget
 
 from Muon.GUI.Common.grouping_tab_widget.grouping_tab_widget_model import GroupingTabModel
 from Muon.GUI.Common.difference_table_widget.difference_widget_presenter import DifferencePresenter
-from Muon.GUI.Common.muon_group import MuonDiff, MuonGroup
+from Muon.GUI.Common.muon_diff import MuonDiff
+from Muon.GUI.Common.muon_group import MuonGroup
 from Muon.GUI.Common.muon_pair import MuonPair
 from mantidqt.utils.observer_pattern import Observer
 from Muon.GUI.Common.test_helpers.context_setup import setup_context_for_tests
diff --git a/scripts/test/Muon/muon_context_test.py b/scripts/test/Muon/muon_context_test.py
index e7757afde8213604f5ad617909cba6ac91933b40..23ebd36969df580f3f828fe5594fc6ac120e8074 100644
--- a/scripts/test/Muon/muon_context_test.py
+++ b/scripts/test/Muon/muon_context_test.py
@@ -15,7 +15,8 @@ from mantid import ConfigService
 from collections import Counter
 from Muon.GUI.Common.utilities.load_utils import load_workspace_from_filename
 from Muon.GUI.Common.test_helpers.context_setup import setup_context
-from Muon.GUI.Common.muon_group import MuonGroup, MuonDiff
+from Muon.GUI.Common.muon_diff import MuonDiff
+from Muon.GUI.Common.muon_group import MuonGroup
 from Muon.GUI.Common.muon_pair import MuonPair
 from Muon.GUI.Common.muon_phasequad import MuonPhasequad
 
diff --git a/scripts/test/Muon/muon_group_pair_context_test.py b/scripts/test/Muon/muon_group_pair_context_test.py
index b8eb5c7ba0b5f701c7c40bd211eca1f0cd3082e4..2130f5d17dd39f40db6e847fedd04ce4a6bdf291 100644
--- a/scripts/test/Muon/muon_group_pair_context_test.py
+++ b/scripts/test/Muon/muon_group_pair_context_test.py
@@ -7,7 +7,8 @@
 import unittest
 
 from Muon.GUI.Common.contexts.muon_group_pair_context import MuonGroupPairContext
-from Muon.GUI.Common.muon_group import MuonGroup, MuonDiff
+from Muon.GUI.Common.muon_diff import MuonDiff
+from Muon.GUI.Common.muon_group import MuonGroup
 from Muon.GUI.Common.muon_pair import MuonPair
 from Muon.GUI.Common.muon_phasequad import MuonPhasequad
 from Muon.GUI.Common.test_helpers.general_test_helpers import create_group_populated_by_two_workspace
@@ -222,8 +223,8 @@ class MuonGroupPairContextTest(unittest.TestCase):
         self.assertEquals(self.context.pairs[0].backward_group, 'bwd1')
         self.assertEquals(self.context.pairs[1].forward_group, 'fwd2')
         self.assertEquals(self.context.pairs[1].backward_group, 'bwd2')
-        self.assertEquals(self.context.diffs[0].forward_group, 'long1')
-        self.assertEquals(self.context.diffs[0].backward_group, 'long2')
+        self.assertEquals(self.context.diffs[0].positive, 'long1')
+        self.assertEquals(self.context.diffs[0].negative, 'long2')
         self.assertEquals(self.context.selected, 'long1')
 
     def test_get_group_pair_name_and_run_from_workspace_name(self):
diff --git a/scripts/test/Muon/utilities/muon_difference_test.py b/scripts/test/Muon/utilities/muon_difference_test.py
index daed2cdf7caae86795f8f22726d12df6df336cc4..e199908637d2598ac5d1dcb8c87ce60c2184ca37 100644
--- a/scripts/test/Muon/utilities/muon_difference_test.py
+++ b/scripts/test/Muon/utilities/muon_difference_test.py
@@ -6,7 +6,7 @@
 # SPDX - License - Identifier: GPL - 3.0 +
 import unittest
 
-from Muon.GUI.Common.muon_group import MuonDiff
+from Muon.GUI.Common.muon_diff import MuonDiff
 
 
 class MuonDifferenceTest(unittest.TestCase):
@@ -26,8 +26,8 @@ class MuonDifferenceTest(unittest.TestCase):
     def test_that_can_get_positive_and_negative(self):
         diff = MuonDiff("diff1", "positive", "negative")
 
-        self.assertEqual("positive", diff.forward_group)
-        self.assertEqual("negative", diff.backward_group)
+        self.assertEqual("positive", diff.positive)
+        self.assertEqual("negative", diff.negative)
 
     def test_is_group_diff_by_default(self):
         diff = MuonDiff("diff1", "positive", "negative")
diff --git a/scripts/test/corelli/calibration/test_database.py b/scripts/test/corelli/calibration/test_database.py
index 721ed203d337ad52100bb566f8fa4212f2464be5..416c515bc45eb04bdfa079c248bd6e40d4cdc30d 100644
--- a/scripts/test/corelli/calibration/test_database.py
+++ b/scripts/test/corelli/calibration/test_database.py
@@ -6,17 +6,20 @@
 #   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 # SPDX - License - Identifier: GPL - 3.0 +
 
-from os import path, remove
-import unittest
+from contextlib import contextmanager
+from datetime import datetime
 from numpy.testing import assert_allclose
+from os import path, remove
 import pathlib
-from datetime import datetime
+import shutil
 import tempfile
+from typing import List
+import unittest
 
 from mantid import AnalysisDataService, config
 from mantid.api import mtd, WorkspaceGroup
 from mantid.dataobjects import TableWorkspace
-from mantid.simpleapi import CreateEmptyTableWorkspace, DeleteWorkspaces, LoadNexusProcessed
+from mantid.simpleapi import CreateEmptyTableWorkspace, CreateSampleWorkspace, DeleteWorkspaces, LoadNexusProcessed
 
 from corelli.calibration.database import (combine_spatial_banks, combine_temporal_banks, day_stamp, filename_bank_table,
                                           has_valid_columns, init_corelli_table, load_bank_table, load_calibration_set,
@@ -321,8 +324,93 @@ class TestCorelliDatabase(unittest.TestCase):
 
         database.cleanup()
 
-    def tearDown(self) -> None:
+    def test_load_calibration_set(self) -> None:
+        r"""
+        1. create an empty "database"
+          1.1. create a workspace with a particular daystamp
+          1.2. try to find a file in the database
+        2. create a database with only one calibration file with daystamp 20200601
+          1.1 create a workspace with the following daystamps and see in which cases the calibration file is loaded
+              20200101, 20200601, 20201201
+        3. create a database with two calibration files with day-stamsp 20200401 and 20200801
+          3.1 create a workspace with the following day-stamps and see which (in any) calibration is selected
+             20200101, 20200401, 20200601, 20200801, 20201201
+        """
 
+        @contextmanager
+        def mock_database(day_stamps: List[int]):
+            r"""create a database with mock calibration files"""
+            dir_path = tempfile.mkdtemp()
+            path = pathlib.Path(dir_path)
+            for daystamp in day_stamps:
+                file_path = path / f'calibration_corelli_{daystamp}.nxs.h5'
+                with open(str(file_path), 'w') as fp:
+                    fp.write('mock')
+            try:
+                yield dir_path
+            finally:
+                shutil.rmtree(dir_path)
+
+        def set_daystamp(input_workspace: str, daystamp: int):
+            r"""Update the run_start log entry of a workspace
+            :param input_workspace: handle to a workspace (not its name!)
+            :param daystamp: 8-digit integer
+            """
+            x = str(daystamp)
+            run_start = f'{x[0:4]}-{x[4:6]}-{x[6:]}T20:54:07.265105667'
+            run = input_workspace.getRun()
+            run.addProperty(name='run_start', value=run_start, replace=True)
+
+        workspace = CreateSampleWorkspace(OutputWorkspace='test_load_calibration_set')
+        set_daystamp(workspace, 20200101)
+
+        # empty calibration database (corner case)
+        with mock_database([]) as database_path:
+            instrument_tables = load_calibration_set(workspace, database_path)
+            assert list(instrument_tables) == [None, None]
+
+        # database with only one calibration file (corner case)
+        with mock_database([20200601]) as database_path:
+            set_daystamp(workspace, 20200101)  # no calibration found
+            assert list(load_calibration_set(workspace, database_path)) == [None, None]
+
+            set_daystamp(workspace, 20200601)
+            with self.assertRaises(RuntimeError) as ar:
+                load_calibration_set(workspace, database_path)  # should pick calibration 20200601
+            self.assertEqual('20200601' in str(ar.exception), True)
+
+            set_daystamp(workspace, 20201201)
+            with self.assertRaises(RuntimeError) as ar:
+                load_calibration_set(workspace, database_path)
+            self.assertEqual('calibration_corelli_20200601.nxs.h5' in str(ar.exception), True)
+
+        # database with two calibration files (general case)
+        with mock_database([20200401, 20200801]) as database_path:
+            set_daystamp(workspace, '20200101')
+            assert list(load_calibration_set(workspace, database_path)) == [None, None]
+
+            set_daystamp(workspace, '20200401')
+            with self.assertRaises(RuntimeError) as ar:
+                load_calibration_set(workspace, database_path)
+            self.assertEqual('calibration_corelli_20200401.nxs.h5' in str(ar.exception), True)
+
+            set_daystamp(workspace, '20200601')
+            with self.assertRaises(RuntimeError) as ar:
+                load_calibration_set(workspace, database_path)
+            self.assertEqual('calibration_corelli_20200401.nxs.h5' in str(ar.exception), True)
+
+            set_daystamp(workspace, '20200801')
+            with self.assertRaises(RuntimeError) as ar:
+                load_calibration_set(workspace, database_path)
+            self.assertEqual('calibration_corelli_20200801.nxs.h5' in str(ar.exception), True)
+
+            set_daystamp(workspace, '20201201')
+            with self.assertRaises(RuntimeError) as ar:
+                load_calibration_set(workspace, database_path)
+            self.assertEqual('calibration_corelli_20200801.nxs.h5' in str(ar.exception), True)
+        workspace.delete()
+
+    def tearDown(self) -> None:
         date: str = datetime.now().strftime('%Y%m%d')  # format YYYYMMDD
         remove(filename_bank_table(10, self.database_path, date))
         remove(filename_bank_table(20, self.database_path, date))
diff --git a/tools/release_generator/release.py b/tools/release_generator/release.py
index 56f8ce90e37487168ef3147469ba4d867f720abf..2d6a546734ebad2d358fa70aadeb06058f52ff36 100755
--- a/tools/release_generator/release.py
+++ b/tools/release_generator/release.py
@@ -262,7 +262,6 @@ def toMilestoneName(version):
 
 def addToReleaseList(release_root, version):
     filename = os.path.join(release_root, 'index.rst')
-    newversion = '   %s <%s/index>\n' % (version, version)
 
     # read in the entire old version
     with open(filename, 'r') as handle:
@@ -273,9 +272,9 @@ def addToReleaseList(release_root, version):
         search_for_insertion = True
         for i in range(len(oldtext)):
             line = oldtext[i].strip()
-            if search_for_insertion and line.startswith('v') and line.endswith('/index>'):
+            if search_for_insertion and line.startswith('* :doc:`v') and line.endswith('/index>`'):
                 if version not in line:
-                    handle.write(newversion)
+                    handle.write(f"* :doc:`{version} <{version}/index>`\n")
                 search_for_insertion = False
             handle.write(oldtext[i])