diff --git a/.cmake-format.json b/.cmake-format.json
index 127389c1670eac89669b3a3036abe150efdeb284..c0ebd4d96013eb563df8932dc6ff8b4bed34cc3f 100644
--- a/.cmake-format.json
+++ b/.cmake-format.json
@@ -107,7 +107,8 @@
     0,
     1,
     2,
-    3
+    3,
+    4
   ],
   "enable_markup": true,
   "first_comment_is_literal": false,
diff --git a/Framework/API/inc/MantidAPI/ExperimentInfo.h b/Framework/API/inc/MantidAPI/ExperimentInfo.h
index c3946648a93234d65880d20468f1d7a1f3ddcfe0..52dfcdc18163158a8a5153fd1f71ed7a03645315 100644
--- a/Framework/API/inc/MantidAPI/ExperimentInfo.h
+++ b/Framework/API/inc/MantidAPI/ExperimentInfo.h
@@ -115,7 +115,8 @@ public:
   void setEFixed(const detid_t detID, const double value);
 
   /// Saves this experiment description to the open NeXus file
-  void saveExperimentInfoNexus(::NeXus::File *file) const;
+  void saveExperimentInfoNexus(::NeXus::File *file,
+                               bool saveLegacyInstrument = true) const;
   /// Saves this experiment description to the open NeXus file
   void saveExperimentInfoNexus(::NeXus::File *file, bool saveInstrument,
                                bool saveSample, bool saveLogs) const;
diff --git a/Framework/API/src/CompositeFunction.cpp b/Framework/API/src/CompositeFunction.cpp
index 48d27693fe5d84b735ce9aee88f14bb8ae8f6f1c..e1b589627bf6e5ed22902adb86d4f5313b5e3313 100644
--- a/Framework/API/src/CompositeFunction.cpp
+++ b/Framework/API/src/CompositeFunction.cpp
@@ -508,8 +508,9 @@ void CompositeFunction::replaceFunction(size_t i, IFunction_sptr f) {
     } else if (np_new > 0) // it could happen if the old function is an empty
                            // CompositeFunction
     {
+      using std::placeholders::_1;
       itFun = std::find_if(m_IFunction.begin(), m_IFunction.end(),
-                           std::bind2nd(std::greater<size_t>(), i));
+                           std::bind(std::greater<size_t>(), _1, i));
       m_IFunction.insert(itFun, np_new, i);
     }
   }
diff --git a/Framework/API/src/ExperimentInfo.cpp b/Framework/API/src/ExperimentInfo.cpp
index f983d0958a3b85382d24f2a41d96f8bb31bbb865..d412cecd697d4f9656822047d4fca1a6f3eb3da4 100644
--- a/Framework/API/src/ExperimentInfo.cpp
+++ b/Framework/API/src/ExperimentInfo.cpp
@@ -1148,10 +1148,14 @@ void ExperimentInfo::invalidateAllSpectrumDefinitions() {
 
 /** Save the object to an open NeXus file.
  * @param file :: open NeXus file
+ * @param saveLegacyInstrument : defaults to true, otherwise not in file output
  */
-void ExperimentInfo::saveExperimentInfoNexus(::NeXus::File *file) const {
+void ExperimentInfo::saveExperimentInfoNexus(::NeXus::File *file,
+                                             bool saveLegacyInstrument) const {
   Instrument_const_sptr instrument = getInstrument();
-  instrument->saveNexus(file, "instrument");
+  if (saveLegacyInstrument) {
+    instrument->saveNexus(file, "instrument");
+  }
   sample().saveNexus(file, "sample");
   run().saveNexus(file, "logs");
 }
diff --git a/Framework/API/test/SampleTest.h b/Framework/API/test/SampleTest.h
index 43d5cad943799f646c60b1d8f9384b6ce7fe7285..7fb28f51c45f41f908df0c033568ccd3b8d674e2 100644
--- a/Framework/API/test/SampleTest.h
+++ b/Framework/API/test/SampleTest.h
@@ -272,8 +272,8 @@ public:
 
     const Material &mat = sample.getMaterial();
     const double lambda(2.1);
-    TS_ASSERT_DELTA(mat.cohScatterXSection(lambda), 0.0184, 1e-02);
-    TS_ASSERT_DELTA(mat.incohScatterXSection(lambda), 5.08, 1e-02);
+    TS_ASSERT_DELTA(mat.cohScatterXSection(), 0.0184, 1e-02);
+    TS_ASSERT_DELTA(mat.incohScatterXSection(), 5.08, 1e-02);
     TS_ASSERT_DELTA(mat.absorbXSection(lambda), 5.93, 1e-02);
   }
 
diff --git a/Framework/Algorithms/src/AbsorptionCorrection.cpp b/Framework/Algorithms/src/AbsorptionCorrection.cpp
index 29770b6a4a27641a6a06cb7947b5ec2f7e4e81a9..b5750619ad0c76af2c7f2695ef47f6b52cd6d739 100644
--- a/Framework/Algorithms/src/AbsorptionCorrection.cpp
+++ b/Framework/Algorithms/src/AbsorptionCorrection.cpp
@@ -325,7 +325,7 @@ void AbsorptionCorrection::retrieveBaseProperties() {
     if (isEmpty(rho))
       rho = m_material.numberDensity();
     if (isEmpty(sigma_s))
-      sigma_s = m_material.totalScatterXSection(NeutronAtom::ReferenceLambda);
+      sigma_s = m_material.totalScatterXSection();
     if (isEmpty(sigma_atten))
       sigma_atten = m_material.absorbXSection(NeutronAtom::ReferenceLambda);
 
diff --git a/Framework/Algorithms/src/Bin2DPowderDiffraction.cpp b/Framework/Algorithms/src/Bin2DPowderDiffraction.cpp
index a8c2373d380636b2d0c3c8d6bd3967a7b0f73756..2dc486eb16ef673470f0eefb72c4216f3a63ad04 100644
--- a/Framework/Algorithms/src/Bin2DPowderDiffraction.cpp
+++ b/Framework/Algorithms/src/Bin2DPowderDiffraction.cpp
@@ -377,11 +377,12 @@ void Bin2DPowderDiffraction::normalizeToBinArea(MatrixWorkspace_sptr outWS) {
     // divide by the xBinWidth
     outWS->convertToFrequencies(idx);
     auto &freqs = outWS->mutableY(idx);
+    using std::placeholders::_1;
     std::transform(freqs.begin(), freqs.end(), freqs.begin(),
-                   std::bind1st(std::multiplies<double>(), factor));
+                   std::bind(std::multiplies<double>(), factor, _1));
     auto &errors = outWS->mutableE(idx);
     std::transform(errors.begin(), errors.end(), errors.begin(),
-                   std::bind1st(std::multiplies<double>(), factor));
+                   std::bind(std::multiplies<double>(), factor, _1));
   }
 }
 
diff --git a/Framework/Algorithms/src/CalculateCarpenterSampleCorrection.cpp b/Framework/Algorithms/src/CalculateCarpenterSampleCorrection.cpp
index 5d51502bf34892d59b2fdb46fc792de33c648d97..3909b972fd1f234e705c0575a8d9a7f65757ecec 100644
--- a/Framework/Algorithms/src/CalculateCarpenterSampleCorrection.cpp
+++ b/Framework/Algorithms/src/CalculateCarpenterSampleCorrection.cpp
@@ -138,7 +138,7 @@ void CalculateCarpenterSampleCorrection::exec() {
   const bool absOn = getProperty("Absorption");
   const bool msOn = getProperty("MultipleScattering");
   const Material &sampleMaterial = inputWksp->sample().getMaterial();
-  if (sampleMaterial.totalScatterXSection(LAMBDA_REF) != 0.0) {
+  if (sampleMaterial.totalScatterXSection() != 0.0) {
     g_log.information() << "Using material \"" << sampleMaterial.name()
                         << "\" from workspace\n";
     if (std::abs(coeff1 - 2.8) < std::numeric_limits<double>::epsilon())
@@ -147,7 +147,7 @@ void CalculateCarpenterSampleCorrection::exec() {
         (!isEmpty(sampleMaterial.numberDensity())))
       coeff2 = sampleMaterial.numberDensity();
     if (std::abs(coeff3 - 5.1) < std::numeric_limits<double>::epsilon())
-      coeff3 = sampleMaterial.totalScatterXSection(LAMBDA_REF);
+      coeff3 = sampleMaterial.totalScatterXSection();
   } else // Save input in Sample with wrong atomic number and name
   {
     NeutronAtom neutron(0, 0, 0.0, 0.0, coeff3, 0.0, coeff3, coeff1);
diff --git a/Framework/Algorithms/src/CalculateTransmissionBeamSpreader.cpp b/Framework/Algorithms/src/CalculateTransmissionBeamSpreader.cpp
index 4144cd7109ec8bc8799c5b8d9919cf55e3eb1689..d2dc527332185e9708d466a59b80b032916627e7 100644
--- a/Framework/Algorithms/src/CalculateTransmissionBeamSpreader.cpp
+++ b/Framework/Algorithms/src/CalculateTransmissionBeamSpreader.cpp
@@ -121,7 +121,11 @@ void CalculateTransmissionBeamSpreader::exec() {
   }
 
   // Extract the required spectra into separate workspaces
-  std::vector<detid_t> udets{getProperty("IncidentBeamMonitor")};
+  // The static_cast should not be necessary but it is required to avoid a
+  // "internal compiler error: segmentation fault" when compiling with gcc
+  // and std=c++1z
+  std::vector<detid_t> udets{
+      static_cast<detid_t>(getProperty("IncidentBeamMonitor"))};
 
   // Convert UDETs to workspace indices
   // Get monitors (assume that the detector mapping is the same for all data
diff --git a/Framework/Algorithms/src/CreateSampleWorkspace.cpp b/Framework/Algorithms/src/CreateSampleWorkspace.cpp
index dd1510b819668f6b72cf503f7cf43245892b848b..13689d82fde47e40ca88d32ba64d35a80ba8ea91 100644
--- a/Framework/Algorithms/src/CreateSampleWorkspace.cpp
+++ b/Framework/Algorithms/src/CreateSampleWorkspace.cpp
@@ -383,8 +383,10 @@ EventWorkspace_sptr CreateSampleWorkspace::createEventWorkspace(
   // to find the events per bin
   double sum_of_elems = std::accumulate(yValues.begin(), yValues.end(), 0.0);
   double event_distrib_factor = numEvents / sum_of_elems;
-  std::transform(yValues.begin(), yValues.end(), yValues.begin(),
-                 std::bind1st(std::multiplies<double>(), event_distrib_factor));
+  using std::placeholders::_1;
+  std::transform(
+      yValues.begin(), yValues.end(), yValues.begin(),
+      std::bind(std::multiplies<double>(), event_distrib_factor, _1));
   // the array should now contain the number of events required per bin
 
   // Make fake events
diff --git a/Framework/Algorithms/src/CrossCorrelate.cpp b/Framework/Algorithms/src/CrossCorrelate.cpp
index 80862c9a4abee1367aa73ce613bff551f5f80339..761fded612431eea5e9c465d78ae524a1987b4b0 100644
--- a/Framework/Algorithms/src/CrossCorrelate.cpp
+++ b/Framework/Algorithms/src/CrossCorrelate.cpp
@@ -113,12 +113,13 @@ void CrossCorrelate::exec() {
 
   // Now check if the range between x_min and x_max is valid
   auto &referenceX = inputWS->x(index_ref);
+  using std::placeholders::_1;
   auto minIt = std::find_if(referenceX.cbegin(), referenceX.cend(),
-                            std::bind2nd(std::greater<double>(), xmin));
+                            std::bind(std::greater<double>(), _1, xmin));
   if (minIt == referenceX.cend())
     throw std::runtime_error("No data above XMin");
   auto maxIt = std::find_if(minIt, referenceX.cend(),
-                            std::bind2nd(std::greater<double>(), xmax));
+                            std::bind(std::greater<double>(), _1, xmax));
   if (minIt == maxIt)
     throw std::runtime_error("Range is not valid");
 
diff --git a/Framework/Algorithms/src/FFTSmooth.cpp b/Framework/Algorithms/src/FFTSmooth.cpp
index b4a5f5505b82b9ad8fad9e58677d18b1177989f1..26be4b33f599921ff1d3805619fd88a2ef3e6d8a 100644
--- a/Framework/Algorithms/src/FFTSmooth.cpp
+++ b/Framework/Algorithms/src/FFTSmooth.cpp
@@ -175,10 +175,11 @@ void FFTSmooth::truncate(int n) {
   xr.assign(X.begin(), X.begin() + nx);
   xi.assign(X.begin(), X.begin() + nx);
 
+  using std::placeholders::_1;
   std::transform(yr.begin(), yr.end(), yr.begin(),
-                 std::bind2nd(std::multiplies<double>(), f));
+                 std::bind(std::multiplies<double>(), _1, f));
   std::transform(yi.begin(), yi.end(), yi.begin(),
-                 std::bind2nd(std::multiplies<double>(), f));
+                 std::bind(std::multiplies<double>(), _1, f));
 }
 
 /** Smoothing by zeroing.
diff --git a/Framework/Algorithms/src/FindCenterOfMassPosition2.cpp b/Framework/Algorithms/src/FindCenterOfMassPosition2.cpp
index f704e3238bce2760f6a53741ee3aa1c97eede621..7e2c2dfc80e4637e12fe2dd11af68529b3b8ebc1 100644
--- a/Framework/Algorithms/src/FindCenterOfMassPosition2.cpp
+++ b/Framework/Algorithms/src/FindCenterOfMassPosition2.cpp
@@ -166,6 +166,9 @@ void FindCenterOfMassPosition2::exec() {
 
       // Get the current spectrum
       auto &YIn = inputWS->y(i);
+      // Skip if NaN of inf
+      if (std::isnan(YIn[specID]) || std::isinf(YIn[specID]))
+        continue;
       const V3D pos = spectrumInfo.position(i);
       double x = pos.X();
       double y = pos.Y();
diff --git a/Framework/Algorithms/src/GetEi2.cpp b/Framework/Algorithms/src/GetEi2.cpp
index 97be4c087e5f01be1b6333ffd8a9103b4f9072f6..db502a910e68d1c0a4ff5af6dd4dcd16c2df0f04 100644
--- a/Framework/Algorithms/src/GetEi2.cpp
+++ b/Framework/Algorithms/src/GetEi2.cpp
@@ -532,8 +532,9 @@ double GetEi2::calculatePeakWidthAtHalfHeight(
   peak_x.resize(nvalues);
   std::copy(Xs.begin() + im, Xs.begin() + ip + 1, peak_x.begin());
   peak_y.resize(nvalues);
+  using std::placeholders::_1;
   std::transform(Ys.begin() + im, Ys.begin() + ip + 1, peak_y.begin(),
-                 std::bind2nd(std::minus<double>(), bkgd));
+                 std::bind(std::minus<double>(), _1, bkgd));
   peak_e.resize(nvalues);
   std::copy(Es.begin() + im, Es.begin() + ip + 1, peak_e.begin());
 
diff --git a/Framework/Algorithms/src/GetQsInQENSData.cpp b/Framework/Algorithms/src/GetQsInQENSData.cpp
index 9f79cf1522ceb4e2f6e9f6f844b2255dbb3ab987..d880f03a00b495f50aabc75c4ce181e687b066ca 100644
--- a/Framework/Algorithms/src/GetQsInQENSData.cpp
+++ b/Framework/Algorithms/src/GetQsInQENSData.cpp
@@ -113,8 +113,9 @@ MantidVec GetQsInQENSData::extractQValues(
       // Convert Q-values to point values.
       qValues.pop_back();
       qValues.erase(qValues.begin());
+      using std::placeholders::_1;
       std::transform(qValues.begin(), qValues.end(), qValues.begin(),
-                     std::bind2nd(std::divides<double>(), 2.0));
+                     std::bind(std::divides<double>(), _1, 2.0));
     }
   } else {
 
diff --git a/Framework/Algorithms/src/HRPDSlabCanAbsorption.cpp b/Framework/Algorithms/src/HRPDSlabCanAbsorption.cpp
index 1ab1c3370fd546d17eeb82ebb98710a75d39031b..c8f5314a5a9e8e74dca104968a24555f2349ed3c 100644
--- a/Framework/Algorithms/src/HRPDSlabCanAbsorption.cpp
+++ b/Framework/Algorithms/src/HRPDSlabCanAbsorption.cpp
@@ -158,13 +158,11 @@ API::MatrixWorkspace_sptr HRPDSlabCanAbsorption::runFlatPlateAbsorption() {
   double sigma_s = getProperty("SampleScatteringXSection");      // in barns
   double rho = getProperty("SampleNumberDensity"); // in Angstroms-3
   const Material &sampleMaterial = m_inputWS->sample().getMaterial();
-  if (sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda) !=
-      0.0) {
+  if (sampleMaterial.totalScatterXSection() != 0.0) {
     if (rho == EMPTY_DBL())
       rho = sampleMaterial.numberDensity();
     if (sigma_s == EMPTY_DBL())
-      sigma_s =
-          sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda);
+      sigma_s = sampleMaterial.totalScatterXSection();
     if (sigma_atten == EMPTY_DBL())
       sigma_atten = sampleMaterial.absorbXSection(NeutronAtom::ReferenceLambda);
   } else // Save input in Sample with wrong atomic number and name
diff --git a/Framework/Algorithms/src/MaxMin.cpp b/Framework/Algorithms/src/MaxMin.cpp
index 7f21d9ac3bddeb01f1ae0a8b85a120bf65d2c942..6b4f1415d41bb9d4ace7ab09203fbb595b1267a3 100644
--- a/Framework/Algorithms/src/MaxMin.cpp
+++ b/Framework/Algorithms/src/MaxMin.cpp
@@ -122,9 +122,11 @@ void MaxMin::exec() {
 
     if (MaxRange == EMPTY_DBL())
       highit = X.end();
-    else
+    else {
+      using std::placeholders::_1;
       highit = std::find_if(lowit, X.end(),
-                            std::bind2nd(std::greater<double>(), MaxRange));
+                            std::bind(std::greater<double>(), _1, MaxRange));
+    }
 
     // If range specified doesn't overlap with this spectrum then bail out
     if (lowit == X.end() || highit == X.begin() || lowit == highit)
diff --git a/Framework/Algorithms/src/Minus.cpp b/Framework/Algorithms/src/Minus.cpp
index 5d8accfc4ffa1d1f7058fa391f9ae6a7d26fc3e6..f5598050c3521146e35cc9966a3e96ac445df13e 100644
--- a/Framework/Algorithms/src/Minus.cpp
+++ b/Framework/Algorithms/src/Minus.cpp
@@ -31,12 +31,13 @@ void Minus::performBinaryOperation(const HistogramData::Histogram &lhs,
                                    const double rhsY, const double rhsE,
                                    HistogramData::HistogramY &YOut,
                                    HistogramData::HistogramE &EOut) {
+  using std::placeholders::_1;
   std::transform(lhs.y().begin(), lhs.y().end(), YOut.begin(),
-                 std::bind2nd(std::minus<double>(), rhsY));
+                 std::bind(std::minus<double>(), _1, rhsY));
   // Only do E if non-zero, otherwise just copy
   if (rhsE != 0)
     std::transform(lhs.e().begin(), lhs.e().end(), EOut.begin(),
-                   std::bind2nd(VectorHelper::SumGaussError<double>(), rhsE));
+                   std::bind(VectorHelper::SumGaussError<double>(), _1, rhsE));
   else
     EOut = lhs.e();
 }
diff --git a/Framework/Algorithms/src/MultiplyRange.cpp b/Framework/Algorithms/src/MultiplyRange.cpp
index a1382503dae4ad410faf872616f5ad6ab1e35d89..7ced286e845dfdd715ad155e6308ae52a5047f55 100644
--- a/Framework/Algorithms/src/MultiplyRange.cpp
+++ b/Framework/Algorithms/src/MultiplyRange.cpp
@@ -90,12 +90,13 @@ void MultiplyRange::exec() {
     auto &newE = outputWS->mutableE(i);
 
     // Now multiply the requested range
+    using std::placeholders::_1;
     std::transform(newY.begin() + startBin, newY.begin() + endBin + 1,
                    newY.begin() + startBin,
-                   std::bind2nd(std::multiplies<double>(), factor));
+                   std::bind(std::multiplies<double>(), _1, factor));
     std::transform(newE.begin() + startBin, newE.begin() + endBin + 1,
                    newE.begin() + startBin,
-                   std::bind2nd(std::multiplies<double>(), factor));
+                   std::bind(std::multiplies<double>(), _1, factor));
 
     progress.report();
     PARALLEL_END_INTERUPT_REGION
diff --git a/Framework/Algorithms/src/Plus.cpp b/Framework/Algorithms/src/Plus.cpp
index b0683314e6d3b2a2d24b54271812c26c9b83ad86..4186592e1d78de1956b8384368aac66e38d365fd 100644
--- a/Framework/Algorithms/src/Plus.cpp
+++ b/Framework/Algorithms/src/Plus.cpp
@@ -34,12 +34,13 @@ void Plus::performBinaryOperation(const HistogramData::Histogram &lhs,
                                   const double rhsY, const double rhsE,
                                   HistogramData::HistogramY &YOut,
                                   HistogramData::HistogramE &EOut) {
+  using std::placeholders::_1;
   std::transform(lhs.y().begin(), lhs.y().end(), YOut.begin(),
-                 std::bind2nd(std::plus<double>(), rhsY));
+                 std::bind(std::plus<double>(), _1, rhsY));
   // Only do E if non-zero, otherwise just copy
   if (rhsE != 0)
     std::transform(lhs.e().begin(), lhs.e().end(), EOut.begin(),
-                   std::bind2nd(VectorHelper::SumGaussError<double>(), rhsE));
+                   std::bind(VectorHelper::SumGaussError<double>(), _1, rhsE));
   else
     EOut = lhs.e();
 }
diff --git a/Framework/Algorithms/src/PointByPointVCorrection.cpp b/Framework/Algorithms/src/PointByPointVCorrection.cpp
index f4fdcc0b63b6eb1ead613efecb0d1796ae31f2db..fce798593e34568049133ffd34280e605e6b8a81 100644
--- a/Framework/Algorithms/src/PointByPointVCorrection.cpp
+++ b/Framework/Algorithms/src/PointByPointVCorrection.cpp
@@ -118,8 +118,9 @@ void PointByPointVCorrection::exec() {
         (error2_factor1 / factor1 / factor1 + error2_factor2);
 
     // Calculate the normalized Y values
+    using std::placeholders::_1;
     // NOTE: Previously, we had been using std::transform with
-    // std::bind2nd(std::multiplies<double>(),factor)
+    // std::bind(std::multiplies<double>(), _1,factor)
     //       here, but that seemed to have strange effects in Windows Debug
     //       builds which caused the unit tests
     //       to sometimes fail.  Maybe this is some compiler bug to do with
diff --git a/Framework/Algorithms/src/Regroup.cpp b/Framework/Algorithms/src/Regroup.cpp
index 4015812d042570695a10351a9e794899979c4c8b..f103098e5d121079d3faf5489732d88fe24ee7d4 100644
--- a/Framework/Algorithms/src/Regroup.cpp
+++ b/Framework/Algorithms/src/Regroup.cpp
@@ -190,8 +190,9 @@ int Regroup::newAxis(const std::vector<double> &params,
   int isteps = ibounds - 1; // highest index in params array containing a step
 
   xcurr = params[0];
+  using std::placeholders::_1;
   auto iup = std::find_if(xold.cbegin(), xold.cend(),
-                          std::bind2nd(std::greater_equal<double>(), xcurr));
+                          std::bind(std::greater_equal<double>(), _1, xcurr));
   if (iup != xold.end()) {
     xcurr = *iup;
     xnew.push_back(xcurr);
@@ -211,7 +212,7 @@ int Regroup::newAxis(const std::vector<double> &params,
 
     // find nearest x_i that is >= xcurr
     iup = std::find_if(xold.begin(), xold.end(),
-                       std::bind2nd(std::greater_equal<double>(), xcurr + xs));
+                       std::bind(std::greater_equal<double>(), _1, xcurr + xs));
     if (iup != xold.end()) {
       if (*iup <= params[ibound]) {
         xcurr = *iup;
diff --git a/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp b/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp
index e259b3447c033d42bf40f34101e64ccec81118ad..030e13b175a097a4e0277e4b76f4eb80381f7cc8 100644
--- a/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp
+++ b/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp
@@ -18,21 +18,6 @@ using Kernel::V3D;
 
 namespace Algorithms {
 
-namespace {
-
-/**
- * Compute the attenuation factor for the given coefficients
- * @param rho Number density of the sample in \f$\\A^{-3}\f$
- * @param sigma Cross-section in barns
- * @param length Path length in metres
- * @return The dimensionless attenuated fraction
- */
-double attenuation(double rho, double sigma, double length) {
-  using std::exp;
-  return exp(-100 * rho * sigma * length);
-}
-} // namespace
-
 /**
  * Construct the volume encompassing the sample + any environment kit. The
  * beam profile defines a bounding region for the sampling of the scattering
@@ -119,11 +104,7 @@ double MCInteractionVolume::calculateAbsorption(
     for (const auto &segment : path) {
       const double length = segment.distInsideObject;
       const auto &segObj = *(segment.object);
-      const auto &segMat = segObj.material();
-      factor *= attenuation(segMat.numberDensity(),
-                            segMat.totalScatterXSection(lambda) +
-                                segMat.absorbXSection(lambda),
-                            length);
+      factor *= segObj.material().attenuation(length, lambda);
     }
     return factor;
   };
diff --git a/Framework/Algorithms/src/ScaleX.cpp b/Framework/Algorithms/src/ScaleX.cpp
index d22944e3226d484054515fc69ed1ca952664a3d7..083720c5286d17e38a7857d8e256434110d15ad1 100644
--- a/Framework/Algorithms/src/ScaleX.cpp
+++ b/Framework/Algorithms/src/ScaleX.cpp
@@ -143,8 +143,9 @@ void ScaleX::exec() {
     if ((i >= m_wi_min) && (i <= m_wi_max)) {
       double factor = getScaleFactor(inputW, i);
       // Do the offsetting
+      using std::placeholders::_1;
       std::transform(inX.begin(), inX.end(), outX.begin(),
-                     std::bind2nd(m_binOp, factor));
+                     std::bind(m_binOp, _1, factor));
       // reverse the vector if multiplicative factor was negative
       if (multiply && factor < 0.0) {
         std::reverse(outX.begin(), outX.end());
diff --git a/Framework/Algorithms/src/SofQWCentre.cpp b/Framework/Algorithms/src/SofQWCentre.cpp
index 735c7787499e284be094e47e6fe79dd497c46633..4f6569accd317cf2aaaceae3bc19ae86c43c54fb 100644
--- a/Framework/Algorithms/src/SofQWCentre.cpp
+++ b/Framework/Algorithms/src/SofQWCentre.cpp
@@ -199,10 +199,11 @@ void SofQWCentre::makeDistribution(API::MatrixWorkspace &outputWS,
   for (size_t i = 0; i < numQBins; ++i) {
     auto &Y = outputWS.mutableY(i);
     auto &E = outputWS.mutableE(i);
+    using std::placeholders::_1;
     std::transform(Y.begin(), Y.end(), Y.begin(),
-                   std::bind2nd(std::divides<double>(), widths[i + 1]));
+                   std::bind(std::divides<double>(), _1, widths[i + 1]));
     std::transform(E.begin(), E.end(), E.begin(),
-                   std::bind2nd(std::divides<double>(), widths[i + 1]));
+                   std::bind(std::divides<double>(), _1, widths[i + 1]));
   }
 }
 
diff --git a/Framework/Algorithms/src/SphericalAbsorption.cpp b/Framework/Algorithms/src/SphericalAbsorption.cpp
index 16e534704cdf319f91d1c3b5a1810edb137a5d9e..87d4a47488432e7e72bb38a7e347794694032fad 100644
--- a/Framework/Algorithms/src/SphericalAbsorption.cpp
+++ b/Framework/Algorithms/src/SphericalAbsorption.cpp
@@ -116,13 +116,11 @@ void SphericalAbsorption::retrieveBaseProperties() {
   double sigma_s = getProperty("ScatteringXSection");      // in barns
   double rho = getProperty("SampleNumberDensity");         // in Angstroms-3
   const Material &sampleMaterial = m_inputWS->sample().getMaterial();
-  if (sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda) !=
-      0.0) {
+  if (sampleMaterial.totalScatterXSection() != 0.0) {
     if (rho == EMPTY_DBL())
       rho = sampleMaterial.numberDensity();
     if (sigma_s == EMPTY_DBL())
-      sigma_s =
-          sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda);
+      sigma_s = sampleMaterial.totalScatterXSection();
     if (sigma_atten == EMPTY_DBL())
       sigma_atten = sampleMaterial.absorbXSection(NeutronAtom::ReferenceLambda);
   } else // Save input in Sample with wrong atomic number and name
diff --git a/Framework/Algorithms/test/CopySampleTest.h b/Framework/Algorithms/test/CopySampleTest.h
index d24adb45a31177f39c4edd261099db9e3738bf73..a3b3b6e50c2d9148159e5ec674809fcd3e082390 100644
--- a/Framework/Algorithms/test/CopySampleTest.h
+++ b/Framework/Algorithms/test/CopySampleTest.h
@@ -104,7 +104,7 @@ public:
     TS_ASSERT_EQUALS(copy.getOrientedLattice().c(), 3.0);
     TS_ASSERT_EQUALS(copy.getEnvironment().name(), "TestKit");
     TS_ASSERT_EQUALS(copy.getEnvironment().nelements(), 1);
-    TS_ASSERT_DELTA(copy.getMaterial().cohScatterXSection(2.1), 0.0184, 1e-02);
+    TS_ASSERT_DELTA(copy.getMaterial().cohScatterXSection(), 0.0184, 1e-02);
     TS_ASSERT_EQUALS(copy.getShape().getName(), s.getShape().getName());
 
     // Remove workspace from the data service.
@@ -154,7 +154,7 @@ public:
     TS_ASSERT(!copy.hasOrientedLattice());
     TS_ASSERT_EQUALS(copy.getEnvironment().name(), "TestKit");
     TS_ASSERT_EQUALS(copy.getEnvironment().nelements(), 1);
-    TS_ASSERT_DELTA(copy.getMaterial().cohScatterXSection(2.1), 0.0184, 1e-02);
+    TS_ASSERT_DELTA(copy.getMaterial().cohScatterXSection(), 0.0184, 1e-02);
     TS_ASSERT_DIFFERS(copy.getShape().getName(), s.getShape().getName());
 
     // Remove workspace from the data service.
diff --git a/Framework/Algorithms/test/CylinderAbsorptionTest.h b/Framework/Algorithms/test/CylinderAbsorptionTest.h
index 82f3e4df853712664f0f1d21b501f39b941c4d63..e935eb42d8e96897d7dd5b8622726175dcc6bedb 100644
--- a/Framework/Algorithms/test/CylinderAbsorptionTest.h
+++ b/Framework/Algorithms/test/CylinderAbsorptionTest.h
@@ -125,7 +125,6 @@ public:
     setsample.setProperty("Material", material);
     setsample.setProperty("Geometry", geometry);
     setsample.execute();
-    testWS = setsample.getProperty("InputWorkspace");
 
     // run the actual algorithm
     std::string outputWS("factors");
@@ -183,7 +182,6 @@ public:
     setsample.setProperty("Material", material);
     setsample.setProperty("Geometry", geometry);
     setsample.execute();
-    testWS = setsample.getProperty("InputWorkspace");
 
     // run the actual algorithm
     std::string outputWS("factors");
@@ -241,7 +239,6 @@ public:
     setsample.setProperty("Material", material);
     setsample.setProperty("Geometry", geometry);
     setsample.execute();
-    testWS = setsample.getProperty("InputWorkspace");
 
     // run the actual algorithm
     std::string outputWS("factors");
diff --git a/Framework/Algorithms/test/FindCenterOfMassPosition2Test.h b/Framework/Algorithms/test/FindCenterOfMassPosition2Test.h
index de4cbd277ba64be1af6b3debbef3f54e64aeb7c2..b65e97b7fc20396887a265466a6228512729cd30 100644
--- a/Framework/Algorithms/test/FindCenterOfMassPosition2Test.h
+++ b/Framework/Algorithms/test/FindCenterOfMassPosition2Test.h
@@ -56,6 +56,10 @@ public:
         double dx = (center_x - (double)ix);
         double dy = (center_y - (double)iy);
         Y[0] = exp(-(dx * dx + dy * dy));
+        // Set tube extrema to special values
+        if (iy == 0 || iy == SANSInstrumentCreationHelper::nBins - 1)
+          Y[0] =
+              iy % 2 ? std::nan("") : std::numeric_limits<double>::infinity();
         E[0] = 1;
       }
     }
diff --git a/Framework/Algorithms/test/WienerSmoothTest.h b/Framework/Algorithms/test/WienerSmoothTest.h
index e41d28534402841c1183a7b6d07a2760fac183dc..2e23642e37ab259b48dfb7aeaaee2913128084b4 100644
--- a/Framework/Algorithms/test/WienerSmoothTest.h
+++ b/Framework/Algorithms/test/WienerSmoothTest.h
@@ -570,11 +570,11 @@ private:
       std::vector<double> diff(inY.size());
       std::transform(inY.begin(), inY.end(), outY.begin(), diff.begin(),
                      std::minus<double>());
-
+      using std::placeholders::_1;
       auto countPos = std::count_if(diff.begin(), diff.end(),
-                                    std::bind2nd(std::greater<double>(), 0.0));
+                                    std::bind(std::greater<double>(), _1, 0.0));
       auto countNeg = std::count_if(diff.begin(), diff.end(),
-                                    std::bind2nd(std::less<double>(), 0.0));
+                                    std::bind(std::less<double>(), _1, 0.0));
 
       // the delta here is just a guess
       TS_ASSERT_DELTA(double(countPos) / double(countNeg), 1.0, 1e-1);
@@ -646,9 +646,9 @@ private:
       X.assign(x, x + nx);
       Y.assign(y, y + ny);
       E.assign(e, e + ny);
-
+      using std::placeholders::_1;
       std::transform(Y.begin(), Y.end(), Y.begin(),
-                     std::bind2nd(std::multiplies<double>(), double(i + 1)));
+                     std::bind(std::multiplies<double>(), _1, i + 1));
     }
 
     return dataWS;
diff --git a/Framework/CMakeLists.txt b/Framework/CMakeLists.txt
index 6203c15f206e527c56092a87661b2f3c675de134..1d43ec4e7099de6adf314681ceadf232bbcff1a6 100644
--- a/Framework/CMakeLists.txt
+++ b/Framework/CMakeLists.txt
@@ -90,14 +90,14 @@ include_directories(Geometry/inc)
 add_subdirectory(Geometry)
 set(MANTIDLIBS ${MANTIDLIBS} Geometry)
 
-include_directories(NexusGeometry/inc)
-add_subdirectory(NexusGeometry)
-set(MANTIDLIBS ${MANTIDLIBS} NexusGeometry)
-
 include_directories(API/inc)
 add_subdirectory(API)
 set(MANTIDLIBS ${MANTIDLIBS} API)
 
+include_directories(NexusGeometry/inc)
+add_subdirectory(NexusGeometry)
+set(MANTIDLIBS ${MANTIDLIBS} NexusGeometry)
+
 add_subdirectory(PythonInterface)
 
 include_directories(DataObjects/inc)
diff --git a/Framework/Crystal/inc/MantidCrystal/SaveHKL.h b/Framework/Crystal/inc/MantidCrystal/SaveHKL.h
index 758168dacc5a583adc38d3ed89b7e1c20c5d1d2a..2e3db3ea88fac0fd61a5d073e22d54b3a76f8885 100644
--- a/Framework/Crystal/inc/MantidCrystal/SaveHKL.h
+++ b/Framework/Crystal/inc/MantidCrystal/SaveHKL.h
@@ -23,14 +23,14 @@ namespace Crystal {
 class DLLExport SaveHKL : public API::Algorithm {
 public:
   /// Algorithm's name for identification
-  const std::string name() const override { return "SaveHKL"; };
+  const std::string name() const override { return "SaveHKL"; }
   /// Summary of algorithms purpose
   const std::string summary() const override {
     return "Save a PeaksWorkspace to a ASCII .hkl file.";
   }
 
   /// Algorithm's version for identification
-  int version() const override { return 1; };
+  int version() const override { return 1; }
   const std::vector<std::string> seeAlso() const override {
     return {"LoadHKL"};
   }
@@ -45,16 +45,16 @@ private:
   /// Run the algorithm
   void exec() override;
 
-  double absor_sphere(double &twoth, double &wl, double &tbar);
-  double m_smu = 0.0;    // in 1/cm
-  double m_amu = 0.0;    // in 1/cm
-  double m_radius = 0.0; // in cm
-  double m_power_th = 0.0;
+  double absorbSphere(double radius, double twoth, double wl, double &tbar);
   double spectrumCalc(double TOF, int iSpec,
                       std::vector<std::vector<double>> time,
                       std::vector<std::vector<double>> spectra, size_t id);
-  DataObjects::PeaksWorkspace_sptr ws;
   void sizeBanks(std::string bankName, int &nCols, int &nRows);
+
+  DataObjects::PeaksWorkspace_sptr m_ws;
+  double m_smu = 0.0; // in 1/cm
+  double m_amu = 0.0; // in 1/cm
+  double m_power_th = 0.0;
 };
 
 } // namespace Crystal
diff --git a/Framework/Crystal/src/AnvredCorrection.cpp b/Framework/Crystal/src/AnvredCorrection.cpp
index 02b7661c33011988b7c65d27b501a72b5190e782..a13750edbba1e7c4970bc6c21dc749db579ebd9f 100644
--- a/Framework/Crystal/src/AnvredCorrection.cpp
+++ b/Framework/Crystal/src/AnvredCorrection.cpp
@@ -348,8 +348,7 @@ void AnvredCorrection::retrieveBaseProperties() {
   m_power_th = getProperty("PowerLambda");     // in cm
   const Material &sampleMaterial = m_inputWS->sample().getMaterial();
 
-  const double scatterXSection =
-      sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda);
+  const double scatterXSection = sampleMaterial.totalScatterXSection();
 
   if (scatterXSection != 0.0) {
     double rho = sampleMaterial.numberDensity();
diff --git a/Framework/Crystal/src/FindSXPeaksHelper.cpp b/Framework/Crystal/src/FindSXPeaksHelper.cpp
index a16628dfd3c1bff42e67c0af8b443613d7472b14..32384c4956811492fe3e8448ab89cb29fbefd57b 100644
--- a/Framework/Crystal/src/FindSXPeaksHelper.cpp
+++ b/Framework/Crystal/src/FindSXPeaksHelper.cpp
@@ -307,12 +307,12 @@ PeakFindingStrategy::getBounds(const HistogramData::HistogramX &x) const {
   auto lowit = (m_minValue == EMPTY_DBL())
                    ? x.begin()
                    : std::lower_bound(x.begin(), x.end(), m_minValue);
-
+  using std::placeholders::_1;
   auto highit =
       (m_maxValue == EMPTY_DBL())
           ? x.end()
           : std::find_if(lowit, x.end(),
-                         std::bind2nd(std::greater<double>(), m_maxValue));
+                         std::bind(std::greater<double>(), _1, m_maxValue));
 
   return std::make_pair(lowit, highit);
 }
diff --git a/Framework/Crystal/src/SaveHKL.cpp b/Framework/Crystal/src/SaveHKL.cpp
index f1d067736a76585ec15661f0139bd5fe03762e57..4c4c2811e65c7761c67747fa0108abfdde2cbd8d 100644
--- a/Framework/Crystal/src/SaveHKL.cpp
+++ b/Framework/Crystal/src/SaveHKL.cpp
@@ -11,6 +11,7 @@
 #include "MantidCrystal/AnvredCorrection.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
 #include "MantidGeometry/Instrument/RectangularDetector.h"
+#include "MantidGeometry/Objects/ShapeFactory.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/Material.h"
@@ -106,11 +107,11 @@ void SaveHKL::init() {
 void SaveHKL::exec() {
 
   std::string filename = getPropertyValue("Filename");
-  ws = getProperty("InputWorkspace");
+  m_ws = getProperty("InputWorkspace");
 
   PeaksWorkspace_sptr peaksW = getProperty("OutputWorkspace");
-  if (peaksW != ws)
-    peaksW = ws->clone();
+  if (peaksW != m_ws)
+    peaksW = m_ws->clone();
   auto inst = peaksW->getInstrument();
   std::vector<Peak> peaks = peaksW->getPeaks();
   double scaleFactor = getProperty("ScalePeaks");
@@ -230,55 +231,59 @@ void SaveHKL::exec() {
   std::vector<std::vector<double>> spectra;
   std::vector<std::vector<double>> time;
   int iSpec = 0;
+
   m_smu = getProperty("LinearScatteringCoef"); // in 1/cm
   m_amu = getProperty("LinearAbsorptionCoef"); // in 1/cm
-  m_radius = getProperty("Radius");            // in cm
   m_power_th = getProperty("PowerLambda");     // in cm
-  const Material &sampleMaterial = peaksW->sample().getMaterial();
-  if (sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda) !=
-      0.0) {
-    double rho = sampleMaterial.numberDensity();
-    if (m_smu == EMPTY_DBL())
-      m_smu =
-          sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda) *
-          rho;
-    if (m_amu == EMPTY_DBL())
-      m_amu = sampleMaterial.absorbXSection(NeutronAtom::ReferenceLambda) * rho;
-    g_log.notice() << "Sample number density = " << rho
-                   << " atoms/Angstrom^3\n";
-    g_log.notice() << "Cross sections for wavelength = "
-                   << NeutronAtom::ReferenceLambda << "Angstroms\n"
-                   << "    Coherent = " << sampleMaterial.cohScatterXSection()
-                   << " barns\n"
-                   << "    Incoherent = "
-                   << sampleMaterial.incohScatterXSection() << " barns\n"
-                   << "    Total = " << sampleMaterial.totalScatterXSection()
-                   << " barns\n"
-                   << "    Absorption = " << sampleMaterial.absorbXSection()
-                   << " barns\n";
-  } else if (m_smu != EMPTY_DBL() &&
-             m_amu != EMPTY_DBL()) // Save input in Sample
-                                   // with wrong atomic
-                                   // number and name
-  {
-    NeutronAtom neutron(0, 0, 0.0, 0.0, m_smu, 0.0, m_smu, m_amu);
-    auto shape = boost::shared_ptr<IObject>(
-        peaksW->sample().getShape().cloneWithMaterial(
-            Material("SetInSaveHKL", neutron, 1.0)));
-    peaksW->mutableSample().setShape(shape);
-  }
-  if (m_smu != EMPTY_DBL() && m_amu != EMPTY_DBL())
-    g_log.notice() << "LinearScatteringCoef = " << m_smu << " 1/cm\n"
-                   << "LinearAbsorptionCoef = " << m_amu << " 1/cm\n"
-                   << "Radius = " << m_radius << " cm\n"
-                   << "Power Lorentz corrections = " << m_power_th << " \n";
+
+  // Function to calculate total attenuation for a track
+  auto calculateAttenuation = [](const Track &path, double lambda) {
+    double factor(1.0);
+    for (const auto &segment : path) {
+      const double length = segment.distInsideObject;
+      const auto &segObj = *(segment.object);
+      const auto &segMat = segObj.material();
+      factor *= segMat.attenuation(length, lambda);
+    }
+    return factor;
+  };
+
   API::Run &run = peaksW->mutableRun();
-  if (run.hasProperty("Radius")) {
-    if (m_radius == EMPTY_DBL())
-      m_radius = run.getPropertyValueAsType<double>("Radius");
+
+  double radius = getProperty("Radius"); // in cm
+  if (radius != EMPTY_DBL()) {
+    run.addProperty<double>("Radius", radius, true);
   } else {
-    run.addProperty<double>("Radius", m_radius, true);
+    if (run.hasProperty("Radius"))
+      radius = run.getPropertyValueAsType<double>("Radius");
   }
+
+  const auto sourcePos = inst->getSource()->getPos();
+  const auto samplePos = inst->getSample()->getPos();
+  const auto reverseBeamDir = normalize(samplePos - sourcePos);
+
+  const Material &sampleMaterial = peaksW->sample().getMaterial();
+  const IObject *sampleShape{nullptr};
+  // special case of sphere else assume the sample shape and material have been
+  // set already
+  if (radius != EMPTY_DBL()) {
+    // create sphere shape
+    auto sphere = ShapeFactory::createSphere(V3D(), radius * 0.01);
+    if (m_smu != EMPTY_DBL() && m_amu != EMPTY_DBL()) {
+      // Save input in Sample with wrong atomic number and name
+      NeutronAtom neutron(0, 0, 0.0, 0.0, m_smu, 0.0, m_smu, m_amu);
+      sphere->setMaterial(Material("SetInSaveHKL", neutron, 1.0));
+    } else {
+      sphere->setMaterial(sampleMaterial);
+      const double rho = sampleMaterial.numberDensity();
+      m_smu = sampleMaterial.totalScatterXSection() * rho;
+      m_amu = sampleMaterial.absorbXSection(NeutronAtom::ReferenceLambda) * rho;
+    }
+    peaksW->mutableSample().setShape(sphere);
+  } else if (peaksW->sample().getShape().hasValidShape()) {
+    sampleShape = &(peaksW->sample().getShape());
+  }
+
   if (correctPeaks) {
     std::vector<double> spec(11);
     std::string STRING;
@@ -310,8 +315,7 @@ void SaveHKL::exec() {
       infile.close();
     }
   }
-  // ============================== Save all Peaks
-  // =========================================
+  // =================== Save all Peaks ======================================
   std::set<size_t> banned;
   // Go through each peak at this run / bank
 
@@ -367,8 +371,6 @@ void SaveHKL::exec() {
                        bankName.end());
         Strings::convert(bankName, bank);
 
-        double tbar = 0;
-
         // Two-theta = polar angle = scattering angle = between +Z vector and
         // the
         // scattered beam
@@ -379,9 +381,25 @@ void SaveHKL::exec() {
           banned.insert(wi);
           continue;
         }
-        double transmission = 0;
-        if (m_smu != EMPTY_DBL() && m_amu != EMPTY_DBL()) {
-          transmission = absor_sphere(scattering, lambda, tbar);
+
+        double transmission{0}, tbar{0};
+        if (radius != EMPTY_DBL()) {
+          // keep original method if radius is provided
+          transmission = absorbSphere(radius, scattering, lambda, tbar);
+        } else if (sampleShape) {
+          Track beforeScatter(samplePos, reverseBeamDir);
+          sampleShape->interceptSurface(beforeScatter);
+          const auto detDir = normalize(p.getDetPos() - samplePos);
+          Track afterScatter(samplePos, detDir);
+          sampleShape->interceptSurface(afterScatter);
+
+          transmission = calculateAttenuation(beforeScatter, lambda) *
+                         calculateAttenuation(afterScatter, lambda);
+          const auto &mat = sampleShape->material();
+          const auto mu =
+              (mat.totalScatterXSection() + mat.absorbXSection(lambda)) *
+              mat.numberDensity();
+          tbar = -std::log(transmission) / mu;
         }
 
         // Anvred write from Art Schultz/
@@ -523,9 +541,9 @@ void SaveHKL::exec() {
           // This is the scattered beam direction
           V3D dir = p.getDetPos() - inst->getSample()->getPos();
 
-          // "Azimuthal" angle: project the scattered beam direction onto the XY
-          // plane,
-          // and calculate the angle between that and the +X axis (right-handed)
+          // "Azimuthal" angle: project the scattered beam direction onto the
+          // XY plane, and calculate the angle between that and the +X axis
+          // (right-handed)
           double az = atan2(dir.Y(), dir.X());
           R_IPNS[0] = std::cos(twoth) * l2;
           R_IPNS[1] = std::cos(az) * std::sin(twoth) * l2;
@@ -615,7 +633,8 @@ void SaveHKL::exec() {
   }
   peaksW->removePeaks(std::move(badPeaks));
   setProperty("OutputWorkspace", peaksW);
-}
+} // namespace Crystal
+
 /**
  *       function to calculate a spherical absorption correction
  *       and tbar. based on values in:
@@ -632,7 +651,8 @@ p *       input are the smu (scattering) and amu (absorption at 1.8 ang.)
  *
  *       a. j. schultz, june, 2008
  */
-double SaveHKL::absor_sphere(double &twoth, double &wl, double &tbar) {
+double SaveHKL::absorbSphere(double radius, double twoth, double wl,
+                             double &tbar) {
   int i;
   double mu, mur; // mu is the linear absorption coefficient,
   // r is the radius of the spherical sample.
@@ -646,7 +666,7 @@ double SaveHKL::absor_sphere(double &twoth, double &wl, double &tbar) {
 
   mu = m_smu + (m_amu / 1.8f) * wl;
 
-  mur = mu * m_radius;
+  mur = mu * radius;
   if (mur < 0. || mur > 2.5) {
     std::ostringstream s;
     s << mur;
@@ -731,7 +751,7 @@ void SaveHKL::sizeBanks(std::string bankName, int &nCols, int &nRows) {
   if (bankName == "None")
     return;
   boost::shared_ptr<const IComponent> parent =
-      ws->getInstrument()->getComponentByName(bankName);
+      m_ws->getInstrument()->getComponentByName(bankName);
   if (!parent)
     return;
   if (parent->type() == "RectangularDetector") {
@@ -741,7 +761,7 @@ void SaveHKL::sizeBanks(std::string bankName, int &nCols, int &nRows) {
     nCols = RDet->xpixels();
     nRows = RDet->ypixels();
   } else {
-    if (ws->getInstrument()->getName() ==
+    if (m_ws->getInstrument()->getName() ==
         "CORELLI") // for Corelli with sixteenpack under bank
     {
       std::vector<Geometry::IComponent_const_sptr> children;
diff --git a/Framework/Crystal/test/LoadHKLTest.h b/Framework/Crystal/test/LoadHKLTest.h
index c440e7b35fe6d60557d7fd5402e12ea9a14fdb3c..3253fec83c255ce9fa9edf73a727562eba14ab79 100644
--- a/Framework/Crystal/test/LoadHKLTest.h
+++ b/Framework/Crystal/test/LoadHKLTest.h
@@ -111,11 +111,9 @@ public:
     TS_ASSERT_DELTA(p.getDSpacing(), 3.5933, 1e-4);
     double radius;
     const auto sampleMaterial = wsout->sample().getMaterial();
-    if (sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda) !=
-        0.0) {
+    if (sampleMaterial.totalScatterXSection() != 0.0) {
       double rho = sampleMaterial.numberDensity();
-      smu = sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda) *
-            rho;
+      smu = sampleMaterial.totalScatterXSection() * rho;
       amu = sampleMaterial.absorbXSection(NeutronAtom::ReferenceLambda) * rho;
     } else {
       throw std::invalid_argument(
diff --git a/Framework/Crystal/test/SaveHKLTest.h b/Framework/Crystal/test/SaveHKLTest.h
index 3d389f1293819064449a0da726c7ef57ed70fc4f..d12739bc9c541981de15d506d2136c38edb37953 100644
--- a/Framework/Crystal/test/SaveHKLTest.h
+++ b/Framework/Crystal/test/SaveHKLTest.h
@@ -13,6 +13,7 @@
 #include "MantidDataObjects/Peak.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/IDTypes.h"
+#include "MantidGeometry/Objects/ShapeFactory.h"
 #include "MantidKernel/Material.h"
 #include "MantidKernel/System.h"
 #include "MantidKernel/Timer.h"
@@ -37,24 +38,66 @@ public:
     TS_ASSERT(alg.isInitialized())
   }
 
-  void do_test(int numRuns, size_t numBanks, size_t numPeaksPerBank) {
-    Instrument_sptr inst =
-        ComponentCreationHelper::createTestInstrumentRectangular(4, 10, 1.0);
-    PeaksWorkspace_sptr ws(new PeaksWorkspace());
-    ws->setInstrument(inst);
-    double smu = 0.357;
-    double amu = 0.011;
-    NeutronAtom neutron(0, 0, 0.0, 0.0, smu, 0.0, smu, amu);
+  void test_empty_workspace() {
+    const bool expectEmptyFile{true};
+    assertFileContent(assertSaveExec(createTestPeaksWorkspace(0, 0, 0)),
+                      expectEmptyFile);
+  }
 
+  void test_save_using_sample_shape_object_and_radius_log_entry() {
+    auto ws = createTestPeaksWorkspace(4, 4, 2);
+    const double smu{0.357}, amu{0.011};
+    NeutronAtom neutron(0, 0, 0.0, 0.0, smu, 0.0, smu, amu);
     auto sampleShape = boost::make_shared<CSGObject>();
     sampleShape->setMaterial(Material("SetInSaveHKLTest", neutron, 1.0));
     ws->mutableSample().setShape(sampleShape);
-
     API::Run &mrun = ws->mutableRun();
     mrun.addProperty<double>("Radius", 0.1, true);
 
-    for (int run = 1000; run < numRuns + 1000; run++)
-      for (size_t b = 1; b <= numBanks; b++)
+    const bool expectEmptyFile{false};
+    const double expectedTbar{0.1591}, expectedTransmission{0.9434};
+
+    assertFileContent(assertSaveExec(ws), expectEmptyFile, expectedTbar,
+                      expectedTransmission);
+  }
+
+  void test_save_using_sample_shape_object_and_sample_material() {
+    auto ws = createTestPeaksWorkspace(4, 4, 2);
+    const double smu{0.357}, amu{0.011};
+    const double radius{0.1};
+    NeutronAtom neutron(0, 0, 0.0, 0.0, smu, 0.0, smu, amu);
+    auto sampleShape = ShapeFactory::createSphere(V3D(), radius * 0.01);
+    sampleShape->setMaterial(Material("SetInSaveHKLTest", neutron, 1.0));
+    ws->mutableSample().setShape(sampleShape);
+
+    const bool expectEmptyFile{false};
+    const double expectedTbar{0.2}, expectedTransmission{0.9294};
+
+    assertFileContent(assertSaveExec(ws), expectEmptyFile, expectedTbar,
+                      expectedTransmission);
+  }
+
+  void test_save_using_sample_properties_on_algorithm() {
+    auto ws = createTestPeaksWorkspace(4, 4, 2);
+    const double smu{0.357}, amu{0.011};
+    const double radius{0.1};
+
+    const bool expectEmptyFile{false};
+    const double expectedTbar{0.1591}, expectedTransmission{0.9434};
+    assertFileContent(assertSaveExec(ws, radius, smu, amu), expectEmptyFile,
+                      expectedTbar, expectedTransmission);
+  }
+
+private:
+  PeaksWorkspace_sptr createTestPeaksWorkspace(int numRuns, size_t numBanks,
+                                               size_t numPeaksPerBank) {
+    Instrument_sptr inst =
+        ComponentCreationHelper::createTestInstrumentRectangular(4, 10, 1.0);
+    auto ws = boost::make_shared<PeaksWorkspace>();
+    ws->setInstrument(inst);
+
+    for (int run = 1000; run < numRuns + 1000; run++) {
+      for (size_t b = 1; b <= numBanks; b++) {
         for (size_t i = 0; i < numPeaksPerBank; i++) {
           V3D hkl(static_cast<double>(i), static_cast<double>(i),
                   static_cast<double>(i));
@@ -68,53 +111,67 @@ public:
           p.setBinCount(static_cast<double>(i));
           ws->addPeak(p);
         }
+      }
+    }
+    return ws;
+  }
+
+  std::string assertSaveExec(const PeaksWorkspace_sptr &peaksWS,
+                             const double radius = -1.0,
+                             const double smu = -1.0, const double amu = -1.0) {
 
     std::string outfile = "./SaveHKLTest.hkl";
     SaveHKL alg;
     TS_ASSERT_THROWS_NOTHING(alg.initialize())
     TS_ASSERT(alg.isInitialized())
-    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspace", ws));
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspace", peaksWS));
     TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Filename", outfile));
+    if (radius > 0.0)
+      TS_ASSERT_THROWS_NOTHING(alg.setProperty("Radius", radius));
+    if (smu > 0.0)
+      TS_ASSERT_THROWS_NOTHING(alg.setProperty("LinearScatteringCoef", smu));
+    if (amu > 0.0)
+      TS_ASSERT_THROWS_NOTHING(alg.setProperty("LinearAbsorptionCoef", amu));
+
     TS_ASSERT_THROWS_NOTHING(alg.execute(););
     TS_ASSERT(alg.isExecuted());
 
     // Get the file
     outfile = alg.getPropertyValue("Filename");
-    bool fileExists = false;
-    TS_ASSERT(fileExists = Poco::File(outfile).exists());
-    if (fileExists) {
-      std::ifstream in(outfile.c_str());
-
-      double d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14;
-      if (numPeaksPerBank > 0) {
-        in >> d1 >> d2 >> d3 >> d4 >> d5 >> d6 >> d7 >> d8 >> d9 >> d10 >>
-            d11 >> d12 >> d13 >> d14;
-        TS_ASSERT_EQUALS(d1, -1);
-        TS_ASSERT_EQUALS(d2, -1);
-        TS_ASSERT_EQUALS(d3, -1);
-        TS_ASSERT_EQUALS(d4, 1.1);
-        TS_ASSERT_EQUALS(d5, 1);
-        TS_ASSERT_EQUALS(d6, 1);
-        TS_ASSERT_EQUALS(d7, 1.5);
-        TS_ASSERT_EQUALS(d8, 0.1591);
-        TS_ASSERT_EQUALS(d9, 1000.);
-        TS_ASSERT_EQUALS(d10, 2);
-        TS_ASSERT_EQUALS(d11, 0.9434);
-        TS_ASSERT_EQUALS(d12, 1);
-        TS_ASSERT_DELTA(d13, 0.4205, 1e-4);
-        TS_ASSERT_EQUALS(d14, 3.5933);
-      }
-    }
-
-    if (Poco::File(outfile).exists())
-      Poco::File(outfile).remove();
+    TS_ASSERT(Poco::File(outfile).exists());
+    return outfile;
   }
 
-  /// Test with an empty PeaksWorkspace
-  void test_empty() { do_test(0, 0, 0); }
+  void assertFileContent(const std::string &filepath, const bool expectedEmpty,
+                         const double expectedTbar = -1.0,
+                         const double expectedTransmission = -1.0) {
+
+    if (expectedEmpty)
+      return;
 
-  /// Test with a few peaks
-  void test_exec() { do_test(2, 4, 4); }
+    std::ifstream in(filepath.c_str());
+    double d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14;
+    in >> d1 >> d2 >> d3 >> d4 >> d5 >> d6 >> d7 >> d8 >> d9 >> d10 >> d11 >>
+        d12 >> d13 >> d14;
+    TS_ASSERT_EQUALS(d1, -1);
+    TS_ASSERT_EQUALS(d2, -1);
+    TS_ASSERT_EQUALS(d3, -1);
+    TS_ASSERT_EQUALS(d4, 1.1);
+    TS_ASSERT_EQUALS(d5, 1);
+    TS_ASSERT_EQUALS(d6, 1);
+    TS_ASSERT_EQUALS(d7, 1.5);
+    TS_ASSERT_EQUALS(d8, expectedTbar);
+    TS_ASSERT_EQUALS(d9, 1000.);
+    TS_ASSERT_EQUALS(d10, 2);
+    TS_ASSERT_EQUALS(d11, expectedTransmission);
+    TS_ASSERT_EQUALS(d12, 1);
+    TS_ASSERT_DELTA(d13, 0.4205, 1e-4);
+    TS_ASSERT_EQUALS(d14, 3.5933);
+    in.close();
+
+    if (Poco::File(filepath).exists())
+      Poco::File(filepath).remove();
+  }
 };
 
 #endif /* MANTID_CRYSTAL_SAVEHKLTEST_H_ */
diff --git a/Framework/Crystal/test/SortHKLTest.h b/Framework/Crystal/test/SortHKLTest.h
index 2ea3fd49502dd72a4e37cc73319e5d52d9408e25..0f00d69cd6ea9bc7894455de31ba0b518b2014e0 100644
--- a/Framework/Crystal/test/SortHKLTest.h
+++ b/Framework/Crystal/test/SortHKLTest.h
@@ -106,11 +106,9 @@ public:
     TS_ASSERT_EQUALS(p.getRunNumber(), 1000.);
     TS_ASSERT_DELTA(p.getDSpacing(), 3.5933, 1e-4);
     const auto sampleMaterial = wsout->sample().getMaterial();
-    if (sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda) !=
-        0.0) {
+    if (sampleMaterial.totalScatterXSection() != 0.0) {
       double rho = sampleMaterial.numberDensity();
-      smu = sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda) *
-            rho;
+      smu = sampleMaterial.totalScatterXSection() * rho;
       amu = sampleMaterial.absorbXSection(NeutronAtom::ReferenceLambda) * rho;
     } else {
       throw std::invalid_argument(
diff --git a/Framework/CurveFitting/CMakeLists.txt b/Framework/CurveFitting/CMakeLists.txt
index 07311468c15fa95e62f12953c19af2a079e713f6..76452b5f1af2a1b3cfd3594ff111ccfd429b798b 100644
--- a/Framework/CurveFitting/CMakeLists.txt
+++ b/Framework/CurveFitting/CMakeLists.txt
@@ -2,8 +2,8 @@ set(SRC_FILES
     src/Algorithms/CalculateChiSquared.cpp
     src/Algorithms/CalculateCostFunction.cpp
     src/Algorithms/ConvertToYSpace.cpp
-    src/Algorithms/ConvolveWorkspaces.cpp
     src/Algorithms/ConvolutionFit.cpp
+    src/Algorithms/ConvolveWorkspaces.cpp
     src/Algorithms/CrystalFieldEnergies.cpp
     src/Algorithms/EstimateFitParameters.cpp
     src/Algorithms/EstimatePeakErrors.cpp
@@ -17,8 +17,8 @@ set(SRC_FILES
     src/Algorithms/NormaliseByPeakArea.cpp
     src/Algorithms/PawleyFit.cpp
     src/Algorithms/PlotPeakByLogValue.cpp
-    src/Algorithms/QENSFitSimultaneous.cpp
     src/Algorithms/QENSFitSequential.cpp
+    src/Algorithms/QENSFitSimultaneous.cpp
     src/Algorithms/RefinePowderInstrumentParameters.cpp
     src/Algorithms/RefinePowderInstrumentParameters3.cpp
     src/Algorithms/SplineBackground.cpp
@@ -34,6 +34,7 @@ set(SRC_FILES
     src/CostFunctions/CostFuncLeastSquares.cpp
     src/CostFunctions/CostFuncRwp.cpp
     src/CostFunctions/CostFuncUnweightedLeastSquares.cpp
+    src/ExcludeRangeFinder.cpp
     src/FitMW.cpp
     src/FuncMinimizers/BFGS_Minimizer.cpp
     src/FuncMinimizers/DampedGaussNewtonMinimizer.cpp
@@ -155,7 +156,8 @@ set(SRC_FILES
     src/RalNlls/Workspaces.cpp
     src/SeqDomain.cpp
     src/SeqDomainSpectrumCreator.cpp
-    src/SpecialFunctionHelper.cpp)
+    src/SpecialFunctionHelper.cpp
+    src/TableWorkspaceDomainCreator.cpp)
 
 set(SRC_UNITY_IGNORE_FILES src/Fit1D.cpp src/GSLFunctions.cpp)
 
@@ -178,8 +180,8 @@ set(INC_FILES
     inc/MantidCurveFitting/Algorithms/NormaliseByPeakArea.h
     inc/MantidCurveFitting/Algorithms/PawleyFit.h
     inc/MantidCurveFitting/Algorithms/PlotPeakByLogValue.h
-    inc/MantidCurveFitting/Algorithms/QENSFitSimultaneous.h
     inc/MantidCurveFitting/Algorithms/QENSFitSequential.h
+    inc/MantidCurveFitting/Algorithms/QENSFitSimultaneous.h
     inc/MantidCurveFitting/Algorithms/RefinePowderInstrumentParameters.h
     inc/MantidCurveFitting/Algorithms/RefinePowderInstrumentParameters3.h
     inc/MantidCurveFitting/Algorithms/SplineBackground.h
@@ -196,6 +198,7 @@ set(INC_FILES
     inc/MantidCurveFitting/CostFunctions/CostFuncRwp.h
     inc/MantidCurveFitting/CostFunctions/CostFuncUnweightedLeastSquares.h
     inc/MantidCurveFitting/DllConfig.h
+    inc/MantidCurveFitting/ExcludeRangeFinder.h
     inc/MantidCurveFitting/FitMW.h
     inc/MantidCurveFitting/FortranDefs.h
     inc/MantidCurveFitting/FortranMatrix.h
@@ -318,14 +321,15 @@ set(INC_FILES
     inc/MantidCurveFitting/RalNlls/Workspaces.h
     inc/MantidCurveFitting/SeqDomain.h
     inc/MantidCurveFitting/SeqDomainSpectrumCreator.h
-    inc/MantidCurveFitting/SpecialFunctionSupport.h)
+    inc/MantidCurveFitting/SpecialFunctionSupport.h
+    inc/MantidCurveFitting/TableWorkspaceDomainCreator.h)
 
 set(TEST_FILES
     Algorithms/CalculateChiSquaredTest.h
     Algorithms/CalculateCostFunctionTest.h
     Algorithms/ConvertToYSpaceTest.h
-    Algorithms/ConvolveWorkspacesTest.h
     Algorithms/ConvolutionFitSequentialTest.h
+    Algorithms/ConvolveWorkspacesTest.h
     Algorithms/EstimateFitParametersTest.h
     Algorithms/EstimatePeakErrorsTest.h
     Algorithms/EvaluateFunctionTest.h
@@ -462,7 +466,8 @@ set(TEST_FILES
     MultiDomainFunctionTest.h
     ParameterEstimatorTest.h
     RalNlls/NLLSTest.h
-    SpecialFunctionSupportTest.h)
+    SpecialFunctionSupportTest.h
+    TableWorkspaceDomainCreatorTest.h)
 
 if(COVERALLS)
   foreach(loop_var ${SRC_FILES} ${INC_FILES})
diff --git a/Framework/CurveFitting/inc/MantidCurveFitting/ExcludeRangeFinder.h b/Framework/CurveFitting/inc/MantidCurveFitting/ExcludeRangeFinder.h
new file mode 100644
index 0000000000000000000000000000000000000000..e1f095d3e7f4fe8574733b0d8803b7dd034df693
--- /dev/null
+++ b/Framework/CurveFitting/inc/MantidCurveFitting/ExcludeRangeFinder.h
@@ -0,0 +1,50 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_CURVEFITTING_EXCLUDERANGEFINDER_H_
+#define MANTID_CURVEFITTING_EXCLUDERANGEFINDER_H_
+
+#include "MantidCurveFitting/DllConfig.h"
+
+#include <vector>
+
+namespace Mantid {
+namespace CurveFitting {
+
+/** ExcludeRangeFinder : Helper clss that finds if a point should be excluded
+from fit. It keeps the boundaries of the relevant exclusion region for the last
+checked value. A relevant region is the one which either includes the value or
+the nearest one with the left boundary greater than the value. The class also
+keeps the index of the region (its left boundary) for efficient search.
+*/
+class DLLExport ExcludeRangeFinder {
+public:
+  /// Constructor
+  ExcludeRangeFinder(const std::vector<double> &exclude, double startX,
+                     double endX);
+
+  /// Check if an x-value lies in an exclusion range
+  bool isExcluded(double value);
+
+private:
+  /// Find the range from m_exclude that may contain points x >= p
+  void findNextExcludedRange(double p);
+  /// Index of current excluded range
+  size_t m_exclIndex;
+  /// Start of current excluded range
+  double m_startExcludedRange;
+  /// End of current excluded range
+  double m_endExcludeRange;
+  /// Reference to a list of exclusion ranges.
+  const std::vector<double> m_exclude;
+  /// Size of m_exclude.
+  const size_t m_size;
+};
+
+} // namespace CurveFitting
+} // namespace Mantid
+
+#endif /* MANTID_CURVEFITTING_EXCLUDERANGEFINDER_H_ */
diff --git a/Framework/CurveFitting/inc/MantidCurveFitting/TableWorkspaceDomainCreator.h b/Framework/CurveFitting/inc/MantidCurveFitting/TableWorkspaceDomainCreator.h
new file mode 100644
index 0000000000000000000000000000000000000000..7ca825f84d1c6cc71f04b75d670b40f3d598f8cd
--- /dev/null
+++ b/Framework/CurveFitting/inc/MantidCurveFitting/TableWorkspaceDomainCreator.h
@@ -0,0 +1,159 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_CURVEFITTING_TABLEWORKSPACEDOMAINCREATOR_H_
+#define MANTID_CURVEFITTING_TABLEWORKSPACEDOMAINCREATOR_H_
+
+//----------------------------------------------------------------------
+// Includes
+//----------------------------------------------------------------------
+#include "MantidAPI/IDomainCreator.h"
+#include "MantidDataObjects/TableWorkspace.h"
+#include "MantidKernel/cow_ptr.h"
+
+#include <boost/weak_ptr.hpp>
+#include <list>
+
+namespace Mantid {
+namespace API {
+class FunctionDomain;
+class FunctionDomain1D;
+class FunctionValues;
+class TableWorkspace;
+} // namespace API
+
+namespace CurveFitting {
+
+class DLLExport TableWorkspaceDomainCreator : public API::IDomainCreator {
+public:
+  // Constructor
+  TableWorkspaceDomainCreator(Kernel::IPropertyManager *fit,
+                              const std::string &workspacePropertyName,
+                              DomainType domainType = Simple);
+  /// Constructor
+  TableWorkspaceDomainCreator(DomainType domainType = Simple);
+
+  // Declare properties that specify the dataset within the workspace to fit to.
+  void declareDatasetProperties(const std::string &suffix = "",
+                                bool addProp = true) override;
+
+  // Create a domain from the input workspace
+  void createDomain(boost::shared_ptr<API::FunctionDomain> &domain,
+                    boost::shared_ptr<API::FunctionValues> &values,
+                    size_t i0 = 0) override;
+
+  // Create an output workspace.
+  boost::shared_ptr<API::Workspace> createOutputWorkspace(
+      const std::string &baseName, API::IFunction_sptr function,
+      boost::shared_ptr<API::FunctionDomain> domain,
+      boost::shared_ptr<API::FunctionValues> values,
+      const std::string &outputWorkspacePropertyName) override;
+
+  /// Set the workspace
+  /// @param ws :: workspace to set.
+  void setWorkspace(API::ITableWorkspace_sptr ws) { m_tableWorkspace = ws; }
+  /// Set the startX and endX
+  /// @param startX :: Start of the domain
+  /// @param endX :: End of the domain
+  void setRange(double startX, double endX) {
+    m_startX = startX;
+    m_endX = endX;
+  }
+  /// Set max size for Sequential and Parallel domains
+  /// @param maxSize :: Maximum size of each simple domain
+  void setMaxSize(size_t maxSize) { m_maxSize = maxSize; }
+
+  /// Set the names Of the x, y and error columns
+  /// @param xColName :: name of the x column
+  /// @param yColName :: name of the y column
+  /// @param errColName :: name of the y error column
+  void setColumnNames(const std::string &xColName, const std::string &yColName,
+                      const std::string &errColName) const {
+    m_xColName = xColName;
+    m_yColName = yColName;
+    m_errColName = errColName;
+  }
+
+  size_t getDomainSize() const override;
+
+  void initFunction(API::IFunction_sptr function) override;
+
+private:
+  /// Calculate size and starting iterator in the X array
+  std::pair<size_t, size_t> getXInterval() const;
+  /// Set all parameters
+  void setParameters() const;
+  /// Set the names of the X, Y and Error columns
+  void setXYEColumnNames(API::ITableWorkspace_sptr ws) const;
+  /// Creates the blank output workspace of the correct size
+  boost::shared_ptr<API::MatrixWorkspace>
+  createEmptyResultWS(const size_t nhistograms, const size_t nyvalues);
+  /// Set initial values for parameters with default values.
+  void setInitialValues(API::IFunction &function);
+  // Unrolls function into its constituent parts if it is a composite and adds
+  // it to the list. Note this is recursive
+  void
+  appendCompositeFunctionMembers(std::list<API::IFunction_sptr> &functionList,
+                                 const API::IFunction_sptr &function) const;
+  // Create separate Convolutions for each component of the model of a
+  // convolution
+  void appendConvolvedCompositeFunctionMembers(
+      std::list<API::IFunction_sptr> &functionList,
+      const API::IFunction_sptr &function) const;
+  /// Add the calculated function values to the workspace
+  void addFunctionValuesToWS(
+      const API::IFunction_sptr &function,
+      boost::shared_ptr<API::MatrixWorkspace> &ws, const size_t wsIndex,
+      const boost::shared_ptr<API::FunctionDomain> &domain,
+      boost::shared_ptr<API::FunctionValues> resultValues) const;
+  /// Check workspace is in the correct form
+  void setAndValidateWorkspace(API::Workspace_sptr ws) const;
+
+  /// Store workspace property name
+  std::string m_workspacePropertyName;
+  /// Store startX property name
+  std::string m_startXPropertyName;
+  /// Store endX property name
+  std::string m_endXPropertyName;
+  /// Store XColumnName property name
+  std::string m_xColumnPropertyName;
+  /// Store YColumnName property name
+  std::string m_yColumnPropertyName;
+  /// Store errorColumnName property name
+  std::string m_errorColumnPropertyName;
+
+  /// The input TableWorkspace
+  mutable API::ITableWorkspace_sptr m_tableWorkspace;
+  /// startX
+  mutable double m_startX;
+  /// endX
+  mutable double m_endX;
+  /// Max size for seq domain
+  mutable size_t m_maxSize;
+  /// Ranges that must be excluded from fit
+  mutable std::vector<double> m_exclude;
+  /// Store the created domain and values
+  boost::weak_ptr<API::FunctionDomain1D> m_domain;
+  boost::weak_ptr<API::FunctionValues> m_values;
+  /// Store maxSize property name
+  std::string m_maxSizePropertyName;
+  /// Store the Exclude property name
+  std::string m_excludePropertyName;
+
+  /// Store number of the first row used in fitting
+  size_t m_startRowNo;
+  /// Store the X column name
+  mutable std::string m_xColName;
+  /// Store the Y column name
+  mutable std::string m_yColName;
+  /// Store the Y Error column name
+  mutable std::string m_errColName;
+};
+
+} // namespace CurveFitting
+} // namespace Mantid
+
+#endif /*MANTID_CURVEFITTING_TABLEWORKSPACEDOMAINCREATOR_H_*/
diff --git a/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp b/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp
index a8d8dc6148da074671a7c96ec24af801533bfd28..61e0c7df518931e1090a97afb35af30b3886032a 100644
--- a/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp
+++ b/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp
@@ -753,7 +753,11 @@ std::vector<MatrixWorkspace_sptr> QENSFitSequential::getWorkspaces() const {
   const auto inputString = getPropertyValue("Input");
   if (!inputString.empty())
     return extractWorkspaces(inputString);
-  return {getProperty("InputWorkspace")};
+  // The static_cast should not be necessary but it is required to avoid a
+  // "internal compiler error: segmentation fault" when compiling with gcc
+  // and std=c++1z
+  return std::vector<MatrixWorkspace_sptr>{
+      static_cast<MatrixWorkspace_sptr>(getProperty("InputWorkspace"))};
 }
 
 std::vector<MatrixWorkspace_sptr> QENSFitSequential::convertInputToElasticQ(
diff --git a/Framework/CurveFitting/src/Algorithms/VesuvioCalculateGammaBackground.cpp b/Framework/CurveFitting/src/Algorithms/VesuvioCalculateGammaBackground.cpp
index 6dbd0635a061169da72a68c1611190bff9fd573a..20f0bc4a238446cd0cedb6017579199242d99d4a 100644
--- a/Framework/CurveFitting/src/Algorithms/VesuvioCalculateGammaBackground.cpp
+++ b/Framework/CurveFitting/src/Algorithms/VesuvioCalculateGammaBackground.cpp
@@ -32,6 +32,7 @@ using namespace Kernel;
 using namespace CurveFitting;
 using namespace CurveFitting::Functions;
 using namespace std;
+using std::placeholders::_1;
 
 // Subscription
 DECLARE_ALGORITHM(VesuvioCalculateGammaBackground)
@@ -255,7 +256,7 @@ void VesuvioCalculateGammaBackground::calculateSpectrumFromDetector(
   // Correct for distance to the detector: 0.5/l2^2
   const double detDistCorr = 0.5 / detPar.l2 / detPar.l2;
   std::transform(ctdet.begin(), ctdet.end(), ctdet.begin(),
-                 std::bind2nd(std::multiplies<double>(), detDistCorr));
+                 std::bind(std::multiplies<double>(), _1, detDistCorr));
 }
 
 /**
@@ -302,7 +303,7 @@ void VesuvioCalculateGammaBackground::calculateBackgroundFromFoils(
   if (reversed) {
     // The reversed ones should be (C0 - C1)
     std::transform(ctfoil.begin(), ctfoil.end(), ctfoil.begin(),
-                   std::bind2nd(std::multiplies<double>(), -1.0));
+                   std::bind(std::multiplies<double>(), _1, -1.0));
   }
 }
 
@@ -398,7 +399,7 @@ std::vector<double> VesuvioCalculateGammaBackground::calculateTofSpectrum(
   // Assumes the input is in seconds, transform it temporarily
   auto &tseconds = m_backgroundWS->mutableX(wsIndex);
   std::transform(tseconds.begin(), tseconds.end(), tseconds.begin(),
-                 std::bind2nd(std::multiplies<double>(), 1e-6));
+                 std::bind(std::multiplies<double>(), _1, 1e-6));
 
   // retrieveInputs ensures we will get a composite function and that each
   // member is a ComptonProfile
@@ -429,7 +430,7 @@ std::vector<double> VesuvioCalculateGammaBackground::calculateTofSpectrum(
   }
   // Put X back microseconds
   std::transform(tseconds.begin(), tseconds.end(), tseconds.begin(),
-                 std::bind2nd(std::multiplies<double>(), 1e6));
+                 std::bind(std::multiplies<double>(), _1, 1e6));
   return correctedVals;
 }
 
diff --git a/Framework/CurveFitting/src/ExcludeRangeFinder.cpp b/Framework/CurveFitting/src/ExcludeRangeFinder.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a0d55892325dc80500e7566915886f2498b59336
--- /dev/null
+++ b/Framework/CurveFitting/src/ExcludeRangeFinder.cpp
@@ -0,0 +1,95 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+
+#include "MantidCurveFitting/ExcludeRangeFinder.h"
+
+#include <algorithm>
+
+namespace Mantid {
+namespace CurveFitting {
+
+/// Constructor.
+/// @param exclude :: The value of the "Exclude" property.
+/// @param startX :: The start of the overall fit interval.
+/// @param endX :: The end of the overall fit interval.
+ExcludeRangeFinder::ExcludeRangeFinder(const std::vector<double> &exclude,
+                                       double startX, double endX)
+    : m_exclIndex(exclude.size()), m_startExcludedRange(), m_endExcludeRange(),
+      m_exclude(exclude), m_size(exclude.size()) {
+  // m_exclIndex is initialised with exclude.size() to be the default when
+  // there are no exclusion ranges defined.
+  if (!m_exclude.empty()) {
+    if (startX < m_exclude.back() && endX > m_exclude.front()) {
+      // In this case there are some ranges, the index starts with 0
+      // and first range is found.
+      m_exclIndex = 0;
+      findNextExcludedRange(startX);
+    }
+  }
+}
+
+/// Check if an x-value lies in an exclusion range.
+/// @param value :: A value to check.
+/// @returns true if the value lies in an exclusion range and should be
+/// excluded from fit.
+bool ExcludeRangeFinder::isExcluded(double value) {
+  if (m_exclIndex < m_size) {
+    if (value < m_startExcludedRange) {
+      // If a value is below the start of the current interval
+      // it is not in any other interval by the workings of
+      // findNextExcludedRange
+      return false;
+    } else if (value <= m_endExcludeRange) {
+      // value is inside
+      return true;
+    } else {
+      // Value is past the current range. Find the next one or set the index
+      // to m_exclude.size() to stop further searches.
+      findNextExcludedRange(value);
+      // The value can find itself inside another range.
+      return isExcluded(value);
+    }
+  }
+  return false;
+}
+
+/// Find the range from m_exclude that may contain points x >= p .
+/// @param p :: An x value to use in the seach.
+void ExcludeRangeFinder::findNextExcludedRange(double p) {
+  if (p > m_exclude.back()) {
+    // If the value is past the last point stop any searches or checks.
+    m_exclIndex = m_size;
+    return;
+  }
+  // Starting with the current index m_exclIndex find the first value in
+  // m_exclude that is greater than p. If this point is a start than the
+  // end will be the following point. If it's an end then the start is
+  // the previous point. Keep index m_exclIndex pointing to the start.
+  for (auto it = m_exclude.begin() + m_exclIndex; it != m_exclude.end(); ++it) {
+    if (*it >= p) {
+      m_exclIndex = static_cast<size_t>(std::distance(m_exclude.begin(), it));
+      if (m_exclIndex % 2 == 0) {
+        // A number at an even position in m_exclude starts an exclude
+        // range
+        m_startExcludedRange = *it;
+        m_endExcludeRange = *(it + 1);
+      } else {
+        // A number at an odd position in m_exclude ends an exclude range
+        m_startExcludedRange = *(it - 1);
+        m_endExcludeRange = *it;
+        --m_exclIndex;
+      }
+      break;
+    }
+  }
+  // No need for additional checks as p < m_exclude.back()
+  // and m_exclude[m_exclIndex] < p due to conditions at the calls
+  // so the break statement will always be reached.
+}
+} // namespace CurveFitting
+
+} // namespace Mantid
diff --git a/Framework/CurveFitting/src/FitMW.cpp b/Framework/CurveFitting/src/FitMW.cpp
index 634c305f1d937c29ed7a97d92460921b4b6b9026..6a18ba146dd7a6751d9bbfd1a1e6f1cbbd85ea7e 100644
--- a/Framework/CurveFitting/src/FitMW.cpp
+++ b/Framework/CurveFitting/src/FitMW.cpp
@@ -7,6 +7,7 @@
 // Includes
 //----------------------------------------------------------------------
 #include "MantidCurveFitting/FitMW.h"
+#include "MantidCurveFitting/ExcludeRangeFinder.h"
 #include "MantidCurveFitting/Functions/Convolution.h"
 #include "MantidCurveFitting/ParameterEstimator.h"
 #include "MantidCurveFitting/SeqDomain.h"
@@ -40,108 +41,6 @@ using API::Workspace;
 
 namespace {
 
-/// Helper calss that finds if a point should be excluded from fit.
-/// It keeps the boundaries of the relevant exclusion region for
-/// the last checked value. A relevant region is the one which either
-/// includes the value or the nearest one with the left boundary greater
-/// than the value.
-/// The class also keeps the index of the region (its left boundary) for
-/// efficient search.
-class ExcludeRangeFinder {
-  /// Index of current excluded range
-  size_t m_exclIndex;
-  /// Start of current excluded range
-  double m_startExcludedRange;
-  /// End of current excluded range
-  double m_endExcludeRange;
-  /// Reference to a list of exclusion ranges.
-  const std::vector<double> &m_exclude;
-  /// Size of m_exclude.
-  const size_t m_size;
-
-public:
-  /// Constructor.
-  /// @param exclude :: The value of the "Exclude" property.
-  /// @param startX :: The start of the overall fit interval.
-  /// @param endX :: The end of the overall fit interval.
-  ExcludeRangeFinder(const std::vector<double> &exclude, double startX,
-                     double endX)
-      : m_exclIndex(exclude.size()), m_startExcludedRange(),
-        m_endExcludeRange(), m_exclude(exclude), m_size(exclude.size()) {
-    // m_exclIndex is initialised with exclude.size() to be the default when
-    // there are no exclusion ranges defined.
-    if (!m_exclude.empty()) {
-      if (startX < m_exclude.back() && endX > m_exclude.front()) {
-        // In this case there are some ranges, the index starts with 0
-        // and first range is found.
-        m_exclIndex = 0;
-        findNextExcludedRange(startX);
-      }
-    }
-  }
-
-  /// Check if an x-value lies in an exclusion range.
-  /// @param value :: A value to check.
-  /// @returns true if the value lies in an exclusion range and should be
-  /// excluded from fit.
-  bool isExcluded(double value) {
-    if (m_exclIndex < m_size) {
-      if (value < m_startExcludedRange) {
-        // If a value is below the start of the current interval
-        // it is not in any other interval by the workings of
-        // findNextExcludedRange
-        return false;
-      } else if (value <= m_endExcludeRange) {
-        // value is inside
-        return true;
-      } else {
-        // Value is past the current range. Find the next one or set the index
-        // to m_exclude.size() to stop further searches.
-        findNextExcludedRange(value);
-        // The value can find itself inside another range.
-        return isExcluded(value);
-      }
-    }
-    return false;
-  }
-
-private:
-  /// Find the range from m_exclude that may contain points x >= p .
-  /// @param p :: An x value to use in the seach.
-  void findNextExcludedRange(double p) {
-    if (p > m_exclude.back()) {
-      // If the value is past the last point stop any searches or checks.
-      m_exclIndex = m_size;
-      return;
-    }
-    // Starting with the current index m_exclIndex find the first value in
-    // m_exclude that is greater than p. If this point is a start than the
-    // end will be the following point. If it's an end then the start is
-    // the previous point. Keep index m_exclIndex pointing to the start.
-    for (auto it = m_exclude.begin() + m_exclIndex; it != m_exclude.end();
-         ++it) {
-      if (*it >= p) {
-        m_exclIndex = static_cast<size_t>(std::distance(m_exclude.begin(), it));
-        if (m_exclIndex % 2 == 0) {
-          // A number at an even position in m_exclude starts an exclude
-          // range
-          m_startExcludedRange = *it;
-          m_endExcludeRange = *(it + 1);
-        } else {
-          // A number at an odd position in m_exclude ends an exclude range
-          m_startExcludedRange = *(it - 1);
-          m_endExcludeRange = *it;
-          --m_exclIndex;
-        }
-        break;
-      }
-    }
-    // No need for additional checks as p < m_exclude.back()
-    // and m_exclude[m_exclIndex] < p due to conditions at the calls
-    // so the break statement will always be reached.
-  }
-};
-
 /// Helper struct for helping with joining exclusion ranges.
 /// Endge points of the ranges can be wrapped in this struct
 /// and sorted together without loosing their function.
diff --git a/Framework/CurveFitting/src/Functions/ChebfunBase.cpp b/Framework/CurveFitting/src/Functions/ChebfunBase.cpp
index 460e7d719ee5afe8a63ab81b225c1c894abf2984..808c69daf4fd37753ac218814da73cc681183821 100644
--- a/Framework/CurveFitting/src/Functions/ChebfunBase.cpp
+++ b/Framework/CurveFitting/src/Functions/ChebfunBase.cpp
@@ -316,9 +316,10 @@ void ChebfunBase::derivative(const std::vector<double> &a,
   } else {
     aout.front() = a[1];
   }
-  double d = (m_end - m_start) / 2;
-  std::transform(aout.begin(), aout.end(), aout.begin(),
-                 std::bind2nd(std::divides<double>(), d));
+  using std::placeholders::_1;
+  std::transform(
+      aout.begin(), aout.end(), aout.begin(),
+      std::bind(std::divides<double>(), _1, 0.5 * (m_end - m_start)));
 }
 
 /**
diff --git a/Framework/CurveFitting/src/Functions/ComptonPeakProfile.cpp b/Framework/CurveFitting/src/Functions/ComptonPeakProfile.cpp
index 4c2e6e27ef7896e8ace4269049cfb8bc6e36a0ae..c1b3c66c1631e18f6a4f5849a1e42f0e89370887 100644
--- a/Framework/CurveFitting/src/Functions/ComptonPeakProfile.cpp
+++ b/Framework/CurveFitting/src/Functions/ComptonPeakProfile.cpp
@@ -68,8 +68,9 @@ void ComptonPeakProfile::function1D(double *out, const double *xValues,
     m_voigt->setParameter(3, gaussFWHM);
     m_voigt->functionLocal(out, xValues, nData);
     const double norm = 1.0 / (0.5 * M_PI * lorentzFWHM);
+    using std::placeholders::_1;
     std::transform(out, out + nData, out,
-                   std::bind2nd(std::multiplies<double>(), norm));
+                   std::bind(std::multiplies<double>(), _1, norm));
   } else {
     double sigmaTotalSq =
         m_hwhmLorentz * m_hwhmLorentz + gaussSigma * gaussSigma;
diff --git a/Framework/CurveFitting/src/Functions/ComptonProfile.cpp b/Framework/CurveFitting/src/Functions/ComptonProfile.cpp
index 2337c466604e5b9355910aa7b518502f722f44b8..5f64c9685f9f896a2946e5f05cea16b9ab59b8c8 100644
--- a/Framework/CurveFitting/src/Functions/ComptonProfile.cpp
+++ b/Framework/CurveFitting/src/Functions/ComptonProfile.cpp
@@ -203,15 +203,16 @@ void ComptonProfile::voigtApproxDiff(std::vector<double> &voigtDiff,
 
   std::vector<double> ypmEps(yspace.size());
   // y+2eps
+  using std::placeholders::_1;
   std::transform(
       yspace.begin(), yspace.end(), ypmEps.begin(),
-      std::bind2nd(std::plus<double>(), 2.0 * epsilon)); // Add 2 epsilon
+      std::bind(std::plus<double>(), _1, 2.0 * epsilon)); // Add 2 epsilon
   m_resolutionFunction->voigtApprox(voigtDiff, ypmEps, lorentzPos, lorentzAmp,
                                     lorentzWidth, gaussWidth);
   // y-2eps
   std::transform(
       yspace.begin(), yspace.end(), ypmEps.begin(),
-      std::bind2nd(std::minus<double>(), 2.0 * epsilon)); // Subtract 2 epsilon
+      std::bind(std::minus<double>(), _1, 2.0 * epsilon)); // Subtract 2 epsilon
   std::vector<double> tmpResult(yspace.size());
   m_resolutionFunction->voigtApprox(tmpResult, ypmEps, lorentzPos, lorentzAmp,
                                     lorentzWidth, gaussWidth);
@@ -221,11 +222,11 @@ void ComptonProfile::voigtApproxDiff(std::vector<double> &voigtDiff,
 
   // y+eps
   std::transform(yspace.begin(), yspace.end(), ypmEps.begin(),
-                 std::bind2nd(std::plus<double>(), epsilon)); // Add epsilon
+                 std::bind(std::plus<double>(), _1, epsilon)); // Add epsilon
   m_resolutionFunction->voigtApprox(tmpResult, ypmEps, lorentzPos, lorentzAmp,
                                     lorentzWidth, gaussWidth);
   std::transform(tmpResult.begin(), tmpResult.end(), tmpResult.begin(),
-                 std::bind2nd(std::multiplies<double>(), 2.0)); // times 2
+                 std::bind(std::multiplies<double>(), _1, 2.0)); // times 2
   // Difference with 3rd term - result is put back in voigtDiff
   std::transform(voigtDiff.begin(), voigtDiff.end(), tmpResult.begin(),
                  voigtDiff.begin(), std::minus<double>());
@@ -233,20 +234,19 @@ void ComptonProfile::voigtApproxDiff(std::vector<double> &voigtDiff,
   // y-eps
   std::transform(
       yspace.begin(), yspace.end(), ypmEps.begin(),
-      std::bind2nd(std::minus<double>(), epsilon)); // Subtract epsilon
+      std::bind(std::minus<double>(), _1, epsilon)); // Subtract epsilon
   m_resolutionFunction->voigtApprox(tmpResult, ypmEps, lorentzPos, lorentzAmp,
                                     lorentzWidth, gaussWidth);
   std::transform(tmpResult.begin(), tmpResult.end(), tmpResult.begin(),
-                 std::bind2nd(std::multiplies<double>(), 2.0)); // times 2
+                 std::bind(std::multiplies<double>(), _1, 2.0)); // times 2
   // Sum final term
   std::transform(voigtDiff.begin(), voigtDiff.end(), tmpResult.begin(),
                  voigtDiff.begin(), std::plus<double>());
 
-  // Finally multiply by 2*eps^3
+  // Finally divide by 2*eps^3
   std::transform(
       voigtDiff.begin(), voigtDiff.end(), voigtDiff.begin(),
-      std::bind2nd(std::divides<double>(),
-                   2.0 * std::pow(epsilon, 3))); // divided by (2eps^3)
+      std::bind(std::divides<double>(), _1, 2.0 * std::pow(epsilon, 3)));
 }
 
 } // namespace Functions
diff --git a/Framework/CurveFitting/src/Functions/Convolution.cpp b/Framework/CurveFitting/src/Functions/Convolution.cpp
index 3b440e7062e03cc9e8a1fda383d0f8f929d2ae02..0831c2e8897d5209d716e2d07ba2564756905b86 100644
--- a/Framework/CurveFitting/src/Functions/Convolution.cpp
+++ b/Framework/CurveFitting/src/Functions/Convolution.cpp
@@ -34,10 +34,9 @@ namespace CurveFitting {
 namespace Functions {
 
 using namespace CurveFitting;
-
 using namespace Kernel;
-
 using namespace API;
+using std::placeholders::_1;
 
 DECLARE_FUNCTION(Convolution)
 
@@ -183,7 +182,7 @@ void Convolution::functionFFTMode(const FunctionDomain &domain,
                            workspace.workspace);
     std::transform(m_resolution.begin(), m_resolution.end(),
                    m_resolution.begin(),
-                   std::bind2nd(std::multiplies<double>(), dx));
+                   std::bind(std::multiplies<double>(), _1, dx));
   }
 
   // Now m_resolution contains fourier transform of the resolution
@@ -193,7 +192,7 @@ void Convolution::functionFFTMode(const FunctionDomain &domain,
     double dx = 1.; // nData > 1? xValues[1] - xValues[0]: 1.;
     std::transform(m_resolution.begin(), m_resolution.end(),
                    values.getPointerToCalculated(0),
-                   std::bind2nd(std::multiplies<double>(), dx));
+                   std::bind(std::multiplies<double>(), _1, dx));
     return;
   }
 
@@ -247,7 +246,7 @@ void Convolution::functionFFTMode(const FunctionDomain &domain,
     // integration variable
     double dx = nData > 1 ? xValues[1] - xValues[0] : 1.;
     std::transform(out, out + nData, out,
-                   std::bind2nd(std::multiplies<double>(), dx));
+                   std::bind(std::multiplies<double>(), _1, dx));
 
     // now out contains fourier transform of the model function
 
@@ -276,7 +275,7 @@ void Convolution::functionFFTMode(const FunctionDomain &domain,
     // integration variable
     dx = nData > 1 ? 1. / (xValues[1] - xValues[0]) : 1.;
     std::transform(out, out + nData, out,
-                   std::bind2nd(std::multiplies<double>(), dx));
+                   std::bind(std::multiplies<double>(), _1, dx));
   } else {
     values.zeroCalculated();
   }
@@ -287,7 +286,7 @@ void Convolution::functionFFTMode(const FunctionDomain &domain,
     std::vector<double> tmp(nData);
     resolution->function1D(tmp.data(), xValues, nData);
     std::transform(tmp.begin(), tmp.end(), tmp.begin(),
-                   std::bind2nd(std::multiplies<double>(), dltF));
+                   std::bind(std::multiplies<double>(), _1, dltF));
     std::transform(out, out + nData, tmp.data(), out, std::plus<double>());
   } else if (!dltFuns.empty()) {
     std::vector<double> x(nData);
@@ -295,11 +294,11 @@ void Convolution::functionFFTMode(const FunctionDomain &domain,
       double shift = -df->getParameter("Centre");
       dltF = df->getParameter("Height") * df->HeightPrefactor();
       std::transform(xValues, xValues + nData, x.data(),
-                     std::bind2nd(std::plus<double>(), shift));
+                     std::bind(std::plus<double>(), _1, shift));
       std::vector<double> tmp(nData);
       resolution->function1D(tmp.data(), x.data(), nData);
       std::transform(tmp.begin(), tmp.end(), tmp.begin(),
-                     std::bind2nd(std::multiplies<double>(), dltF));
+                     std::bind(std::multiplies<double>(), _1, dltF));
       std::transform(out, out + nData, tmp.data(), out, std::plus<double>());
     }
   }
@@ -417,7 +416,7 @@ void Convolution::functionDirectMode(const FunctionDomain &domain,
     std::vector<double> tmp(nData);
     resolution->function1D(tmp.data(), xValues, nData);
     std::transform(tmp.begin(), tmp.end(), tmp.begin(),
-                   std::bind2nd(std::multiplies<double>(), dltF));
+                   std::bind(std::multiplies<double>(), _1, dltF));
     std::transform(out, out + nData, tmp.data(), out, std::plus<double>());
   } else if (!dltFuns.empty()) {
     std::vector<double> x(nData);
@@ -425,11 +424,11 @@ void Convolution::functionDirectMode(const FunctionDomain &domain,
       double shift = -df->getParameter("Centre");
       dltF = df->getParameter("Height") * df->HeightPrefactor();
       std::transform(xValues, xValues + nData, x.data(),
-                     std::bind2nd(std::plus<double>(), shift));
+                     std::bind(std::plus<double>(), _1, shift));
       std::vector<double> tmp(nData);
       resolution->function1D(tmp.data(), x.data(), nData);
       std::transform(tmp.begin(), tmp.end(), tmp.begin(),
-                     std::bind2nd(std::multiplies<double>(), dltF));
+                     std::bind(std::multiplies<double>(), _1, dltF));
       std::transform(out, out + nData, tmp.data(), out, std::plus<double>());
     }
   }
diff --git a/Framework/CurveFitting/src/Functions/GramCharlierComptonProfile.cpp b/Framework/CurveFitting/src/Functions/GramCharlierComptonProfile.cpp
index af3294cd0db0b7a3ebbcf4ebfe830ebc36bde906..6d5cd021a0e0ae63b1a35055ae09183df7fc61ba 100644
--- a/Framework/CurveFitting/src/Functions/GramCharlierComptonProfile.cpp
+++ b/Framework/CurveFitting/src/Functions/GramCharlierComptonProfile.cpp
@@ -428,8 +428,9 @@ void GramCharlierComptonProfile::cacheYSpaceValues(
 
   // Cache voigt function over yfine
   std::vector<double> minusYFine(NFINE_Y);
+  using std::placeholders::_1;
   std::transform(m_yfine.begin(), m_yfine.end(), minusYFine.begin(),
-                 std::bind2nd(std::multiplies<double>(), -1.0));
+                 std::bind(std::multiplies<double>(), _1, -1.0));
   std::vector<double> ym(
       NFINE_Y); // Holds result of (y[i] - yfine) for each original y
   m_voigt.resize(ncoarseY);
@@ -441,7 +442,7 @@ void GramCharlierComptonProfile::cacheYSpaceValues(
     const double yi = yspace[i];
     std::transform(
         minusYFine.begin(), minusYFine.end(), ym.begin(),
-        std::bind2nd(std::plus<double>(), yi)); // yfine is actually -yfine
+        std::bind(std::plus<double>(), _1, yi)); // yfine is actually -yfine
     m_resolutionFunction->voigtApprox(voigt, ym, 0, 1.0);
   }
 
diff --git a/Framework/CurveFitting/src/Functions/VesuvioResolution.cpp b/Framework/CurveFitting/src/Functions/VesuvioResolution.cpp
index bdc065ef2098f8753ce40f03be2fe12d258d8e2d..e58687711f6cf3a2ed4173f66abd9d8ca39e26fc 100644
--- a/Framework/CurveFitting/src/Functions/VesuvioResolution.cpp
+++ b/Framework/CurveFitting/src/Functions/VesuvioResolution.cpp
@@ -247,8 +247,9 @@ void VesuvioResolution::voigtApprox(std::vector<double> &voigt,
 
   // Normalize so that integral of V=lorentzAmp
   const double norm = 1.0 / (0.5 * M_PI * lorentzWidth);
+  using std::placeholders::_1;
   std::transform(voigt.begin(), voigt.end(), voigt.begin(),
-                 std::bind2nd(std::multiplies<double>(), norm));
+                 std::bind(std::multiplies<double>(), _1, norm));
 }
 
 } // namespace Functions
diff --git a/Framework/CurveFitting/src/IFittingAlgorithm.cpp b/Framework/CurveFitting/src/IFittingAlgorithm.cpp
index 04797b9c9b24e30b9265df0eb990e44f615195ba..de2426777a78cca666575f6e25ac79814f97fc57 100644
--- a/Framework/CurveFitting/src/IFittingAlgorithm.cpp
+++ b/Framework/CurveFitting/src/IFittingAlgorithm.cpp
@@ -13,6 +13,7 @@
 #include "MantidCurveFitting/LatticeDomainCreator.h"
 #include "MantidCurveFitting/MultiDomainCreator.h"
 #include "MantidCurveFitting/SeqDomainSpectrumCreator.h"
+#include "MantidCurveFitting/TableWorkspaceDomainCreator.h"
 
 #include "MantidAPI/CostFunctionFactory.h"
 #include "MantidAPI/FunctionProperty.h"
@@ -39,6 +40,13 @@ IDomainCreator *createDomainCreator(const IFunction *fun,
                                     IDomainCreator::DomainType domainType) {
 
   IDomainCreator *creator = nullptr;
+  Workspace_sptr ws;
+
+  try {
+    ws = manager->getProperty("InputWorkspace");
+  } catch (...) {
+    // InputWorkspace is not needed for some fit function so continue
+  }
 
   // ILatticeFunction requires API::LatticeDomain.
   if (dynamic_cast<const ILatticeFunction *>(fun)) {
@@ -50,6 +58,9 @@ IDomainCreator *createDomainCreator(const IFunction *fun,
     creator = new SeqDomainSpectrumCreator(manager, workspacePropertyName);
   } else if (auto gfun = dynamic_cast<const IFunctionGeneral *>(fun)) {
     creator = new GeneralDomainCreator(*gfun, *manager, workspacePropertyName);
+  } else if (boost::dynamic_pointer_cast<ITableWorkspace>(ws)) {
+    creator = new TableWorkspaceDomainCreator(manager, workspacePropertyName,
+                                              domainType);
   } else {
     bool histogramFit =
         manager->getPropertyValue("EvaluationType") == "Histogram";
diff --git a/Framework/CurveFitting/src/IMWDomainCreator.cpp b/Framework/CurveFitting/src/IMWDomainCreator.cpp
index c5a9952295f8141685a6effab6c0c1244535c787..5af7ac1f54c4290ff0730cd2f55214dd338c833c 100644
--- a/Framework/CurveFitting/src/IMWDomainCreator.cpp
+++ b/Framework/CurveFitting/src/IMWDomainCreator.cpp
@@ -8,6 +8,7 @@
 //----------------------------------------------------------------------
 #include "MantidCurveFitting/IMWDomainCreator.h"
 #include "MantidCurveFitting/Functions/Convolution.h"
+#include "MantidCurveFitting/Jacobian.h"
 #include "MantidCurveFitting/ParameterEstimator.h"
 #include "MantidCurveFitting/SeqDomain.h"
 
@@ -32,30 +33,6 @@ namespace Mantid {
 namespace CurveFitting {
 
 namespace {
-/**
- * A simple implementation of Jacobian.
- */
-class SimpleJacobian : public API::Jacobian {
-public:
-  /// Constructor
-  SimpleJacobian(size_t nData, size_t nParams)
-      : m_nParams(nParams), m_data(nData * nParams) {}
-  /// Setter
-  void set(size_t iY, size_t iP, double value) override {
-    m_data[iY * m_nParams + iP] = value;
-  }
-  /// Getter
-  double get(size_t iY, size_t iP) override {
-    return m_data[iY * m_nParams + iP];
-  }
-  /// Zero
-  void zero() override { m_data.assign(m_data.size(), 0.0); }
-
-private:
-  size_t m_nParams;           ///< number of parameters / second dimension
-  std::vector<double> m_data; ///< data storage
-};
-
 bool greaterIsLess(double x1, double x2) { return x1 > x2; }
 } // namespace
 
@@ -455,7 +432,7 @@ void IMWDomainCreator::addFunctionValuesToWS(
 
   if (covar || hasErrors) {
     // and errors
-    SimpleJacobian J(nData, nParams);
+    Jacobian J(nData, nParams);
     try {
       function->functionDeriv(*domain, J);
     } catch (...) {
diff --git a/Framework/CurveFitting/src/TableWorkspaceDomainCreator.cpp b/Framework/CurveFitting/src/TableWorkspaceDomainCreator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8b02dcab15bfaecb68019f5459d85579e57cabca
--- /dev/null
+++ b/Framework/CurveFitting/src/TableWorkspaceDomainCreator.cpp
@@ -0,0 +1,792 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+// Includes
+//----------------------------------------------------------------------
+
+#include "MantidCurveFitting/TableWorkspaceDomainCreator.h"
+
+#include "MantidAPI/FunctionDomain1D.h"
+#include "MantidAPI/ITableWorkspace_fwd.h"
+#include "MantidAPI/MatrixWorkspace.h"
+#include "MantidAPI/TableRow.h"
+#include "MantidAPI/TextAxis.h"
+#include "MantidAPI/WorkspaceFactory.h"
+#include "MantidAPI/WorkspaceProperty.h"
+
+#include "MantidCurveFitting/ExcludeRangeFinder.h"
+#include "MantidCurveFitting/Functions/Convolution.h"
+#include "MantidCurveFitting/Jacobian.h"
+#include "MantidCurveFitting/ParameterEstimator.h"
+#include "MantidCurveFitting/SeqDomain.h"
+
+#include "MantidDataObjects/TableColumn.h"
+
+#include "MantidKernel/ArrayOrderedPairsValidator.h"
+#include "MantidKernel/ArrayProperty.h"
+#include "MantidKernel/BoundedValidator.h"
+#include "MantidKernel/EmptyValues.h"
+
+namespace Mantid {
+namespace CurveFitting {
+
+namespace {
+
+/// Helper struct for helping with joining exclusion ranges.
+/// Edge points of the ranges can be wrapped in this struct
+/// and sorted together without loosing their function.
+struct RangePoint {
+  enum Kind : char { Opening, Closing };
+  /// The kind of the point: either opening or closing the range.
+  Kind kind;
+  /// The value of the point.
+  double value;
+  /// Comparison of two points.
+  /// @param point :: Another point to compare with.
+  bool operator<(const RangePoint &point) const {
+    if (this->value == point.value) {
+      // If an Opening and Closing points have the same value
+      // the Opening one should go first (be "smaller").
+      // This way the procedure of joinOverlappingRanges will join
+      // the ranges that meet at these points.
+      return this->kind == Opening;
+    }
+    return this->value < point.value;
+  }
+};
+
+/// Find any overlapping ranges in a vector and join them.
+/// @param[in,out] exclude :: A vector with the ranges some of which may
+/// overlap. On output all overlapping ranges are joined and the vector contains
+/// increasing series of doubles (an even number of them).
+void joinOverlappingRanges(std::vector<double> &exclude) {
+  if (exclude.empty()) {
+    return;
+  }
+  // The situation here is similar to matching brackets in an expression.
+  // If we sort all the points in the input vector remembering their kind
+  // then a separate exclusion region starts with the first openning bracket
+  // and ends with the matching closing bracket. All brackets (points) inside
+  // them can be dropped.
+
+  // Wrap the points into helper struct RangePoint
+  std::vector<RangePoint> points;
+  points.reserve(exclude.size());
+  for (auto point = exclude.begin(); point != exclude.end(); point += 2) {
+    points.push_back(RangePoint{RangePoint::Opening, *point});
+    points.push_back(RangePoint{RangePoint::Closing, *(point + 1)});
+  }
+  // Sort the points according to the operator defined in RangePoint.
+  std::sort(points.begin(), points.end());
+
+  // Clear the argument vector.
+  exclude.clear();
+  // Start the level counter which shows the number of unmatched openning
+  // brackets.
+  size_t level(0);
+  for (auto &point : points) {
+    if (point.kind == RangePoint::Opening) {
+      if (level == 0) {
+        // First openning bracket starts a new exclusion range.
+        exclude.push_back(point.value);
+      }
+      // Each openning bracket increases the level
+      ++level;
+    } else {
+      if (level == 1) {
+        // The bracket that makes level 0 is an end of a range.
+        exclude.push_back(point.value);
+      }
+      // Each closing bracket decreases the level
+      --level;
+    }
+  }
+}
+
+bool greaterIsLess(double x1, double x2) { return x1 > x2; }
+} // namespace
+
+using namespace Kernel;
+
+/**
+ * Constructor.
+ * @param fit :: Property manager with properties defining the domain to be
+ * created
+ * @param workspacePropertyName :: Name of the workspace property.
+ * @param domainType :: Type of the domain: Simple, Sequential, or Parallel.
+ */
+TableWorkspaceDomainCreator::TableWorkspaceDomainCreator(
+    Kernel::IPropertyManager *fit, const std::string &workspacePropertyName,
+    TableWorkspaceDomainCreator::DomainType domainType)
+    : IDomainCreator(fit, std::vector<std::string>(1, workspacePropertyName),
+                     domainType),
+      m_startX(EMPTY_DBL()), m_endX(EMPTY_DBL()), m_maxSize(0) {
+  if (m_workspacePropertyNames.empty()) {
+    throw std::runtime_error("Cannot create FitMW: no workspace given");
+  }
+  m_workspacePropertyName = m_workspacePropertyNames[0];
+}
+
+/**
+ * Constructor. Methods setWorkspace and setRange must becalled
+ * set up the creator.
+ * @param domainType :: Type of the domain: Simple, Sequential, or Parallel.
+ */
+TableWorkspaceDomainCreator::TableWorkspaceDomainCreator(
+    TableWorkspaceDomainCreator::DomainType domainType)
+    : IDomainCreator(nullptr, std::vector<std::string>(), domainType),
+      m_startX(EMPTY_DBL()), m_endX(EMPTY_DBL()), m_maxSize(10) {}
+
+/**
+ * Declare properties that specify the dataset within the workspace to fit to.
+ * @param suffix :: names the dataset
+ * @param addProp :: allows for the declaration of certain properties of the
+ * dataset
+ */
+void TableWorkspaceDomainCreator::declareDatasetProperties(
+    const std::string &suffix, bool addProp) {
+
+  m_startXPropertyName = "StartX" + suffix;
+  m_endXPropertyName = "EndX" + suffix;
+  m_maxSizePropertyName = "MaxSize" + suffix;
+  m_excludePropertyName = "Exclude" + suffix;
+  m_xColumnPropertyName = "XColumn" + suffix;
+  m_yColumnPropertyName = "YColumn" + suffix;
+  m_errorColumnPropertyName = "ErrColumn" + suffix;
+
+  if (addProp) {
+    auto mustBePositive = boost::make_shared<BoundedValidator<int>>();
+    mustBePositive->setLower(0);
+    declareProperty(
+        new PropertyWithValue<double>(m_startXPropertyName, EMPTY_DBL()),
+        "A value of x in, or on the low x boundary of, the first bin to "
+        "include in\n"
+        "the fit (default lowest value of x)");
+    declareProperty(
+        new PropertyWithValue<double>(m_endXPropertyName, EMPTY_DBL()),
+        "A value in, or on the high x boundary of, the last bin the fitting "
+        "range\n"
+        "(default the highest value of x)");
+    if (m_domainType != Simple &&
+        !m_manager->existsProperty(m_maxSizePropertyName)) {
+      auto mustBePositive = boost::make_shared<BoundedValidator<int>>();
+      mustBePositive->setLower(0);
+      declareProperty(
+          new PropertyWithValue<int>(m_maxSizePropertyName, 1, mustBePositive),
+          "The maximum number of values per a simple domain.");
+    }
+    if (!m_manager->existsProperty(m_excludePropertyName)) {
+      auto mustBeOrderedPairs =
+          boost::make_shared<ArrayOrderedPairsValidator<double>>();
+      declareProperty(
+          new ArrayProperty<double>(m_excludePropertyName, mustBeOrderedPairs),
+          "A list of pairs of doubles that specify ranges that must be "
+          "excluded from fit.");
+    }
+    declareProperty(
+        new PropertyWithValue<std::string>(m_xColumnPropertyName, ""),
+        "The name of the X column.");
+    declareProperty(
+        new PropertyWithValue<std::string>(m_yColumnPropertyName, ""),
+        "The name of the Y column.");
+    declareProperty(
+        new PropertyWithValue<std::string>(m_errorColumnPropertyName, ""),
+        "The name of the error column.");
+  }
+}
+
+/// Create a domain from the input workspace
+void TableWorkspaceDomainCreator::createDomain(
+    boost::shared_ptr<API::FunctionDomain> &domain,
+    boost::shared_ptr<API::FunctionValues> &values, size_t i0) {
+
+  setParameters();
+
+  auto rowCount = m_tableWorkspace->rowCount();
+  if (rowCount == 0) {
+    throw std::runtime_error("Workspace contains no data.");
+  }
+
+  auto X = static_cast<DataObjects::TableColumn<double> &>(
+      *m_tableWorkspace->getColumn(m_xColName));
+  auto XData = X.data();
+
+  // find the fitting interval: from -> to
+  size_t endRowNo = 0;
+  std::tie(m_startRowNo, endRowNo) = getXInterval();
+  auto from = XData.begin() + m_startRowNo;
+  auto to = XData.begin() + endRowNo;
+  auto n = endRowNo - m_startRowNo;
+
+  if (m_domainType != Simple) {
+    if (m_maxSize < n) {
+      SeqDomain *seqDomain = SeqDomain::create(m_domainType);
+      domain.reset(seqDomain);
+      size_t m = 0;
+      while (m < n) {
+        // create a simple creator
+        auto creator = new TableWorkspaceDomainCreator;
+        creator->setWorkspace(m_tableWorkspace);
+        creator->setColumnNames(m_xColName, m_yColName, m_errColName);
+        size_t k = m + m_maxSize;
+        if (k > n)
+          k = n;
+        creator->setRange(*(from + m), *(from + k - 1));
+        seqDomain->addCreator(API::IDomainCreator_sptr(creator));
+        m = k;
+      }
+      values.reset();
+      return;
+    }
+    // else continue with simple domain
+  }
+
+  // set function domain
+  domain.reset(new API::FunctionDomain1DVector(from, to));
+
+  if (!values) {
+    values.reset(new API::FunctionValues(*domain));
+  } else {
+    values->expand(i0 + domain->size());
+  }
+
+  // set the data to fit to
+  assert(n == domain->size());
+  auto Y = m_tableWorkspace->getColumn(m_yColName);
+  if (endRowNo > Y->size()) {
+    throw std::runtime_error(
+        "TableWorkspaceDomainCreator: Inconsistent TableWorkspace");
+  }
+
+  // Helps find points excluded form fit.
+  ExcludeRangeFinder excludeFinder(m_exclude, XData.front(), XData.back());
+
+  auto errors = m_tableWorkspace->getColumn(m_errColName);
+  for (size_t i = m_startRowNo; i < endRowNo; ++i) {
+    size_t j = i - m_startRowNo + i0;
+    auto y = Y->cell<double>(i);
+    auto error = errors->cell<double>(i);
+    double weight = 0.0;
+
+    if (excludeFinder.isExcluded(X[i])) {
+      weight = 0.0;
+    } else if (!std::isfinite(y)) {
+      // nan or inf data
+      if (!m_ignoreInvalidData)
+        throw std::runtime_error("Infinte number or NaN found in input data.");
+      y = 0.0; // leaving inf or nan would break the fit
+    } else if (!std::isfinite(error)) {
+      // nan or inf error
+      if (!m_ignoreInvalidData)
+        throw std::runtime_error("Infinte number or NaN found in input data.");
+    } else if (error <= 0) {
+      if (!m_ignoreInvalidData)
+        weight = 1.0;
+    } else {
+      weight = 1.0 / error;
+      if (!std::isfinite(weight)) {
+        if (!m_ignoreInvalidData)
+          throw std::runtime_error(
+              "Error of a data point is probably too small.");
+        weight = 0.0;
+      }
+    }
+
+    values->setFitData(j, y);
+    values->setFitWeight(j, weight);
+  }
+  m_domain = boost::dynamic_pointer_cast<API::FunctionDomain1D>(domain);
+  m_values = values;
+}
+
+/**
+ * Create an output workspace with the calculated values.
+ * @param baseName :: Specifies the name of the output workspace
+ * @param function :: A Pointer to the fitting function
+ * @param domain :: The domain containing x-values for the function
+ * @param values :: A API::FunctionValues instance containing the fitting data
+ * @param outputWorkspacePropertyName :: The property name
+ */
+boost::shared_ptr<API::Workspace>
+TableWorkspaceDomainCreator::createOutputWorkspace(
+    const std::string &baseName, API::IFunction_sptr function,
+    boost::shared_ptr<API::FunctionDomain> domain,
+    boost::shared_ptr<API::FunctionValues> values,
+    const std::string &outputWorkspacePropertyName) {
+
+  if (!values) {
+    throw std::logic_error("FunctionValues expected");
+  }
+
+  // Compile list of functions to output. The top-level one is first
+  std::list<API::IFunction_sptr> functionsToDisplay(1, function);
+  if (m_outputCompositeMembers) {
+    appendCompositeFunctionMembers(functionsToDisplay, function);
+  }
+
+  // Nhist = Data histogram, Difference Histogram + nfunctions
+  const size_t nhistograms = functionsToDisplay.size() + 2;
+  const size_t nyvalues = values->size();
+  auto ws = createEmptyResultWS(nhistograms, nyvalues);
+  // The workspace was constructed with a TextAxis
+  API::TextAxis *textAxis = static_cast<API::TextAxis *>(ws->getAxis(1));
+  textAxis->setLabel(0, "Data");
+  textAxis->setLabel(1, "Calc");
+  textAxis->setLabel(2, "Diff");
+
+  // Add each calculated function
+  auto iend = functionsToDisplay.end();
+  size_t wsIndex(1); // Zero reserved for data
+  for (auto it = functionsToDisplay.begin(); it != iend; ++it) {
+    if (wsIndex > 2)
+      textAxis->setLabel(wsIndex, (*it)->name());
+    addFunctionValuesToWS(*it, ws, wsIndex, domain, values);
+    if (it == functionsToDisplay.begin())
+      wsIndex += 2; // Skip difference histogram for now
+    else
+      ++wsIndex;
+  }
+
+  // Set the difference spectrum
+  auto &Ycal = ws->mutableY(1);
+  auto &Diff = ws->mutableY(2);
+  const size_t nData = values->size();
+  for (size_t i = 0; i < nData; ++i) {
+    if (values->getFitWeight(i) != 0.0) {
+      Diff[i] = values->getFitData(i) - Ycal[i];
+    } else {
+      Diff[i] = 0.0;
+    }
+  }
+
+  if (!outputWorkspacePropertyName.empty()) {
+    declareProperty(
+        new API::WorkspaceProperty<API::MatrixWorkspace>(
+            outputWorkspacePropertyName, "", Direction::Output),
+        "Name of the output Workspace holding resulting simulated spectrum");
+    m_manager->setPropertyValue(outputWorkspacePropertyName,
+                                baseName + "Workspace");
+    m_manager->setProperty(outputWorkspacePropertyName, ws);
+  }
+
+  return ws;
+}
+
+/**
+ * @param functionList The current list of functions to append to
+ * @param function A function that may or not be composite
+ */
+void TableWorkspaceDomainCreator::appendCompositeFunctionMembers(
+    std::list<API::IFunction_sptr> &functionList,
+    const API::IFunction_sptr &function) const {
+  // if function is a Convolution then output of convolved model's members may
+  // be required
+  if (m_convolutionCompositeMembers &&
+      boost::dynamic_pointer_cast<Functions::Convolution>(function)) {
+    appendConvolvedCompositeFunctionMembers(functionList, function);
+  } else {
+    const auto compositeFn =
+        boost::dynamic_pointer_cast<API::CompositeFunction>(function);
+    if (!compositeFn)
+      return;
+
+    const size_t nlocals = compositeFn->nFunctions();
+    for (size_t i = 0; i < nlocals; ++i) {
+      auto localFunction = compositeFn->getFunction(i);
+      auto localComposite =
+          boost::dynamic_pointer_cast<API::CompositeFunction>(localFunction);
+      if (localComposite)
+        appendCompositeFunctionMembers(functionList, localComposite);
+      else
+        functionList.insert(functionList.end(), localFunction);
+    }
+  }
+}
+
+/**
+ * If the fit function is Convolution and flag m_convolutionCompositeMembers is
+ * set and Convolution's
+ * second function (the model) is composite then use members of the model for
+ * the output.
+ * @param functionList :: A list of Convolutions constructed from the
+ * resolution of the fitting function (index 0)
+ *   and members of the model.
+ * @param function A Convolution function which model may or may not be a
+ * composite function.
+ * @return True if all conditions are fulfilled and it is possible to produce
+ * the output.
+ */
+void TableWorkspaceDomainCreator::appendConvolvedCompositeFunctionMembers(
+    std::list<API::IFunction_sptr> &functionList,
+    const API::IFunction_sptr &function) const {
+  boost::shared_ptr<Functions::Convolution> convolution =
+      boost::dynamic_pointer_cast<Functions::Convolution>(function);
+
+  const auto compositeFn = boost::dynamic_pointer_cast<API::CompositeFunction>(
+      convolution->getFunction(1));
+  if (!compositeFn) {
+    functionList.insert(functionList.end(), convolution);
+  } else {
+    auto resolution = convolution->getFunction(0);
+    const size_t nlocals = compositeFn->nFunctions();
+    for (size_t i = 0; i < nlocals; ++i) {
+      auto localFunction = compositeFn->getFunction(i);
+      boost::shared_ptr<Functions::Convolution> localConvolution =
+          boost::make_shared<Functions::Convolution>();
+      localConvolution->addFunction(resolution);
+      localConvolution->addFunction(localFunction);
+      functionList.insert(functionList.end(), localConvolution);
+    }
+  }
+}
+
+/**
+ * Add the calculated function values to the workspace. Estimate an error for
+ * each calculated value.
+ * @param function The function to evaluate
+ * @param ws A workspace to fill
+ * @param wsIndex The index to store the values
+ * @param domain The domain to calculate the values over
+ * @param resultValues A presized values holder for the results
+ */
+void TableWorkspaceDomainCreator::addFunctionValuesToWS(
+    const API::IFunction_sptr &function,
+    boost::shared_ptr<API::MatrixWorkspace> &ws, const size_t wsIndex,
+    const boost::shared_ptr<API::FunctionDomain> &domain,
+    boost::shared_ptr<API::FunctionValues> resultValues) const {
+  const size_t nData = resultValues->size();
+  resultValues->zeroCalculated();
+
+  // Function value
+  function->function(*domain, *resultValues);
+
+  size_t nParams = function->nParams();
+
+  // the function should contain the parameter's covariance matrix
+  auto covar = function->getCovarianceMatrix();
+  bool hasErrors = false;
+  if (!covar) {
+    for (size_t j = 0; j < nParams; ++j) {
+      if (function->getError(j) != 0.0) {
+        hasErrors = true;
+        break;
+      }
+    }
+  }
+
+  if (covar || hasErrors) {
+    // and errors
+    Jacobian J(nData, nParams);
+    try {
+      function->functionDeriv(*domain, J);
+    } catch (...) {
+      function->calNumericalDeriv(*domain, J);
+    }
+    if (covar) {
+      // if the function has a covariance matrix attached - use it for the
+      // errors
+      const Kernel::Matrix<double> &C = *covar;
+      // The formula is E = J * C * J^T
+      // We don't do full 3-matrix multiplication because we only need the
+      // diagonals of E
+      std::vector<double> E(nData);
+      for (size_t k = 0; k < nData; ++k) {
+        double s = 0.0;
+        for (size_t i = 0; i < nParams; ++i) {
+          double tmp = J.get(k, i);
+          s += C[i][i] * tmp * tmp;
+          for (size_t j = i + 1; j < nParams; ++j) {
+            s += J.get(k, i) * C[i][j] * J.get(k, j) * 2;
+          }
+        }
+        E[k] = s;
+      }
+
+      double chi2 = function->getChiSquared();
+      auto &yValues = ws->mutableY(wsIndex);
+      auto &eValues = ws->mutableE(wsIndex);
+      for (size_t i = 0; i < nData; i++) {
+        yValues[i] = resultValues->getCalculated(i);
+        eValues[i] = std::sqrt(E[i] * chi2);
+      }
+
+    } else {
+      // otherwise use the parameter errors which is OK for uncorrelated
+      // parameters
+      auto &yValues = ws->mutableY(wsIndex);
+      auto &eValues = ws->mutableE(wsIndex);
+      for (size_t i = 0; i < nData; i++) {
+        yValues[i] = resultValues->getCalculated(i);
+        double err = 0.0;
+        for (size_t j = 0; j < nParams; ++j) {
+          double d = J.get(i, j) * function->getError(j);
+          err += d * d;
+        }
+        eValues[i] = std::sqrt(err);
+      }
+    }
+  } else {
+    // No errors
+    auto &yValues = ws->mutableY(wsIndex);
+    for (size_t i = 0; i < nData; i++) {
+      yValues[i] = resultValues->getCalculated(i);
+    }
+  }
+}
+
+/**
+ * Creates a workspace to hold the results. If the input workspace contains
+ * histogram data then so will this
+ * It assigns the X and input data values but no Y,E data for any functions
+ * @param nhistograms The number of histograms
+ * @param nyvalues The number of y values to hold
+ */
+API::MatrixWorkspace_sptr
+TableWorkspaceDomainCreator::createEmptyResultWS(const size_t nhistograms,
+                                                 const size_t nyvalues) {
+  size_t nxvalues(nyvalues);
+  API::MatrixWorkspace_sptr ws = API::WorkspaceFactory::Instance().create(
+      "Workspace2D", nhistograms, nxvalues, nyvalues);
+  ws->setTitle(m_tableWorkspace->getTitle());
+  auto tAxis = std::make_unique<API::TextAxis>(nhistograms);
+  ws->replaceAxis(1, std::move(tAxis));
+
+  auto &inputX = static_cast<DataObjects::TableColumn<double> &>(
+      *m_tableWorkspace->getColumn(m_xColName));
+  auto &inputY = static_cast<DataObjects::TableColumn<double> &>(
+      *m_tableWorkspace->getColumn(m_yColName));
+  auto &inputE = static_cast<DataObjects::TableColumn<double> &>(
+      *m_tableWorkspace->getColumn(m_errColName));
+  // X values for all
+  for (size_t i = 0; i < nhistograms; i++) {
+    ws->mutableX(i).assign(inputX.data().begin() + m_startRowNo,
+                           inputX.data().begin() + m_startRowNo + nxvalues);
+  }
+  // Data values for the first histogram
+  ws->mutableY(0).assign(inputY.data().begin() + m_startRowNo,
+                         inputY.data().begin() + m_startRowNo + nyvalues);
+  ws->mutableE(0).assign(inputE.data().begin() + m_startRowNo,
+                         inputE.data().begin() + m_startRowNo + nyvalues);
+
+  return ws;
+}
+
+/**
+ * Return the size of the domain to be created.
+ */
+size_t TableWorkspaceDomainCreator::getDomainSize() const {
+  setParameters();
+  size_t startIndex, endIndex;
+  std::tie(startIndex, endIndex) = getXInterval();
+  return endIndex - startIndex;
+}
+
+/**
+ * Initialize the function with the workspace.
+ * @param function :: Function to initialize.
+ */
+void TableWorkspaceDomainCreator::initFunction(API::IFunction_sptr function) {
+  setParameters();
+  if (!function) {
+    throw std::runtime_error("Cannot initialize empty function.");
+  }
+  function->setWorkspace(m_tableWorkspace);
+  setInitialValues(*function);
+}
+
+/**
+ * Set initial values for parameters with default values.
+ * @param function : A function to set parameters for.
+ */
+void TableWorkspaceDomainCreator::setInitialValues(API::IFunction &function) {
+  auto domain = m_domain.lock();
+  auto values = m_values.lock();
+  if (domain && values) {
+    ParameterEstimator::estimate(function, *domain, *values);
+  }
+}
+
+/**
+ * Calculate size and starting iterator in the X array
+ * @returns :: A pair of start iterator and size of the data.
+ */
+std::pair<size_t, size_t> TableWorkspaceDomainCreator::getXInterval() const {
+  auto X = static_cast<DataObjects::TableColumn<double> &>(
+      *m_tableWorkspace->getColumn(m_xColName));
+  auto XData = X.data();
+  const auto sizeOfData = XData.size();
+  if (sizeOfData == 0) {
+    throw std::runtime_error("Workspace contains no data.");
+  }
+
+  setParameters();
+
+  // From points to the first occurrence of StartX in the workspace interval.
+  // End points to the last occurrence of EndX in the workspace interval.
+  // Find the fitting interval: from -> to
+  Mantid::MantidVec::iterator from;
+  Mantid::MantidVec::iterator to;
+
+  bool isXAscending = XData.front() < XData.back();
+
+  if (m_startX == EMPTY_DBL() && m_endX == EMPTY_DBL()) {
+    m_startX = XData.front();
+    from = XData.begin();
+    m_endX = XData.back();
+    to = XData.end();
+  } else if (m_startX == EMPTY_DBL() || m_endX == EMPTY_DBL()) {
+    throw std::invalid_argument(
+        "Both StartX and EndX must be given to set fitting interval.");
+  } else if (isXAscending) {
+    if (m_startX > m_endX) {
+      std::swap(m_startX, m_endX);
+    }
+    from = std::lower_bound(XData.begin(), XData.end(), m_startX);
+    to = std::upper_bound(from, XData.end(), m_endX);
+  } else { // x is descending
+    if (m_startX < m_endX) {
+      std::swap(m_startX, m_endX);
+    }
+    from =
+        std::lower_bound(XData.begin(), XData.end(), m_startX, greaterIsLess);
+    to = std::upper_bound(from, XData.end(), m_endX, greaterIsLess);
+  }
+
+  // Check whether the fitting interval defined by StartX and EndX is 0.
+  // This occurs when StartX and EndX are both less than the minimum workspace
+  // x-value or greater than the maximum workspace x-value.
+  if (to - from == 0) {
+    throw std::invalid_argument("StartX and EndX values do not capture a range "
+                                "within the workspace interval.");
+  }
+
+  return std::make_pair(std::distance(XData.begin(), from),
+                        std::distance(XData.begin(), to));
+}
+
+/**
+ * Set all parameters.
+ * @throws std::runtime_error if the Exclude property has an odd number of
+ * entries.
+ */
+void TableWorkspaceDomainCreator::setParameters() const {
+  // if property manager is set overwrite any set parameters
+  if (m_manager) {
+    // get the workspace
+    API::Workspace_sptr ws = m_manager->getProperty(m_workspacePropertyName);
+    setAndValidateWorkspace(ws);
+    if (m_domainType != Simple) {
+      const int maxSizeInt = m_manager->getProperty(m_maxSizePropertyName);
+      m_maxSize = static_cast<size_t>(maxSizeInt);
+    }
+    m_exclude = m_manager->getProperty(m_excludePropertyName);
+    if (m_exclude.size() % 2 != 0) {
+      throw std::runtime_error("Exclude property has an odd number of entries. "
+                               "It has to be even as each pair specifies a "
+                               "start and an end of an interval to exclude.");
+    }
+    m_startX = m_manager->getProperty(m_startXPropertyName);
+    m_endX = m_manager->getProperty(m_endXPropertyName);
+    joinOverlappingRanges(m_exclude);
+  }
+}
+
+/**
+ * Get the X, Y and Error column names as a vector. If no error column is found
+ * there will only be 2 entries in the vector. Gets the name from arguments if
+ * specified otherwise it check if any columns have been set as plot type X, Y
+ * or Yerror. If none found it will return an error
+ * @returns :: a vector containing the X and Y column names and the error column
+ * if it has been set
+ * @throws std::invalid_argument if no X or Y value can be found or are of the
+ * wrong type.
+ */
+
+void TableWorkspaceDomainCreator::setXYEColumnNames(
+    API::ITableWorkspace_sptr ws) const {
+
+  auto columnNames = ws->getColumnNames();
+
+  std::string xColName = m_manager->getProperty(m_xColumnPropertyName);
+  if (xColName != "") {
+    auto column = ws->getColumn(xColName);
+    if (!column->isNumber()) {
+      throw std::invalid_argument(xColName + " does not contain numbers");
+    }
+  }
+
+  std::string yColName = m_manager->getProperty(m_yColumnPropertyName);
+  if (yColName != "") {
+    auto column = ws->getColumn(yColName);
+    if (!column->isNumber()) {
+      throw std::invalid_argument(yColName + " does not contain numbers");
+    }
+  }
+
+  std::string eColName = m_manager->getProperty(m_errorColumnPropertyName);
+  if (eColName != "") {
+    auto column = ws->getColumn(eColName);
+    if (!column->isNumber()) {
+      throw std::invalid_argument(eColName + " does not contain numbers");
+    }
+  }
+
+  // get the column name from plot type
+  for (const auto &name : columnNames) {
+    auto column = ws->getColumn(name);
+    if (xColName == "" && column->getPlotType() == 1) {
+      xColName = column->name();
+    }
+    if (yColName == "" && column->getPlotType() == 2) {
+      yColName = column->name();
+    }
+    if (eColName == "" && column->getPlotType() == 5) {
+      eColName = column->name();
+    }
+  }
+
+  if (xColName != "" && yColName != "") {
+    setColumnNames(xColName, yColName, eColName);
+  } else {
+    throw std::invalid_argument("No valid input for X or Y column names");
+  }
+}
+
+/**
+ * Set the table workspace. Clones the input workspace and makes workspace
+ * into form: x data | y data | error data
+ * @throws std::runtime_error if the Exclude property has an odd number of
+ * entries.
+ */
+void TableWorkspaceDomainCreator::setAndValidateWorkspace(
+    API::Workspace_sptr ws) const {
+  auto tableWorkspace = boost::dynamic_pointer_cast<API::ITableWorkspace>(ws);
+  if (!tableWorkspace) {
+    throw std::invalid_argument("InputWorkspace must be a TableWorkspace.");
+  }
+  setXYEColumnNames(tableWorkspace);
+  std::vector<std::string> columnNames;
+  columnNames.push_back(m_xColName);
+  columnNames.push_back(m_yColName);
+  if (m_errColName != "")
+    columnNames.push_back(m_errColName);
+  // table workspace is cloned so it can be changed within the domain
+  m_tableWorkspace = tableWorkspace->cloneColumns(columnNames);
+
+  // if no error column has been found a column is added with 0 errors
+  if (columnNames.size() == 2) {
+    m_errColName = "AddedErrorColumn";
+    auto columnAdded = m_tableWorkspace->addColumn("double", m_errColName);
+    if (!columnAdded)
+      throw std::invalid_argument("No error column provided.");
+  }
+
+  if (m_tableWorkspace->columnCount() != 3) {
+    throw std::invalid_argument("X or Y Columns not found");
+  }
+}
+} // namespace CurveFitting
+} // namespace Mantid
diff --git a/Framework/CurveFitting/test/Algorithms/CalculateChiSquaredTest.h b/Framework/CurveFitting/test/Algorithms/CalculateChiSquaredTest.h
index 3277c264db06172d4201ab05967f99c567aded32..5c95a66db243b198d8368f00f4fcd4616b1623ae 100644
--- a/Framework/CurveFitting/test/Algorithms/CalculateChiSquaredTest.h
+++ b/Framework/CurveFitting/test/Algorithms/CalculateChiSquaredTest.h
@@ -293,8 +293,9 @@ private:
       }
       if (isHisto) {
         xValues.resize(nData);
+        using std::placeholders::_1;
         std::transform(xBins.begin(), xBins.end() - 1, xValues.begin(),
-                       std::bind2nd(std::plus<double>(), dx / 2));
+                       std::bind(std::plus<double>(), _1, dx / 2));
       } else {
         xValues = xBins;
       }
diff --git a/Framework/CurveFitting/test/Algorithms/EvaluateFunctionTest.h b/Framework/CurveFitting/test/Algorithms/EvaluateFunctionTest.h
index e04c5cc2bea230d53496ec1c9bf13b7cef2dd217..4cacab7e7a1ea47c48ab87a91f91d5efca53ffb8 100644
--- a/Framework/CurveFitting/test/Algorithms/EvaluateFunctionTest.h
+++ b/Framework/CurveFitting/test/Algorithms/EvaluateFunctionTest.h
@@ -191,17 +191,17 @@ private:
       for (size_t i = 0; i < xBins.size(); ++i) {
         xBins[i] = xMin + double(i) * dx;
       }
-
+      using std::placeholders::_1;
       if (workspaceIndex > 0) {
         std::transform(
             xBins.begin(), xBins.end(), xBins.begin(),
-            std::bind2nd(std::plus<double>(), double(workspaceIndex)));
+            std::bind(std::plus<double>(), _1, double(workspaceIndex)));
       }
 
       if (isHisto) {
         xValues.resize(nData);
         std::transform(xBins.begin(), xBins.end() - 1, xValues.begin(),
-                       std::bind2nd(std::plus<double>(), dx / 2));
+                       std::bind(std::plus<double>(), _1, dx / 2));
       } else {
         xValues = xBins;
       }
diff --git a/Framework/CurveFitting/test/Functions/ComptonScatteringCountRateTest.h b/Framework/CurveFitting/test/Functions/ComptonScatteringCountRateTest.h
index 7b1dc1176cbf0326a3e915a4db7001c5013cd3d2..4c43050a0850bbba9bc56e693a488b8e16e90d14 100644
--- a/Framework/CurveFitting/test/Functions/ComptonScatteringCountRateTest.h
+++ b/Framework/CurveFitting/test/Functions/ComptonScatteringCountRateTest.h
@@ -83,9 +83,10 @@ public:
     auto testWS = ComptonProfileTestHelpers::createTestWorkspace(
         1, x0, x1, dx, ComptonProfileTestHelpers::NoiseType::None);
     auto &dataX = testWS->mutableX(0);
+    using std::placeholders::_1;
     std::transform(
         dataX.begin(), dataX.end(), dataX.begin(),
-        std::bind2nd(std::multiplies<double>(), 1e-06)); // to seconds
+        std::bind(std::multiplies<double>(), _1, 1e-06)); // to seconds
     func->setMatrixWorkspace(testWS, 0, dataX.front(), dataX.back());
     FunctionDomain1DView domain(&dataX.front(), dataX.size());
     FunctionValues values(domain);
diff --git a/Framework/CurveFitting/test/Functions/GaussianComptonProfileTest.h b/Framework/CurveFitting/test/Functions/GaussianComptonProfileTest.h
index 2245f9b49ce6db42db4ecee8643101d8af59e3cb..6b7c94ed28f73af6b944c1b3d89f9f97539e2995 100644
--- a/Framework/CurveFitting/test/Functions/GaussianComptonProfileTest.h
+++ b/Framework/CurveFitting/test/Functions/GaussianComptonProfileTest.h
@@ -62,9 +62,10 @@ public:
     auto testWS = ComptonProfileTestHelpers::createTestWorkspace(
         1, x0, x1, dx, ComptonProfileTestHelpers::NoiseType::None);
     auto &dataX = testWS->dataX(0);
+    using std::placeholders::_1;
     std::transform(
         dataX.begin(), dataX.end(), dataX.begin(),
-        std::bind2nd(std::multiplies<double>(), 1e-06)); // to seconds
+        std::bind(std::multiplies<double>(), _1, 1e-06)); // to seconds
     func->setMatrixWorkspace(testWS, 0, dataX.front(), dataX.back());
     FunctionDomain1DView domain(dataX.data(), dataX.size());
     FunctionValues values(domain);
diff --git a/Framework/CurveFitting/test/Functions/GramCharlierComptonProfileTest.h b/Framework/CurveFitting/test/Functions/GramCharlierComptonProfileTest.h
index 15859cad4d288fd793002fd19604d4a12418422f..96b7f454400c201ab9e806543aba0b6dcd1c3390 100644
--- a/Framework/CurveFitting/test/Functions/GramCharlierComptonProfileTest.h
+++ b/Framework/CurveFitting/test/Functions/GramCharlierComptonProfileTest.h
@@ -92,9 +92,10 @@ public:
     auto testWS = ComptonProfileTestHelpers::createTestWorkspace(
         1, x0, x1, dx, ComptonProfileTestHelpers::NoiseType::None);
     auto &dataX = testWS->dataX(0);
+    using std::placeholders::_1;
     std::transform(
         dataX.begin(), dataX.end(), dataX.begin(),
-        std::bind2nd(std::multiplies<double>(), 1e-06)); // to seconds
+        std::bind(std::multiplies<double>(), _1, 1e-06)); // to seconds
     func->setMatrixWorkspace(testWS, 0, dataX.front(), dataX.back());
     FunctionDomain1DView domain(dataX.data(), dataX.size());
     FunctionValues values(domain);
diff --git a/Framework/CurveFitting/test/Functions/InelasticIsoRotDiffTest.h b/Framework/CurveFitting/test/Functions/InelasticIsoRotDiffTest.h
index 1252106eb14126bfed29200eec5d5a58dc22e005..17c5f0c68b9b7ef01ba48350b563f65bec960f85 100644
--- a/Framework/CurveFitting/test/Functions/InelasticIsoRotDiffTest.h
+++ b/Framework/CurveFitting/test/Functions/InelasticIsoRotDiffTest.h
@@ -88,15 +88,16 @@ public:
     // Create the domain of energy values
     std::vector<double> xValues(nData, 0);
     std::iota(xValues.begin(), xValues.end(), -10000.0);
+    using std::placeholders::_1;
     std::transform(xValues.begin(), xValues.end(), xValues.begin(),
-                   std::bind1st(std::multiplies<double>(), dE));
+                   std::bind(std::multiplies<double>(), dE, _1));
     // Evaluate the function on the domain
     std::vector<double> calculatedValues(nData, 0);
     func->function1D(calculatedValues.data(), xValues.data(), nData);
     // Integrate the evaluation
     std::transform(calculatedValues.begin(), calculatedValues.end(),
                    calculatedValues.begin(),
-                   std::bind1st(std::multiplies<double>(), dE));
+                   std::bind(std::multiplies<double>(), dE, _1));
     auto integral =
         std::accumulate(calculatedValues.begin(), calculatedValues.end(), 0.0);
     std::cout << integral << std::endl;
diff --git a/Framework/CurveFitting/test/Functions/MultivariateGaussianComptonProfileTest.h b/Framework/CurveFitting/test/Functions/MultivariateGaussianComptonProfileTest.h
index 3277b0a1161455db42145e5e9d5fd345cbaf9290..8014ed212149507ceff1a3a2722e1e0ad302cbaa 100644
--- a/Framework/CurveFitting/test/Functions/MultivariateGaussianComptonProfileTest.h
+++ b/Framework/CurveFitting/test/Functions/MultivariateGaussianComptonProfileTest.h
@@ -77,9 +77,10 @@ public:
     auto testWS = ComptonProfileTestHelpers::createTestWorkspace(
         1, x0, x1, dx, ComptonProfileTestHelpers::NoiseType::None);
     auto &dataX = testWS->dataX(0);
+    using std::placeholders::_1;
     std::transform(
         dataX.begin(), dataX.end(), dataX.begin(),
-        std::bind2nd(std::multiplies<double>(), 1e-06)); // to seconds
+        std::bind(std::multiplies<double>(), _1, 1e-06)); // to seconds
     func->setMatrixWorkspace(testWS, 0, dataX.front(), dataX.back());
     FunctionDomain1DView domain(dataX.data(), dataX.size());
     FunctionValues values(domain);
diff --git a/Framework/CurveFitting/test/Functions/TeixeiraWaterSQETest.h b/Framework/CurveFitting/test/Functions/TeixeiraWaterSQETest.h
index 63640d1300192aee8f4bf719320178063a92598b..8ebdca8bc53ccd2d8ee60bb4a7862d4019960af2 100644
--- a/Framework/CurveFitting/test/Functions/TeixeiraWaterSQETest.h
+++ b/Framework/CurveFitting/test/Functions/TeixeiraWaterSQETest.h
@@ -67,15 +67,16 @@ public:
     // Create the domain of energy values
     std::vector<double> xValues(nData, 0);
     std::iota(xValues.begin(), xValues.end(), -10000.0);
+    using std::placeholders::_1;
     std::transform(xValues.begin(), xValues.end(), xValues.begin(),
-                   std::bind1st(std::multiplies<double>(), dE));
+                   std::bind(std::multiplies<double>(), dE, _1));
     // Evaluate the function on the domain
     std::vector<double> calculatedValues(nData, 0);
     func->function1D(calculatedValues.data(), xValues.data(), nData);
     // Integrate the evaluation
     std::transform(calculatedValues.begin(), calculatedValues.end(),
                    calculatedValues.begin(),
-                   std::bind1st(std::multiplies<double>(), dE));
+                   std::bind(std::multiplies<double>(), dE, _1));
     auto integral =
         std::accumulate(calculatedValues.begin(), calculatedValues.end(), 0.0);
     TS_ASSERT_DELTA(integral, 1.0, 0.01);
diff --git a/Framework/CurveFitting/test/Functions/VesuvioResolutionTest.h b/Framework/CurveFitting/test/Functions/VesuvioResolutionTest.h
index be95236e63d272149c457f305051b6cb2efafd5b..0149e9f42c9f154a83524e7de3644e7737fadaf1 100644
--- a/Framework/CurveFitting/test/Functions/VesuvioResolutionTest.h
+++ b/Framework/CurveFitting/test/Functions/VesuvioResolutionTest.h
@@ -43,9 +43,10 @@ public:
     auto testWS = ComptonProfileTestHelpers::createTestWorkspace(
         1, x0, x1, dx, ComptonProfileTestHelpers::NoiseType::None);
     auto &dataX = testWS->dataX(0);
+    using std::placeholders::_1;
     std::transform(
         dataX.begin(), dataX.end(), dataX.begin(),
-        std::bind2nd(std::multiplies<double>(), 1e-06)); // to seconds
+        std::bind(std::multiplies<double>(), _1, 1e-06)); // to seconds
     func->setMatrixWorkspace(testWS, 0, dataX.front(), dataX.back());
     FunctionDomain1DView domain(dataX.data(), dataX.size());
     FunctionValues values(domain);
diff --git a/Framework/CurveFitting/test/TableWorkspaceDomainCreatorTest.h b/Framework/CurveFitting/test/TableWorkspaceDomainCreatorTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..ae40772a260275ec522532ac73729c20fd080717
--- /dev/null
+++ b/Framework/CurveFitting/test/TableWorkspaceDomainCreatorTest.h
@@ -0,0 +1,790 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_CURVEFITTING_TABLEWORKSPACEDOMAINCREATORTEST_H_
+#define MANTID_CURVEFITTING_TABLEWORKSPACEDOMAINCREATORTEST_H_
+
+#include "MantidTestHelpers/WorkspaceCreationHelper.h"
+#include <cxxtest/TestSuite.h>
+
+#include "MantidAPI/AlgorithmManager.h"
+#include "MantidAPI/AnalysisDataService.h"
+#include "MantidAPI/Axis.h"
+#include "MantidAPI/CompositeFunction.h"
+#include "MantidAPI/FunctionDomain1D.h"
+#include "MantidAPI/MatrixWorkspace.h"
+#include "MantidAPI/TableRow.h"
+
+#include "MantidCurveFitting/Algorithms/Fit.h"
+#include "MantidCurveFitting/Functions/Convolution.h"
+#include "MantidCurveFitting/Functions/ExpDecay.h"
+#include "MantidCurveFitting/Functions/FlatBackground.h"
+#include "MantidCurveFitting/Functions/Gaussian.h"
+#include "MantidCurveFitting/Functions/Polynomial.h"
+#include "MantidCurveFitting/SeqDomain.h"
+#include "MantidCurveFitting/TableWorkspaceDomainCreator.h"
+
+using Mantid::CurveFitting::TableWorkspaceDomainCreator;
+using namespace Mantid;
+using namespace Mantid::CurveFitting::Algorithms;
+using namespace Mantid::CurveFitting::Functions;
+
+class TableWorkspaceDomainCreatorTest : public CxxTest::TestSuite {
+public:
+  // This pair of boilerplate methods prevent the suite being created statically
+  // This means the constructor isn't called when running other tests
+  static TableWorkspaceDomainCreatorTest *createSuite() {
+    return new TableWorkspaceDomainCreatorTest();
+  }
+  static void destroySuite(TableWorkspaceDomainCreatorTest *suite) {
+    delete suite;
+  }
+
+  void test_exec_with_table_workspace() {
+    auto ws = createTestTableWorkspace();
+    auto fun = createExpDecayFunction(1.0, 1.0);
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws, false);
+    fit->execute();
+    TS_ASSERT(fit->isExecuted());
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_function_parameters() {
+    auto ws = createTestTableWorkspace();
+    auto fun = createExpDecayFunction(1.0, 1.0);
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws, false);
+    fit->execute();
+
+    TS_ASSERT_DELTA(fun->getParameter("Height"), 10.0, 1e-6);
+    TS_ASSERT_DELTA(fun->getParameter("Lifetime"), 0.5, 1e-6);
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_Output_Workspace() {
+    auto ws = createTestTableWorkspace();
+    auto fun = createExpDecayFunction(1.0, 1.0);
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws);
+    fit->execute();
+
+    TS_ASSERT_EQUALS(fit->getPropertyValue("OutputStatus"), "success");
+
+    API::MatrixWorkspace_sptr outWS =
+        boost::dynamic_pointer_cast<API::MatrixWorkspace>(
+            API::AnalysisDataService::Instance().retrieve("Output_Workspace"));
+
+    TS_ASSERT(outWS);
+    TS_ASSERT_EQUALS(outWS->getNumberHistograms(), 3);
+    API::Axis *axis = outWS->getAxis(1);
+    TS_ASSERT(axis);
+    TS_ASSERT(axis->isText());
+    TS_ASSERT_EQUALS(axis->length(), 3);
+    TS_ASSERT_EQUALS(axis->label(0), "Data");
+    TS_ASSERT_EQUALS(axis->label(1), "Calc");
+    TS_ASSERT_EQUALS(axis->label(2), "Diff");
+
+    const auto &Data = outWS->y(0);
+    const auto &Calc = outWS->y(1);
+    const auto &Diff = outWS->y(2);
+    for (size_t i = 0; i < Data.size(); ++i) {
+      TS_ASSERT_EQUALS(Data[i] - Calc[i], Diff[i]);
+    }
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_Output_NormalisedCovarianceMatrix_table() {
+    auto ws = createTestTableWorkspace();
+    auto fun = createExpDecayFunction(1.0, 1.0);
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws);
+    fit->execute();
+
+    API::ITableWorkspace_sptr covar =
+        boost::dynamic_pointer_cast<API::ITableWorkspace>(
+            API::AnalysisDataService::Instance().retrieve(
+                "Output_NormalisedCovarianceMatrix"));
+
+    TS_ASSERT(covar);
+    TS_ASSERT_EQUALS(covar->columnCount(), 3);
+    TS_ASSERT_EQUALS(covar->rowCount(), 2);
+    TS_ASSERT_EQUALS(covar->String(0, 0), "Height");
+    TS_ASSERT_EQUALS(covar->String(1, 0), "Lifetime");
+    TS_ASSERT_EQUALS(covar->getColumn(0)->type(), "str");
+    TS_ASSERT_EQUALS(covar->getColumn(0)->name(), "Name");
+    TS_ASSERT_EQUALS(covar->getColumn(1)->type(), "double");
+    TS_ASSERT_EQUALS(covar->getColumn(1)->name(), "Height");
+    TS_ASSERT_EQUALS(covar->getColumn(2)->type(), "double");
+    TS_ASSERT_EQUALS(covar->getColumn(2)->name(), "Lifetime");
+    TS_ASSERT_EQUALS(covar->Double(0, 1), 100.0);
+    TS_ASSERT_EQUALS(covar->Double(1, 2), 100.0);
+    TS_ASSERT(fabs(covar->Double(0, 2)) < 100.0);
+    TS_ASSERT(fabs(covar->Double(0, 2)) > 0.0);
+    TS_ASSERT_DELTA(covar->Double(0, 2), covar->Double(1, 1), 0.000001);
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_Output_Parameters_table() {
+    auto ws = createTestTableWorkspace();
+    auto fun = createExpDecayFunction(1.0, 1.0);
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws);
+    fit->execute();
+    API::ITableWorkspace_sptr params =
+        boost::dynamic_pointer_cast<API::ITableWorkspace>(
+            API::AnalysisDataService::Instance().retrieve("Output_Parameters"));
+
+    double chi2 = fit->getProperty("OutputChi2overDoF");
+    TS_ASSERT_DELTA(chi2, 0.0, 1e-8);
+
+    TS_ASSERT(params);
+    TS_ASSERT_EQUALS(params->columnCount(), 3);
+    TS_ASSERT_EQUALS(params->rowCount(), 3);
+    TS_ASSERT_EQUALS(params->String(0, 0), "Height");
+    TS_ASSERT_EQUALS(params->String(1, 0), "Lifetime");
+    TS_ASSERT_EQUALS(params->String(2, 0), "Cost function value");
+    TS_ASSERT_EQUALS(params->Double(0, 1), fun->getParameter(0));
+    TS_ASSERT_EQUALS(params->Double(1, 1), fun->getParameter(1));
+    TS_ASSERT_EQUALS(params->Double(2, 1), chi2);
+    TS_ASSERT_EQUALS(params->Double(0, 2), fun->getError(0));
+    TS_ASSERT_EQUALS(params->Double(1, 2), fun->getError(1));
+    TS_ASSERT_EQUALS(params->Double(2, 2), 0.0);
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  // test that errors of the calculated output are reasonable
+  void test_output_errors_are_reasonable() {
+    auto ws = createTestTableWorkspace();
+    auto fun = createPolynomialFunction(5);
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws);
+    fit->execute();
+
+    TS_ASSERT(fit->isExecuted());
+
+    Mantid::API::MatrixWorkspace_sptr out_ws =
+        Mantid::API::AnalysisDataService::Instance()
+            .retrieveWS<Mantid::API::MatrixWorkspace>("Output_Workspace");
+    TS_ASSERT(out_ws);
+    TS_ASSERT_EQUALS(out_ws->getNumberHistograms(), 3);
+    auto &e = out_ws->e(1);
+    for (size_t i = 0; i < e.size(); ++i) {
+      TS_ASSERT(e[i] < 1.0);
+    }
+
+    Mantid::API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_output_no_errors_provided() {
+    auto wsWithNoErrors = createTestTableWorkspace(false);
+    auto fun = createExpDecayFunction(1.0, 1.0);
+    auto fit = boost::make_shared<Fit>();
+    fit->initialize();
+    fit->setProperty("Function", fun);
+    fit->setProperty("InputWorkspace", wsWithNoErrors);
+    fit->setProperty("CreateOutput", true);
+    fit->setProperty("XColumn", "X data");
+    fit->setProperty("YColumn", "Y data");
+    fit->execute();
+
+    TS_ASSERT_DELTA(fun->getParameter("Height"), 10.0, 1e-6);
+    TS_ASSERT_DELTA(fun->getParameter("Lifetime"), 0.5, 1e-6);
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_all_outputs() {
+    auto ws = createTestTableWorkspace();
+    auto fun = createPolynomialFunction(1);
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws);
+    fit->setProperty("Output", "out");
+    fit->execute();
+
+    TS_ASSERT(fit->isExecuted());
+    TS_ASSERT(Mantid::API::AnalysisDataService::Instance().doesExist(
+        "out_Workspace"));
+    TS_ASSERT(Mantid::API::AnalysisDataService::Instance().doesExist(
+        "out_Parameters"));
+    TS_ASSERT(Mantid::API::AnalysisDataService::Instance().doesExist(
+        "out_NormalisedCovarianceMatrix"));
+
+    Mantid::API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_output_parameters_only() {
+    auto ws = createTestTableWorkspace();
+    auto fun = createPolynomialFunction(1);
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws);
+    fit->setProperty("Output", "out");
+    fit->setProperty("OutputParametersOnly", true);
+    fit->execute();
+
+    TS_ASSERT(fit->isExecuted());
+    TS_ASSERT(!Mantid::API::AnalysisDataService::Instance().doesExist(
+        "out_Workspace"));
+    TS_ASSERT(Mantid::API::AnalysisDataService::Instance().doesExist(
+        "out_Parameters"));
+
+    Mantid::API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_takes_correct_columns_when_no_column_names_given_but_types_set() {
+    // create table
+    API::ITableWorkspace_sptr table =
+        API::WorkspaceFactory::Instance().createTable();
+    table->addColumn("double", "Y data");
+    table->addColumn("double", "Errors");
+    table->addColumn("double", "other data");
+    table->addColumn("double", "X data");
+    table->addColumn("double", "more extra data");
+
+    // set values in table
+    for (auto i = 0; i != 20; ++i) {
+      const double xValue = i * 0.1;
+      const double yValue = 10.0 * exp(-xValue / 0.5);
+      const double eValue = 0.1;
+      API::TableRow newRow = table->appendRow();
+      newRow << yValue << eValue << 2.0 << xValue << 5.1;
+    }
+
+    // set plot type for x, y and error columns
+    table->getColumn("X data")->setPlotType(1);
+    table->getColumn("Y data")->setPlotType(2);
+    table->getColumn("Errors")->setPlotType(5);
+
+    auto fun = createExpDecayFunction(1.0, 1.0);
+    auto fit = boost::make_shared<Fit>();
+    fit->initialize();
+    fit->setProperty("Function", fun);
+    fit->setProperty("InputWorkspace", table);
+    fit->setProperty("CreateOutput", true);
+    fit->execute();
+
+    TS_ASSERT_DELTA(fun->getParameter("Height"), 10.0, 1e-6);
+    TS_ASSERT_DELTA(fun->getParameter("Lifetime"), 0.5, 1e-6);
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_createDomain_creates_FunctionDomain1DVector() {
+    auto ws = createTestTableWorkspace();
+
+    // set plot type for x, y and error columns
+    ws->getColumn("X data")->setPlotType(1);
+    ws->getColumn("Y data")->setPlotType(2);
+    ws->getColumn("Errors")->setPlotType(5);
+
+    API::FunctionDomain_sptr domain;
+    API::FunctionValues_sptr values;
+
+    TableWorkspaceDomainCreator tableDomainCreator;
+    tableDomainCreator.setWorkspace(ws);
+    tableDomainCreator.setColumnNames("X data", "Y data", "Errors");
+    tableDomainCreator.createDomain(domain, values);
+
+    API::FunctionDomain1DVector *specDom =
+        dynamic_cast<API::FunctionDomain1DVector *>(domain.get());
+    TS_ASSERT(specDom);
+    TS_ASSERT_EQUALS(specDom->size(), ws->rowCount());
+  }
+
+  void test_create_SeqDomain_creates_domain() {
+    auto ws = createTableWorkspaceForSeqFit();
+
+    // set plot type for x, y and error columns
+    ws->getColumn("X data")->setPlotType(1);
+    ws->getColumn("Y data")->setPlotType(2);
+    ws->getColumn("Errors")->setPlotType(5);
+
+    API::FunctionDomain_sptr domain;
+    API::FunctionValues_sptr values;
+
+    TableWorkspaceDomainCreator tableDomainCreator(
+        TableWorkspaceDomainCreator::Sequential);
+    tableDomainCreator.setWorkspace(ws);
+    tableDomainCreator.setMaxSize(3);
+    tableDomainCreator.setColumnNames("X data", "Y data", "Errors");
+    tableDomainCreator.createDomain(domain, values);
+
+    CurveFitting::SeqDomain *seq =
+        dynamic_cast<CurveFitting::SeqDomain *>(domain.get());
+    TS_ASSERT(seq);
+    TS_ASSERT_EQUALS(seq->getNDomains(), 4);
+    TS_ASSERT_EQUALS(seq->size(), 10);
+    Mantid::API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_create_SeqDomain_outputs() {
+    auto ws = createTableWorkspaceForSeqFit();
+
+    // set plot type for x, y and error columns
+    ws->getColumn("X data")->setPlotType(1);
+    ws->getColumn("Y data")->setPlotType(2);
+    ws->getColumn("Errors")->setPlotType(5);
+
+    API::FunctionDomain_sptr domain;
+    API::FunctionValues_sptr values;
+
+    TableWorkspaceDomainCreator tableDomainCreator(
+        TableWorkspaceDomainCreator::Sequential);
+    tableDomainCreator.setWorkspace(ws);
+    tableDomainCreator.setMaxSize(3);
+    tableDomainCreator.setColumnNames("X data", "Y data", "Errors");
+    tableDomainCreator.createDomain(domain, values);
+
+    CurveFitting::SeqDomain *seq =
+        dynamic_cast<CurveFitting::SeqDomain *>(domain.get());
+    TS_ASSERT(seq);
+
+    API::FunctionDomain_sptr dom;
+    API::FunctionValues_sptr val;
+
+    for (auto i = 0; i < 2; ++i) {
+      seq->getDomainAndValues(i, dom, val);
+      TS_ASSERT_EQUALS(dom->size(), 3);
+      TS_ASSERT_EQUALS(val->size(), 3);
+      auto d1d = static_cast<Mantid::API::FunctionDomain1D *>(dom.get());
+      auto v1d = static_cast<Mantid::API::FunctionValues *>(val.get());
+      TS_ASSERT(d1d);
+      TS_ASSERT_DELTA((*d1d)[0], i * 0.3, 1e-13);
+      TS_ASSERT_DELTA((*d1d)[1], 0.1 + i * 0.3, 1e-13);
+      TS_ASSERT_DELTA((*d1d)[2], 0.2 + i * 0.3, 1e-13);
+      TS_ASSERT_DELTA(v1d->getFitData(0), i + 1, 1e-13);
+      TS_ASSERT_DELTA(v1d->getFitData(1), i + 1, 1e-13);
+      TS_ASSERT_DELTA(v1d->getFitData(2), i + 1, 1e-13);
+      val.reset();
+    }
+    seq->getDomainAndValues(3, dom, val);
+    TS_ASSERT_EQUALS(dom->size(), 1);
+    TS_ASSERT_EQUALS(val->size(), 1);
+    auto d1d = static_cast<Mantid::API::FunctionDomain1D *>(dom.get());
+    auto v1d = static_cast<Mantid::API::FunctionValues *>(val.get());
+    TS_ASSERT(d1d);
+    TS_ASSERT_DELTA((*d1d)[0], 0.9, 1e-13);
+    TS_ASSERT_DELTA(v1d->getFitData(0), 4.0, 1e-13);
+
+    Mantid::API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_ignore_invalid_data_weighting() {
+    auto ws = createTableWorkspaceWithInvalidData();
+
+    // set plot type for x, y and error columns
+    ws->getColumn("X data")->setPlotType(1);
+    ws->getColumn("Y data")->setPlotType(2);
+    ws->getColumn("Errors")->setPlotType(5);
+
+    API::FunctionDomain_sptr domain;
+    API::FunctionValues_sptr values;
+
+    // Requires a property manager to make a workspce
+    auto propManager = boost::make_shared<Mantid::Kernel::PropertyManager>();
+    const std::string wsPropName = "TestWorkspaceInput";
+    propManager->declareProperty(
+        std::make_unique<API::WorkspaceProperty<API::Workspace>>(
+            wsPropName, "", Mantid::Kernel::Direction::Input));
+    propManager->setProperty<API::Workspace_sptr>(wsPropName, ws);
+
+    TableWorkspaceDomainCreator tableWSDomainCreator(propManager.get(),
+                                                     wsPropName);
+    tableWSDomainCreator.declareDatasetProperties("", true);
+    tableWSDomainCreator.ignoreInvalidData(true);
+    tableWSDomainCreator.setColumnNames("X data", "Y data", "Errors");
+    tableWSDomainCreator.createDomain(domain, values);
+
+    API::FunctionValues *val =
+        dynamic_cast<API::FunctionValues *>(values.get());
+    for (size_t i = 0; i < val->size(); ++i) {
+      if (i == 3 || i == 5 || i == 7 || i == 9 || i == 11) {
+        TS_ASSERT_EQUALS(val->getFitWeight(i), 0.0);
+      } else {
+        TS_ASSERT_DIFFERS(val->getFitWeight(i), 0.0);
+      }
+    }
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_ignore_invalid_data_LevenbergMarquardt() {
+    auto ws = createTableWorkspaceWithInvalidData();
+    auto fun = createExpDecayFunction(1.0, 1.0);
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws);
+    fit->setProperty("IgnoreInvalidData", true);
+    fit->setProperty("Minimizer", "Levenberg-Marquardt");
+    fit->execute();
+    TS_ASSERT(fit->isExecuted());
+
+    TS_ASSERT_DELTA(fun->getParameter("Height"), 10.0, 1e-3);
+    TS_ASSERT_DELTA(fun->getParameter("Lifetime"), 0.5, 1e-4);
+
+    Mantid::API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_ignore_invalid_data_LevenbergMarquardtMD() {
+    auto ws = createTableWorkspaceWithInvalidData();
+    auto fun = createExpDecayFunction(1.0, 1.0);
+    auto fit1 = setupBasicFitPropertiesAlgorithm(fun, ws);
+    fit1->setProperty("IgnoreInvalidData", true);
+    fit1->setProperty("Minimizer", "Levenberg-MarquardtMD");
+    fit1->execute();
+    TS_ASSERT(fit1->isExecuted());
+
+    TS_ASSERT_DELTA(fun->getParameter("Height"), 10.0, 1e-3);
+    TS_ASSERT_DELTA(fun->getParameter("Lifetime"), 0.5, 1e-4);
+
+    Mantid::API::AnalysisDataService::Instance().clear();
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_exclude_with_values_in_data() {
+    auto ws = createTableWorkspaceForExclude();
+
+    std::vector<double> exclude{1.0, 2.0};
+    API::IFunction_sptr fun(new FlatBackground);
+    fun->initialize();
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws);
+    fit->setProperty("Exclude", exclude);
+
+    API::FunctionDomain_sptr domain;
+    API::FunctionValues_sptr values;
+
+    TableWorkspaceDomainCreator tableWSDomainCreator(fit.get(),
+                                                     "InputWorkspace");
+    tableWSDomainCreator.declareDatasetProperties("", false);
+    tableWSDomainCreator.createDomain(domain, values);
+
+    TS_ASSERT_EQUALS(values->getFitWeight(0), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(1), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(2), 0);
+    TS_ASSERT_EQUALS(values->getFitWeight(3), 0);
+    TS_ASSERT_EQUALS(values->getFitWeight(4), 0);
+    TS_ASSERT_EQUALS(values->getFitWeight(5), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(6), 1);
+
+    fit->execute();
+
+    fun = fit->getProperty("Function");
+    TS_ASSERT_EQUALS(fun->getParameter("A0"), 1);
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_exclude_with_values_below_x_data_range() {
+    auto ws = createTableWorkspaceForExclude();
+    std::vector<double> exclude{-2.0, -1.0};
+    API::IFunction_sptr fun(new FlatBackground);
+    fun->initialize();
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws);
+    fit->setProperty("Exclude", exclude);
+
+    API::FunctionDomain_sptr domain;
+    API::FunctionValues_sptr values;
+
+    TableWorkspaceDomainCreator tableWSDomainCreator(fit.get(),
+                                                     "InputWorkspace");
+    tableWSDomainCreator.declareDatasetProperties("", false);
+    tableWSDomainCreator.createDomain(domain, values);
+
+    TS_ASSERT_EQUALS(values->getFitWeight(0), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(1), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(2), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(3), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(4), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(5), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(6), 1);
+
+    fit->execute();
+
+    fun = fit->getProperty("Function");
+    TS_ASSERT_DELTA(fun->getParameter("A0"), 1.4285, 1e-4);
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_exclude_with_values_above_x_data_range() {
+    auto ws = createTableWorkspaceForExclude();
+    std::vector<double> exclude{4.0, 5.0};
+    API::IFunction_sptr fun(new FlatBackground);
+    fun->initialize();
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws);
+    fit->setProperty("Exclude", exclude);
+
+    API::FunctionDomain_sptr domain;
+    API::FunctionValues_sptr values;
+
+    TableWorkspaceDomainCreator tableWSDomainCreator(fit.get(),
+                                                     "InputWorkspace");
+    tableWSDomainCreator.declareDatasetProperties("", false);
+    tableWSDomainCreator.createDomain(domain, values);
+
+    TS_ASSERT_EQUALS(values->getFitWeight(0), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(1), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(2), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(3), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(4), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(5), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(6), 1);
+
+    fit->execute();
+
+    fun = fit->getProperty("Function");
+    TS_ASSERT_DELTA(fun->getParameter("A0"), 1.4285, 1e-4);
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_exclude_with_values_above_and_below_x_data_range() {
+    auto ws = createTableWorkspaceForExclude();
+    std::vector<double> exclude{-2.0, -1.0, 4.0, 5.0};
+    API::IFunction_sptr fun(new FlatBackground);
+    fun->initialize();
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws);
+    fit->setProperty("Exclude", exclude);
+
+    API::FunctionDomain_sptr domain;
+    API::FunctionValues_sptr values;
+
+    TableWorkspaceDomainCreator tableWSDomainCreator(fit.get(),
+                                                     "InputWorkspace");
+    tableWSDomainCreator.declareDatasetProperties("", false);
+    tableWSDomainCreator.createDomain(domain, values);
+
+    TS_ASSERT_EQUALS(values->getFitWeight(0), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(1), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(2), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(3), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(4), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(5), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(6), 1);
+
+    fit->execute();
+
+    fun = fit->getProperty("Function");
+    TS_ASSERT_DELTA(fun->getParameter("A0"), 1.4285, 1e-4);
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_exclude_fails_with_odd_number_of_entries() {
+    auto ws = createTableWorkspaceForExclude();
+    std::vector<double> exclude{-2.0, -1.0, 4.0};
+    API::IFunction_sptr fun(new FlatBackground);
+    fun->initialize();
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws);
+
+    TS_ASSERT_THROWS(fit->setProperty("Exclude", exclude),
+                     const std::invalid_argument &);
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_exclude_fails_for_unordered_entries() {
+    auto ws = createTableWorkspaceForExclude();
+    std::vector<double> exclude{-2.0, -1.0, 4.0, 2.0};
+    API::IFunction_sptr fun(new FlatBackground);
+    fun->initialize();
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws);
+    TS_ASSERT_THROWS(fit->setProperty("Exclude", exclude),
+                     const std::invalid_argument &);
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_exclude_for_overlapped_entries() {
+    auto ws = createTableWorkspaceForExclude();
+
+    std::vector<double> exclude{-1.0, 0.5, 0.0, 0.5, 2.5, 5.0, 2.0, 4.0};
+    API::IFunction_sptr fun(new FlatBackground);
+    fun->initialize();
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws);
+    fit->setProperty("Exclude", exclude);
+
+    API::FunctionDomain_sptr domain;
+    API::FunctionValues_sptr values;
+
+    TableWorkspaceDomainCreator tableWSDomainCreator(fit.get(),
+                                                     "InputWorkspace");
+    tableWSDomainCreator.declareDatasetProperties("", false);
+    tableWSDomainCreator.createDomain(domain, values);
+
+    TS_ASSERT_EQUALS(values->getFitWeight(0), 0);
+    TS_ASSERT_EQUALS(values->getFitWeight(1), 0);
+    TS_ASSERT_EQUALS(values->getFitWeight(2), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(3), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(4), 0);
+    TS_ASSERT_EQUALS(values->getFitWeight(5), 0);
+    TS_ASSERT_EQUALS(values->getFitWeight(6), 0);
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+  void test_exclude_overlapped_unsorted_order() {
+    auto ws = createTableWorkspaceForExclude();
+
+    std::vector<double> exclude{2.2, 2.9, 0.6, 1.5, 1.4, 2.4};
+    API::IFunction_sptr fun(new FlatBackground);
+    fun->initialize();
+    auto fit = setupBasicFitPropertiesAlgorithm(fun, ws);
+    fit->setProperty("Exclude", exclude);
+
+    API::FunctionDomain_sptr domain;
+    API::FunctionValues_sptr values;
+
+    TableWorkspaceDomainCreator tableWSDomainCreator(fit.get(),
+                                                     "InputWorkspace");
+    tableWSDomainCreator.declareDatasetProperties("", false);
+    tableWSDomainCreator.createDomain(domain, values);
+
+    TS_ASSERT_EQUALS(values->getFitWeight(0), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(1), 1);
+    TS_ASSERT_EQUALS(values->getFitWeight(2), 0);
+    TS_ASSERT_EQUALS(values->getFitWeight(3), 0);
+    TS_ASSERT_EQUALS(values->getFitWeight(4), 0);
+    TS_ASSERT_EQUALS(values->getFitWeight(5), 0);
+    TS_ASSERT_EQUALS(values->getFitWeight(6), 1);
+
+    API::AnalysisDataService::Instance().clear();
+  }
+
+private:
+  API::ITableWorkspace_sptr
+  createEmptyTableWith3ColumnsWorkspace(bool errors = true) {
+    API::ITableWorkspace_sptr table =
+        API::WorkspaceFactory::Instance().createTable();
+    table->addColumn("double", "X data");
+    table->addColumn("double", "Y data");
+    if (errors)
+      table->addColumn("double", "Errors");
+    return table;
+  }
+
+  API::ITableWorkspace_sptr createTestTableWorkspace(bool errors = true) {
+    auto table = createEmptyTableWith3ColumnsWorkspace(errors);
+
+    for (auto i = 0; i != 20; ++i) {
+      const double xValue = i * 0.1;
+      const double yValue = 10.0 * exp(-xValue / 0.5);
+      API::TableRow newRow = table->appendRow();
+      if (errors) {
+        const double eValue = 0.1;
+        newRow << xValue << yValue << eValue;
+      } else {
+        newRow << xValue << yValue;
+      }
+    }
+
+    return table;
+  }
+
+  API::ITableWorkspace_sptr createTableWorkspaceForSeqFit() {
+    auto table = createEmptyTableWith3ColumnsWorkspace();
+
+    for (auto i = 0; i != 10; ++i) {
+      const double xValue = i * 0.1;
+      double yValue;
+      if (i < 3) {
+        yValue = 1.0;
+      } else if (i < 6) {
+        yValue = 2.0;
+      } else if (i < 9) {
+        yValue = 3.0;
+      } else {
+        yValue = 4.0;
+      }
+      const double eValue = 0.1;
+      API::TableRow newRow = table->appendRow();
+      newRow << xValue << yValue << eValue;
+    }
+    return table;
+  }
+
+  API::ITableWorkspace_sptr createTableWorkspaceWithInvalidData() {
+    auto table = createEmptyTableWith3ColumnsWorkspace();
+
+    for (auto i = 0; i != 20; ++i) {
+      const double xValue = i * 0.1;
+      double yValue;
+      double eValue;
+      const double one = 1.0;
+
+      if (i == 3)
+        yValue = std::numeric_limits<double>::infinity();
+      else if (i == 5)
+        yValue = log(-one);
+      else
+        yValue = 10.0 * exp(-xValue / 0.5);
+
+      if (i == 7)
+        eValue = 0;
+      else if (i == 9)
+        eValue = std::numeric_limits<double>::infinity();
+      else if (i == 11)
+        eValue = log(-one);
+      else
+        eValue = 0.1;
+
+      API::TableRow newRow = table->appendRow();
+      newRow << xValue << yValue << eValue;
+    }
+    return table;
+  }
+
+  API::ITableWorkspace_sptr createTableWorkspaceForExclude() {
+    auto table = createEmptyTableWith3ColumnsWorkspace();
+    for (auto i = 0; i < 7; ++i) {
+      const double xValue = i * 0.5;
+      double yValue;
+      if (xValue >= 1.0 && xValue <= 2.0)
+        yValue = 2.0;
+      else
+        yValue = 1.0;
+      API::TableRow newRow = table->appendRow();
+      newRow << xValue << yValue;
+    }
+    return table;
+  }
+
+  API::IFunction_sptr createExpDecayFunction(double height, double lifetime) {
+    API::IFunction_sptr fun(new ExpDecay);
+    fun->setParameter("Height", height);
+    fun->setParameter("Lifetime", lifetime);
+    return fun;
+  }
+
+  API::IFunction_sptr createPolynomialFunction(int degree) {
+    API::IFunction_sptr fun(new Polynomial);
+    fun->setAttributeValue("n", degree);
+    return fun;
+  }
+
+  API::IFunction_sptr createGaussianFunction(double height, double peakCentre,
+                                             double sigma) {
+    API::IFunction_sptr fun(new Gaussian);
+    fun->initialize();
+    fun->setParameter("Height", height);
+    fun->setParameter("PeakCentre", peakCentre);
+    fun->setParameter("Sigma", sigma);
+    return fun;
+  }
+
+  boost::shared_ptr<Fit>
+  setupBasicFitPropertiesAlgorithm(API::IFunction_sptr fun,
+                                   API::Workspace_sptr ws,
+                                   bool createOutput = true) {
+    auto fit = boost::make_shared<Fit>();
+    fit->initialize();
+    fit->setProperty("Function", fun);
+    fit->setProperty("InputWorkspace", ws);
+    fit->setProperty("CreateOutput", createOutput);
+    fit->setProperty("XColumn", "X data");
+    fit->setProperty("YColumn", "Y data");
+    fit->setProperty("ErrColumn", "Errors");
+
+    return fit;
+  }
+};
+
+#endif /* MANTID_CURVEFITTING_TABLEWORKSPACEDOMAINCREATORTEST_H_ */
diff --git a/Framework/DataHandling/CMakeLists.txt b/Framework/DataHandling/CMakeLists.txt
index d1d6b69016858201fda69f7e2d8d1e792a314352..efc18bf54c669823107ad883bf17a3fec42cdc58 100644
--- a/Framework/DataHandling/CMakeLists.txt
+++ b/Framework/DataHandling/CMakeLists.txt
@@ -90,6 +90,7 @@ set(SRC_FILES
     src/LoadNexusMonitors.cpp
     src/LoadNexusMonitors2.cpp
     src/LoadNexusProcessed.cpp
+    src/LoadNexusProcessed2.cpp
     src/LoadOff.cpp
     src/LoadPDFgetNFile.cpp
     src/LoadPLN.cpp
@@ -170,6 +171,7 @@ set(SRC_FILES
     src/SaveNXTomo.cpp
     src/SaveNXcanSAS.cpp
     src/SaveNexus.cpp
+    src/SaveNexusESS.cpp
     src/SaveNexusGeometry.cpp
     src/SaveNexusProcessed.cpp
     src/SaveOpenGenieAscii.cpp
@@ -290,6 +292,7 @@ set(INC_FILES
     inc/MantidDataHandling/LoadNexusMonitors.h
     inc/MantidDataHandling/LoadNexusMonitors2.h
     inc/MantidDataHandling/LoadNexusProcessed.h
+    inc/MantidDataHandling/LoadNexusProcessed2.h
     inc/MantidDataHandling/LoadOff.h
     inc/MantidDataHandling/LoadPDFgetNFile.h
     inc/MantidDataHandling/LoadPLN.h
@@ -367,6 +370,7 @@ set(INC_FILES
     inc/MantidDataHandling/SaveNXTomo.h
     inc/MantidDataHandling/SaveNXcanSAS.h
     inc/MantidDataHandling/SaveNexus.h
+    inc/MantidDataHandling/SaveNexusESS.h
     inc/MantidDataHandling/SaveNexusGeometry.h
     inc/MantidDataHandling/SaveNexusProcessed.h
     inc/MantidDataHandling/SaveOpenGenieAscii.h
@@ -477,6 +481,7 @@ set(TEST_FILES
     LoadNXcanSASTest.h
     LoadNexusLogsTest.h
     LoadNexusMonitorsTest.h
+    LoadNexusProcessed2Test.h
     LoadNexusProcessedTest.h
     LoadNexusTest.h
     LoadPDFgetNFileTest.h
@@ -549,6 +554,7 @@ set(TEST_FILES
     SaveNXSPETest.h
     SaveNXTomoTest.h
     SaveNXcanSASTest.h
+    SaveNexusESSTest.h
     SaveNexusGeometryTest.h
     SaveNexusProcessedTest.h
     SaveNexusTest.h
diff --git a/Framework/DataHandling/inc/MantidDataHandling/CreateSampleShape.h b/Framework/DataHandling/inc/MantidDataHandling/CreateSampleShape.h
index 3cf2c9c3e29552becbc0b28ebe09364aa02fb911..74893dc83826abfe49c6dec9e9747c022f06d9bc 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/CreateSampleShape.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/CreateSampleShape.h
@@ -13,42 +13,42 @@
 #include "MantidAPI/Algorithm.h"
 
 namespace Mantid {
+namespace API {
+class ExperimentInfo;
+}
 namespace DataHandling {
 
 /**
     This class allows the shape of the sample to be defined by using the allowed
-   XML
-    expressions
+   XML expressions
 
     @author Martyn Gigg, Tessella Support Services plc
     @date 13/03/2009
 */
-class DLLExport CreateSampleShape : public Mantid::API::Algorithm {
+class DLLExport CreateSampleShape : public API::Algorithm {
+public:
+  static void setSampleShape(API::ExperimentInfo &expt,
+                             const std::string &shapeXML);
+
 public:
-  /// Algorithm's name
   const std::string name() const override { return "CreateSampleShape"; }
-  /// Summary of algorithms purpose
   const std::string summary() const override {
     return "Create a shape object to model the sample.";
   }
 
-  /// Algorithm's version
   int version() const override { return (1); }
   const std::vector<std::string> seeAlso() const override {
     return {"SetSample", "AbsorptionCorrection", "SetSampleMaterial",
             "CopySample"};
   }
-  /// Algorithm's category for identification
   const std::string category() const override { return "Sample;"; }
-  /// Algorithm's aliases
   const std::string alias() const override { return "SetSampleShape"; }
 
 private:
-  /// Initialisation code
   void init() override;
-  /// Execution code
   void exec() override;
 };
+
 } // namespace DataHandling
 } // namespace Mantid
 
diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadNexusProcessed.h b/Framework/DataHandling/inc/MantidDataHandling/LoadNexusProcessed.h
index ae7d384249179271e0ffad423c1962c66a66b666..84eee2fb590d5ff719af4a14232677022e8ceb61 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/LoadNexusProcessed.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/LoadNexusProcessed.h
@@ -15,10 +15,9 @@
 #include "MantidAPI/MatrixWorkspace_fwd.h"
 #include "MantidHistogramData/BinEdges.h"
 #include "MantidKernel/cow_ptr.h"
-
 #include "MantidNexus/NexusClasses.h"
-
 #include <map>
+#include <vector>
 
 namespace NeXus {
 class File;
@@ -27,7 +26,6 @@ class File;
 namespace Mantid {
 
 namespace DataHandling {
-
 /**
 
 Loads a workspace from a NeXus Processed entry in a NeXus file.
@@ -70,7 +68,15 @@ public:
   /// Returns a confidence value that this algorithm can load a file
   int confidence(Kernel::NexusDescriptor &descriptor) const override;
 
+protected:
+  /// Read the spectra
+  void readInstrumentGroup(Mantid::NeXus::NXEntry &mtd_entry,
+                           API::MatrixWorkspace &local_workspace);
+
 private:
+  virtual void readSpectraToDetectorMapping(Mantid::NeXus::NXEntry &mtd_entry,
+                                            Mantid::API::MatrixWorkspace &ws);
+
   /// Validates the input Min < Max and Max < Maximum_Int
   std::map<std::string, std::string> validateInputs() override;
   /// Overwrites Algorithm method.
@@ -93,6 +99,12 @@ private:
   std::string loadWorkspaceName(Mantid::NeXus::NXRoot &root,
                                 const std::string &entry_name);
 
+  /// Load nexus geometry and apply to workspace
+  virtual bool loadNexusGeometry(Mantid::API::Workspace &, const int,
+                                 Kernel::Logger &,
+                                 const std::string &) { /*do nothing*/
+    return false;
+  }
   /// Load a single entry
   API::Workspace_sptr loadEntry(Mantid::NeXus::NXRoot &root,
                                 const std::string &entry_name,
@@ -139,9 +151,6 @@ private:
   bool addSampleProperty(Mantid::NeXus::NXMainClass &sample_entry,
                          const std::string &entryName,
                          API::Sample &sampleDetails);
-  /// Read the spectra
-  void readInstrumentGroup(Mantid::NeXus::NXEntry &mtd_entry,
-                           API::MatrixWorkspace_sptr local_workspace);
   /// Splits a string of exactly three words into the separate words
   void getWordsInString(const std::string &words3, std::string &w1,
                         std::string &w2, std::string &w3);
diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadNexusProcessed2.h b/Framework/DataHandling/inc/MantidDataHandling/LoadNexusProcessed2.h
new file mode 100644
index 0000000000000000000000000000000000000000..39ec85a7896379431f67e171a2f1c35dc192aa03
--- /dev/null
+++ b/Framework/DataHandling/inc/MantidDataHandling/LoadNexusProcessed2.h
@@ -0,0 +1,61 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_DATAHANDLING_LOADNEXUSPROCESSED2_H_
+#define MANTID_DATAHANDLING_LOADNEXUSPROCESSED2_H_
+
+#include "MantidAPI/Algorithm.h"
+#include "MantidDataHandling/DllConfig.h"
+#include "MantidDataHandling/LoadNexusProcessed.h"
+#include "MantidGeometry/IDTypes.h"
+#include "MantidIndexing/SpectrumNumber.h"
+#include "MantidKernel/NexusDescriptor.h"
+#include <string>
+
+namespace Mantid {
+namespace API {
+class Workspace;
+class MatrixWorkspace;
+} // namespace API
+namespace NeXus {
+class NXEntry;
+}
+namespace DataHandling {
+/// Layout information relating to detector-spectra mappings
+enum class InstrumentLayout { Mantid, NexusFormat, NotRecognised };
+
+/** LoadNexusProcessed2 : Second variation of LoadNexusProcess, built to handle
+ * ESS file specifics in addition to existing behaviour for standard Mantid
+ * Processed files.
+ *
+ * The majority of the implementation consists of function overrides for
+ * specific virtual hooks make in the base Algorithm LoadNexusProcessed
+ */
+class MANTID_DATAHANDLING_DLL LoadNexusProcessed2 : public LoadNexusProcessed {
+public:
+  const std::string name() const override;
+  int version() const override;
+  int confidence(Kernel::NexusDescriptor &descriptor) const override;
+
+private:
+  void readSpectraToDetectorMapping(Mantid::NeXus::NXEntry &mtd_entry,
+                                    Mantid::API::MatrixWorkspace &ws) override;
+  bool loadNexusGeometry(Mantid::API::Workspace &ws,
+                         const int nWorkspaceEntries, Kernel::Logger &logger,
+                         const std::string &filename) override;
+  /// Extract mapping information where it is build across NXDetectors
+  void extractMappingInfoNew(Mantid::NeXus::NXEntry &mtd_entry);
+  /// Load nexus geometry and apply to workspace
+  /// Local caches
+  InstrumentLayout m_instrumentLayout = InstrumentLayout::Mantid;
+  std::vector<Indexing::SpectrumNumber> m_spectrumNumbers;
+  std::vector<Mantid::detid_t> m_detectorIds;
+};
+
+} // namespace DataHandling
+} // namespace Mantid
+
+#endif /* MANTID_DATAHANDLING_LOADNEXUSPROCESSED2_H_ */
diff --git a/Framework/DataHandling/inc/MantidDataHandling/SaveNexusESS.h b/Framework/DataHandling/inc/MantidDataHandling/SaveNexusESS.h
new file mode 100644
index 0000000000000000000000000000000000000000..fb52db78c9473fda260eac33d670f2b6efaaab92
--- /dev/null
+++ b/Framework/DataHandling/inc/MantidDataHandling/SaveNexusESS.h
@@ -0,0 +1,46 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_DATAHANDLING_SAVENEXUSESS_H_
+#define MANTID_DATAHANDLING_SAVENEXUSESS_H_
+
+#include "MantidAPI/Algorithm.h"
+#include "MantidDataHandling/DllConfig.h"
+#include "MantidDataHandling/SaveNexusProcessed.h"
+
+namespace Mantid {
+namespace DataHandling {
+
+/** SaveNexusESS : Save algorithm to save a NeXus organised hdf5 file containing
+ * data and geometry from reduced experiment for use at European Spallation
+ * Source.
+ *
+ * Uses Template Method pattern to reuse as much as possible from base
+ * SaveNexusProcessed.
+ */
+class MANTID_DATAHANDLING_DLL SaveNexusESS
+    : public Mantid::DataHandling::SaveNexusProcessed {
+public:
+  const std::string name() const override;
+  int version() const override;
+  const std::string category() const override;
+  const std::string summary() const override;
+
+protected:
+  bool processGroups() override;
+
+private:
+  void saveNexusGeometry(const Mantid::API::MatrixWorkspace &ws,
+                         const std::string &filename);
+  virtual bool saveLegacyInstrument() override;
+  void init() override;
+  void exec() override;
+};
+
+} // namespace DataHandling
+} // namespace Mantid
+
+#endif /* MANTID_DATAHANDLING_SAVENEXUSESS_H_ */
diff --git a/Framework/DataHandling/inc/MantidDataHandling/SaveNexusProcessed.h b/Framework/DataHandling/inc/MantidDataHandling/SaveNexusProcessed.h
index 936c86bf3c24546038cacf9f6e46e39762410cd1..8f769f01186a2d70ecd9f6afeb0f9465962e82d2 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/SaveNexusProcessed.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/SaveNexusProcessed.h
@@ -63,16 +63,18 @@ public:
       const std::vector<int> &wsIndices,
       const ::NeXus::NXcompression compression = ::NeXus::LZW) const;
 
+  virtual bool saveLegacyInstrument() { return true; }
+
 protected:
   /// Override process groups
   bool processGroups() override;
 
-private:
   /// Overwrites Algorithm method.
   void init() override;
   /// Overwrites Algorithm method
   void exec() override;
 
+private:
   void getWSIndexList(std::vector<int> &indices,
                       Mantid::API::MatrixWorkspace_const_sptr matrixWorkspace);
 
@@ -87,7 +89,6 @@ private:
   void setOtherProperties(IAlgorithm *alg, const std::string &propertyName,
                           const std::string &propertyValue,
                           int perioidNum) override;
-  /// execute the algorithm.
   void doExec(Mantid::API::Workspace_sptr inputWorkspace,
               boost::shared_ptr<Mantid::NeXus::NexusFileIO> &nexusFile,
               const bool keepFile = false,
diff --git a/Framework/DataHandling/inc/MantidDataHandling/SetSample.h b/Framework/DataHandling/inc/MantidDataHandling/SetSample.h
index dfe9085670a3f70d350b9acec87b8077cc9d504b..34e50943abf6cec0f437ea617414be4a88238b9a 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/SetSample.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/SetSample.h
@@ -16,6 +16,9 @@ namespace Geometry {
 class ReferenceFrame;
 class SampleEnvironment;
 } // namespace Geometry
+namespace API {
+class ExperimentInfo;
+} // namespace API
 namespace DataHandling {
 
 /**
@@ -37,9 +40,9 @@ private:
   void exec() override final;
 
   const Geometry::SampleEnvironment *
-  setSampleEnvironment(API::MatrixWorkspace_sptr &workspace,
+  setSampleEnvironment(API::ExperimentInfo &experiment,
                        const Kernel::PropertyManager_const_sptr &args);
-  void setSampleShape(API::MatrixWorkspace_sptr &workspace,
+  void setSampleShape(API::ExperimentInfo &experiment,
                       const Kernel::PropertyManager_const_sptr &args,
                       const Geometry::SampleEnvironment *sampleEnv);
   std::string
@@ -52,10 +55,8 @@ private:
                                     const Geometry::ReferenceFrame &refFrame,
                                     bool hollow) const;
 
-  void runSetSampleShape(API::MatrixWorkspace_sptr &workspace,
-                         const std::string &xml);
   void runChildAlgorithm(const std::string &name,
-                         API::MatrixWorkspace_sptr &workspace,
+                         API::Workspace_sptr &workspace,
                          const Kernel::PropertyManager &args);
 };
 
diff --git a/Framework/DataHandling/src/CreateSampleShape.cpp b/Framework/DataHandling/src/CreateSampleShape.cpp
index f6499c0b933b9e9e62905ca6ff47f7d8e20aec41..77b16b6247fe700831dd120bdfb7b1ea821a8093 100644
--- a/Framework/DataHandling/src/CreateSampleShape.cpp
+++ b/Framework/DataHandling/src/CreateSampleShape.cpp
@@ -24,36 +24,21 @@ using namespace Mantid::DataHandling;
 using namespace Mantid::API;
 
 /**
- * Initialize the algorithm
- */
-void CreateSampleShape::init() {
-  using namespace Mantid::Kernel;
-  declareProperty(std::make_unique<WorkspaceProperty<MatrixWorkspace>>(
-                      "InputWorkspace", "", Direction::Input),
-                  "The workspace with which to associate the sample ");
-  declareProperty("ShapeXML", "",
-                  boost::make_shared<MandatoryValidator<std::string>>(),
-                  "The XML that describes the shape");
-}
-
-/**
- * Execute the algorithm
+ * @brief Set the shape via an XML string on the given experiment
+ * @param expt A reference to the experiment holding the sample object
+ * @param shapeXML XML defining the object's shape
  */
-void CreateSampleShape::exec() {
-  // Get the input workspace
-  MatrixWorkspace_sptr workspace = getProperty("InputWorkspace");
-  // Get the XML definition
-  std::string shapeXML = getProperty("ShapeXML");
-
+void CreateSampleShape::setSampleShape(API::ExperimentInfo &expt,
+                                       const std::string &shapeXML) {
   Geometry::ShapeFactory sFactory;
   // Create the object
   auto shape = sFactory.createShape(shapeXML);
   // Check it's valid and attach it to the workspace sample but preserve any
   // material
   if (shape->hasValidShape()) {
-    const auto mat = workspace->sample().getMaterial();
+    const auto mat = expt.sample().getMaterial();
     shape->setMaterial(mat);
-    workspace->mutableSample().setShape(shape);
+    expt.mutableSample().setShape(shape);
   } else {
     std::ostringstream msg;
     msg << "Object has invalid shape.";
@@ -64,6 +49,28 @@ void CreateSampleShape::exec() {
     }
     throw std::runtime_error(msg.str());
   }
+}
+
+/**
+ * Initialize the algorithm
+ */
+void CreateSampleShape::init() {
+  using namespace Mantid::Kernel;
+  declareProperty(std::make_unique<WorkspaceProperty<MatrixWorkspace>>(
+                      "InputWorkspace", "", Direction::Input),
+                  "The workspace with which to associate the sample ");
+  declareProperty("ShapeXML", "",
+                  boost::make_shared<MandatoryValidator<std::string>>(),
+                  "The XML that describes the shape");
+}
+
+/**
+ * Execute the algorithm
+ */
+void CreateSampleShape::exec() {
+  // Get the input workspace
+  MatrixWorkspace_sptr workspace = getProperty("InputWorkspace");
+  setSampleShape(*workspace, getProperty("ShapeXML"));
   // Done!
   progress(1);
 }
diff --git a/Framework/DataHandling/src/FilterEventsByLogValuePreNexus.cpp b/Framework/DataHandling/src/FilterEventsByLogValuePreNexus.cpp
index 5b49cd974cdc3feae4a9dfa79b478dc56a21f51e..b6fadad0a208a8f63b1a21b9aaf85940144ee650 100644
--- a/Framework/DataHandling/src/FilterEventsByLogValuePreNexus.cpp
+++ b/Framework/DataHandling/src/FilterEventsByLogValuePreNexus.cpp
@@ -2277,8 +2277,9 @@ void FilterEventsByLogValuePreNexus::loadPixelMap(const std::string &filename) {
   this->m_pixelmap = pixelmapFile.loadAllIntoVector();
 
   // Check for funky file
+  using std::placeholders::_1;
   if (std::find_if(m_pixelmap.begin(), m_pixelmap.end(),
-                   std::bind2nd(std::greater<PixelType>(), max_pid)) !=
+                   std::bind(std::greater<PixelType>(), _1, max_pid)) !=
       m_pixelmap.end()) {
     this->g_log.warning("Pixel id in mapping file was out of bounds. Loading "
                         "without mapping file");
diff --git a/Framework/DataHandling/src/LoadEventNexus.cpp b/Framework/DataHandling/src/LoadEventNexus.cpp
index fcd159d8e11e2f570ffe5c2a5772c62c7e353c81..2c770928dd7e02887d5d5e3a4984bac894e9cd7c 100644
--- a/Framework/DataHandling/src/LoadEventNexus.cpp
+++ b/Framework/DataHandling/src/LoadEventNexus.cpp
@@ -29,7 +29,6 @@
 #include "MantidKernel/VisibleWhenProperty.h"
 
 #include <H5Cpp.h>
-#include <boost/function.hpp>
 #include <boost/shared_array.hpp>
 #include <boost/shared_ptr.hpp>
 
@@ -355,9 +354,9 @@ template <>
 void LoadEventNexus::filterDuringPause<EventWorkspaceCollection_sptr>(
     EventWorkspaceCollection_sptr workspace) {
   // We provide a function pointer to the filter method of the object
-  boost::function<void(MatrixWorkspace_sptr)> func = std::bind1st(
-      std::mem_fun(&LoadEventNexus::filterDuringPause<MatrixWorkspace_sptr>),
-      this);
+  using std::placeholders::_1;
+  auto func = std::bind(
+      &LoadEventNexus::filterDuringPause<MatrixWorkspace_sptr>, this, _1);
   workspace->applyFilter(func);
 }
 
diff --git a/Framework/DataHandling/src/LoadEventPreNexus2.cpp b/Framework/DataHandling/src/LoadEventPreNexus2.cpp
index afb463bcd0ba29b489dbd2fde294effb25f11a05..37c0f6dab65095726432708be5e1ce760289d800 100644
--- a/Framework/DataHandling/src/LoadEventPreNexus2.cpp
+++ b/Framework/DataHandling/src/LoadEventPreNexus2.cpp
@@ -1236,8 +1236,9 @@ void LoadEventPreNexus2::loadPixelMap(const std::string &filename) {
   this->pixelmap = pixelmapFile.loadAllIntoVector();
 
   // Check for funky file
+  using std::placeholders::_1;
   if (std::find_if(pixelmap.begin(), pixelmap.end(),
-                   std::bind2nd(std::greater<PixelType>(), max_pid)) !=
+                   std::bind(std::greater<PixelType>(), _1, max_pid)) !=
       pixelmap.end()) {
     this->g_log.warning("Pixel id in mapping file was out of bounds. Loading "
                         "without mapping file");
diff --git a/Framework/DataHandling/src/LoadNexusLogs.cpp b/Framework/DataHandling/src/LoadNexusLogs.cpp
index e82bea4e09f6910e757d132f9d3c66bc801161ed..9162a9df98a5da6137babb001cacaaf3d2e81590 100644
--- a/Framework/DataHandling/src/LoadNexusLogs.cpp
+++ b/Framework/DataHandling/src/LoadNexusLogs.cpp
@@ -689,8 +689,9 @@ LoadNexusLogs::createTimeSeries(::NeXus::File &file,
 
   // Convert to seconds if needed
   if (time_units == "minutes") {
+    using std::placeholders::_1;
     std::transform(time_double.begin(), time_double.end(), time_double.begin(),
-                   std::bind2nd(std::multiplies<double>(), 60.0));
+                   std::bind(std::multiplies<double>(), _1, 60.0));
   }
   // Now the values: Could be a string, int or double
   file.openData("value");
diff --git a/Framework/DataHandling/src/LoadNexusProcessed.cpp b/Framework/DataHandling/src/LoadNexusProcessed.cpp
index 4e474279b7ee0ef28c557f4344ea2ff21bcdb945..d794b66ddeb7a0c867f7ec05c5053bef84522031 100644
--- a/Framework/DataHandling/src/LoadNexusProcessed.cpp
+++ b/Framework/DataHandling/src/LoadNexusProcessed.cpp
@@ -91,6 +91,13 @@ using SpectraInfo_optional = boost::optional<SpectraInfo>;
 SpectraInfo extractMappingInfo(NXEntry &mtd_entry, Logger &logger) {
   SpectraInfo spectraInfo;
   // Instrument information
+
+  if (!mtd_entry.containsGroup("instrument")) {
+    logger.information() << "No NXinstrument group called `instrument` under "
+                            "NXEntry. The workspace will not "
+                            "contain any detector information.\n";
+    return spectraInfo;
+  }
   NXInstrument inst = mtd_entry.openNXInstrument("instrument");
   if (!inst.containsGroup("detector")) {
     logger.information() << "Detector block not found. The workspace will not "
@@ -167,6 +174,7 @@ bool isMultiPeriodFile(int nWorkspaceEntries, Workspace_sptr sampleWS,
   }
   return isMultiPeriod;
 }
+
 } // namespace
 
 /// Default constructor
@@ -191,6 +199,11 @@ int LoadNexusProcessed::confidence(Kernel::NexusDescriptor &descriptor) const {
     return 0;
 }
 
+void LoadNexusProcessed::readSpectraToDetectorMapping(
+    NXEntry &mtd_entry, Mantid::API::MatrixWorkspace &ws) {
+  readInstrumentGroup(mtd_entry, ws);
+}
+
 /** Initialisation method.
  *
  */
@@ -371,140 +384,154 @@ Workspace_sptr LoadNexusProcessed::doAccelleratedMultiPeriodLoading(
  *  @throw runtime_error Thrown if algorithm cannot execute
  */
 void LoadNexusProcessed::exec() {
-  progress(0, "Opening file...");
-
-  // Throws an approriate exception if there is a problem with file access
-  NXRoot root(getPropertyValue("Filename"));
-
-  // "Open" the same file but with the C++ interface
-  m_cppFile = new ::NeXus::File(root.m_fileID);
-
-  // Find out how many first level entries there are
-  // Cast down to int as another property later on is an int
-  auto nWorkspaceEntries = static_cast<int>((root.groups().size()));
-
-  // Check for an entry number property
-  int entrynumber = getProperty("EntryNumber");
-  Property const *const entryNumberProperty = this->getProperty("EntryNumber");
-  bool bDefaultEntryNumber = entryNumberProperty->isDefault();
-
-  if (!bDefaultEntryNumber && entrynumber > nWorkspaceEntries) {
-    g_log.error() << "Invalid entry number specified. File only contains "
-                  << nWorkspaceEntries << " entries.\n";
-    throw std::invalid_argument("Invalid entry number specified.");
-  }
-
-  const std::string basename = "mantid_workspace_";
-
-  std::ostringstream os;
-  if (bDefaultEntryNumber) {
-    // Set the entry number to 1 if not provided.
-    entrynumber = 1;
-  }
-  os << basename << entrynumber;
-  const std::string targetEntryName = os.str();
 
-  // Take the first real workspace obtainable. We need it even if loading
-  // groups.
-  API::Workspace_sptr tempWS = loadEntry(root, targetEntryName, 0, 1);
-
-  if (nWorkspaceEntries == 1 || !bDefaultEntryNumber) {
-    // We have what we need.
-    setProperty("OutputWorkspace", tempWS);
-  } else {
-    // We already know that this is a group workspace. Is it a true multiperiod
-    // workspace.
-    const bool bFastMultiPeriod = this->getProperty("FastMultiPeriod");
-    const bool bIsMultiPeriod =
-        isMultiPeriodFile(nWorkspaceEntries, tempWS, g_log);
-    Property *specListProp = this->getProperty("SpectrumList");
-    m_list = !specListProp->isDefault();
-
-    // Load all first level entries
-    auto wksp_group = boost::make_shared<WorkspaceGroup>();
-    // This forms the name of the group
-    std::string base_name = getPropertyValue("OutputWorkspace");
-    // First member of group should be the group itself, for some reason!
-
-    // load names of each of the workspaces. Note that if we have duplicate
-    // names then we don't select them
-    auto names =
-        extractWorkspaceNames(root, static_cast<size_t>(nWorkspaceEntries));
-
-    // remove existing workspace and replace with the one being loaded
-    bool wsExists = AnalysisDataService::Instance().doesExist(base_name);
-    if (wsExists) {
-      Algorithm_sptr alg =
-          AlgorithmManager::Instance().createUnmanaged("DeleteWorkspace");
-      alg->initialize();
-      alg->setChild(true);
-      alg->setProperty("Workspace", base_name);
-      alg->execute();
+  API::Workspace_sptr tempWS;
+  int nWorkspaceEntries = 0;
+  // Start scoped block
+  {
+    progress(0, "Opening file...");
+
+    // Throws an approriate exception if there is a problem with file access
+    const std::string filename = getPropertyValue("Filename");
+    NXRoot root(filename);
+
+    // "Open" the same file but with the C++ interface
+    m_cppFile = new ::NeXus::File(root.m_fileID);
+
+    // Find out how many first level entries there are
+    // Cast down to int as another property later on is an int
+    nWorkspaceEntries = static_cast<int>((root.groups().size()));
+
+    // Check for an entry number property
+    int entrynumber = getProperty("EntryNumber");
+    Property const *const entryNumberProperty =
+        this->getProperty("EntryNumber");
+    bool bDefaultEntryNumber = entryNumberProperty->isDefault();
+
+    if (!bDefaultEntryNumber && entrynumber > nWorkspaceEntries) {
+      g_log.error() << "Invalid entry number specified. File only contains "
+                    << nWorkspaceEntries << " entries.\n";
+      throw std::invalid_argument("Invalid entry number specified.");
     }
 
-    base_name += "_";
-    const std::string prop_name = "OutputWorkspace_";
-
-    MatrixWorkspace_sptr tempMatrixWorkspace =
-        boost::dynamic_pointer_cast<Workspace2D>(tempWS);
-    bool bAccelleratedMultiPeriodLoading = false;
-    if (tempMatrixWorkspace) {
-      // We only accelerate for simple scenarios for now. Spectrum lists are too
-      // complicated to bother with.
-      bAccelleratedMultiPeriodLoading =
-          bIsMultiPeriod && bFastMultiPeriod && !m_list;
-      // Strip out any loaded logs. That way we don't pay for copying that
-      // information around.
-      tempMatrixWorkspace->mutableRun().clearLogs();
-    }
+    const std::string basename = "mantid_workspace_";
 
-    if (bAccelleratedMultiPeriodLoading) {
-      g_log.information("Accelerated multiperiod loading");
-    } else {
-      g_log.information("Individual group loading");
+    std::ostringstream os;
+    if (bDefaultEntryNumber) {
+      // Set the entry number to 1 if not provided.
+      entrynumber = 1;
     }
+    os << basename << entrynumber;
+    const std::string targetEntryName = os.str();
 
-    for (int p = 1; p <= nWorkspaceEntries; ++p) {
-      const auto indexStr = std::to_string(p);
+    // Take the first real workspace obtainable. We need it even if loading
+    // groups.
+    tempWS = loadEntry(root, targetEntryName, 0, 1);
 
-      // decide what the workspace should be called
-      std::string wsName = buildWorkspaceName(names[p], base_name, p);
+    if (nWorkspaceEntries == 1 || !bDefaultEntryNumber) {
+      // We have what we need.
+      setProperty("OutputWorkspace", tempWS);
+    } else {
+      // We already know that this is a group workspace. Is it a true
+      // multiperiod workspace.
+      const bool bFastMultiPeriod = this->getProperty("FastMultiPeriod");
+      const bool bIsMultiPeriod =
+          isMultiPeriodFile(nWorkspaceEntries, tempWS, g_log);
+      Property *specListProp = this->getProperty("SpectrumList");
+      m_list = !specListProp->isDefault();
+
+      // Load all first level entries
+      auto wksp_group = boost::make_shared<WorkspaceGroup>();
+      // This forms the name of the group
+      std::string base_name = getPropertyValue("OutputWorkspace");
+      // First member of group should be the group itself, for some reason!
+
+      // load names of each of the workspaces. Note that if we have duplicate
+      // names then we don't select them
+      auto names =
+          extractWorkspaceNames(root, static_cast<size_t>(nWorkspaceEntries));
+
+      // remove existing workspace and replace with the one being loaded
+      bool wsExists = AnalysisDataService::Instance().doesExist(base_name);
+      if (wsExists) {
+        Algorithm_sptr alg =
+            AlgorithmManager::Instance().createUnmanaged("DeleteWorkspace");
+        alg->initialize();
+        alg->setChild(true);
+        alg->setProperty("Workspace", base_name);
+        alg->execute();
+      }
 
-      Workspace_sptr local_workspace;
+      base_name += "_";
+      const std::string prop_name = "OutputWorkspace_";
+
+      MatrixWorkspace_sptr tempMatrixWorkspace =
+          boost::dynamic_pointer_cast<Workspace2D>(tempWS);
+      bool bAccelleratedMultiPeriodLoading = false;
+      if (tempMatrixWorkspace) {
+        // We only accelerate for simple scenarios for now. Spectrum lists are
+        // too complicated to bother with.
+        bAccelleratedMultiPeriodLoading =
+            bIsMultiPeriod && bFastMultiPeriod && !m_list;
+        // Strip out any loaded logs. That way we don't pay for copying that
+        // information around.
+        tempMatrixWorkspace->mutableRun().clearLogs();
+      }
 
-      /*
-      For multiperiod workspaces we can accelerate the loading by making
-      resonable assumptions about the differences between the workspaces
-      Only Y, E and log data entries should vary. Therefore we can clone our
-      temp workspace, and overwrite those things we are interested in.
-      */
       if (bAccelleratedMultiPeriodLoading) {
-        local_workspace = doAccelleratedMultiPeriodLoading(
-            root, basename + indexStr, tempMatrixWorkspace, nWorkspaceEntries,
-            p);
-      } else // Fall-back for generic loading
-      {
-        const auto nWorkspaceEntries_d = static_cast<double>(nWorkspaceEntries);
-        local_workspace =
-            loadEntry(root, basename + indexStr,
-                      static_cast<double>(p - 1) / nWorkspaceEntries_d,
-                      1. / nWorkspaceEntries_d);
+        g_log.information("Accelerated multiperiod loading");
+      } else {
+        g_log.information("Individual group loading");
       }
 
-      declareProperty(std::make_unique<WorkspaceProperty<API::Workspace>>(
-          prop_name + indexStr, wsName, Direction::Output));
+      for (int p = 1; p <= nWorkspaceEntries; ++p) {
+        const auto indexStr = std::to_string(p);
+
+        // decide what the workspace should be called
+        std::string wsName = buildWorkspaceName(names[p], base_name, p);
+
+        Workspace_sptr local_workspace;
+
+        /*
+        For multiperiod workspaces we can accelerate the loading by making
+        resonable assumptions about the differences between the workspaces
+        Only Y, E and log data entries should vary. Therefore we can clone our
+        temp workspace, and overwrite those things we are interested in.
+        */
+        if (bAccelleratedMultiPeriodLoading) {
+          local_workspace = doAccelleratedMultiPeriodLoading(
+              root, basename + indexStr, tempMatrixWorkspace, nWorkspaceEntries,
+              p);
+        } else // Fall-back for generic loading
+        {
+          const auto nWorkspaceEntries_d =
+              static_cast<double>(nWorkspaceEntries);
+          local_workspace =
+              loadEntry(root, basename + indexStr,
+                        static_cast<double>(p - 1) / nWorkspaceEntries_d,
+                        1. / nWorkspaceEntries_d);
+        }
+
+        declareProperty(std::make_unique<WorkspaceProperty<API::Workspace>>(
+            prop_name + indexStr, wsName, Direction::Output));
+
+        wksp_group->addWorkspace(local_workspace);
+        setProperty(prop_name + indexStr, local_workspace);
+      }
 
-      wksp_group->addWorkspace(local_workspace);
-      setProperty(prop_name + indexStr, local_workspace);
+      // The group is the root property value
+      setProperty("OutputWorkspace",
+                  boost::static_pointer_cast<Workspace>(wksp_group));
     }
 
-    // The group is the root property value
-    setProperty("OutputWorkspace",
-                boost::static_pointer_cast<Workspace>(wksp_group));
-  }
+    root.close();
+  } // All file resources should be scoped to here. All previous file handles
+  // must be cleared to release locks
+  loadNexusGeometry(*tempWS, nWorkspaceEntries, g_log,
+                    std::string(getProperty("Filename")));
 
   m_axis1vals.clear();
-}
+} // namespace DataHandling
 
 /**
  * Decides what to call a child of a group workspace.
@@ -1581,14 +1608,17 @@ API::Workspace_sptr LoadNexusProcessed::loadEntry(NXRoot &root,
              "Reading the parameter maps...");
     local_workspace->readParameterMap(parameterStr);
   } catch (std::exception &e) {
+    // TODO. For workspaces saved via SaveNexusESS, these warnings are not
+    // relevant. Unfortunately we need to close all file handles before we can
+    // attempt loading the new way see loadNexusGeometry function . A better
+    // solution should be found
     g_log.warning("Error loading Instrument section of nxs file");
     g_log.warning(e.what());
     g_log.warning("Try running LoadInstrument Algorithm on the Workspace to "
                   "update the geometry");
   }
 
-  // Now assign the spectra-detector map
-  readInstrumentGroup(mtd_entry, local_workspace);
+  readSpectraToDetectorMapping(mtd_entry, *local_workspace);
 
   if (!local_workspace->getAxis(1)
            ->isSpectra()) { // If not a spectra axis, load the axis data into
@@ -1621,14 +1651,14 @@ API::Workspace_sptr LoadNexusProcessed::loadEntry(NXRoot &root,
  * @param local_workspace :: The workspace to attach the instrument
  */
 void LoadNexusProcessed::readInstrumentGroup(
-    NXEntry &mtd_entry, API::MatrixWorkspace_sptr local_workspace) {
+    NXEntry &mtd_entry, API::MatrixWorkspace &local_workspace) {
   // Get spectrum information for the current entry.
 
   SpectraInfo spectraInfo = extractMappingInfo(mtd_entry, this->g_log);
 
   // Now build the spectra list
   int index = 0;
-  bool haveSpectraAxis = local_workspace->getAxis(1)->isSpectra();
+  bool haveSpectraAxis = local_workspace.getAxis(1)->isSpectra();
 
   for (int i = 1; i <= spectraInfo.nSpectra; ++i) {
     int spectrum(-1);
@@ -1649,7 +1679,7 @@ void LoadNexusProcessed::readInstrumentGroup(
     if ((i >= m_spec_min && i < m_spec_max) ||
         (m_list && find(m_spec_list.begin(), m_spec_list.end(), i) !=
                        m_spec_list.end())) {
-      auto &spec = local_workspace->getSpectrum(index);
+      auto &spec = local_workspace.getSpectrum(index);
       spec.setSpectrumNo(spectrum);
       ++index;
 
diff --git a/Framework/DataHandling/src/LoadNexusProcessed2.cpp b/Framework/DataHandling/src/LoadNexusProcessed2.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fa61dd6f4118dcd2db4c0f379ca7b4dd55dcff69
--- /dev/null
+++ b/Framework/DataHandling/src/LoadNexusProcessed2.cpp
@@ -0,0 +1,217 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+
+#include "MantidDataHandling/LoadNexusProcessed2.h"
+#include "MantidAPI/MatrixWorkspace.h"
+#include "MantidAPI/RegisterFileLoader.h"
+#include "MantidAPI/Workspace.h"
+#include "MantidGeometry/Instrument.h"
+#include "MantidGeometry/Instrument/DetectorInfo.h"
+#include "MantidIndexing/IndexInfo.h"
+#include "MantidNexus/NexusClasses.h"
+#include "MantidNexusGeometry/AbstractLogger.h"
+#include "MantidNexusGeometry/NexusGeometryParser.h"
+#include "MantidTypes/SpectrumDefinition.h"
+
+#include <H5Cpp.h>
+namespace Mantid {
+namespace DataHandling {
+using Mantid::API::WorkspaceProperty;
+using Mantid::Kernel::Direction;
+
+// Register the algorithm into the AlgorithmFactory
+DECLARE_NEXUS_FILELOADER_ALGORITHM(LoadNexusProcessed2)
+//----------------------------------------------------------------------------------------------
+
+namespace {
+template <typename T>
+int countEntriesOfType(const T &entry, const std::string &nxClass) {
+  int count = 0;
+  for (const auto &group : entry.groups()) {
+    if (group.nxclass == nxClass)
+      ++count;
+  }
+  return count;
+}
+
+template <typename T>
+std::vector<Mantid::NeXus::NXClassInfo>
+findEntriesOfType(const T &entry, const std::string &nxClass) {
+  std::vector<Mantid::NeXus::NXClassInfo> result;
+  for (const auto &group : entry.groups()) {
+    if (group.nxclass == nxClass)
+      result.push_back(group);
+  }
+  return result;
+}
+/**
+ * Determine the format/layout of the instrument block. We use this to
+ * distinguish between the ESS saving schemes and the Mantid processed nexus
+ * schemes
+ * @param entry
+ * @return
+ */
+InstrumentLayout instrumentFormat(Mantid::NeXus::NXEntry &entry) {
+  auto result = InstrumentLayout::NotRecognised;
+  const auto instrumentsCount = countEntriesOfType(entry, "NXinstrument");
+  if (instrumentsCount == 1) {
+    // Can now assume nexus format
+    result = InstrumentLayout::NexusFormat;
+
+    if (entry.containsGroup("instrument")) {
+      auto instr = entry.openNXInstrument("instrument");
+      if (instr.containsGroup("detector") ||
+          (instr.containsGroup("physical_detectors") &&
+           instr.containsGroup("physical_monitors"))) {
+        result = InstrumentLayout::Mantid; // 1 nxinstrument called instrument,
+      }
+      instr.close();
+    }
+    entry.close();
+  }
+  return result;
+}
+
+} // namespace
+
+/// Algorithms name for identification. @see Algorithm::name
+const std::string LoadNexusProcessed2::name() const {
+  return "LoadNexusProcessed";
+}
+
+/// Algorithm's version for identification. @see Algorithm::version
+int LoadNexusProcessed2::version() const { return 2; }
+
+void LoadNexusProcessed2::readSpectraToDetectorMapping(
+    Mantid::NeXus::NXEntry &mtd_entry, Mantid::API::MatrixWorkspace &ws) {
+
+  m_instrumentLayout = instrumentFormat(mtd_entry);
+  if (m_instrumentLayout == InstrumentLayout::Mantid) {
+    // Now assign the spectra-detector map
+    readInstrumentGroup(mtd_entry, ws);
+  } else if (m_instrumentLayout == InstrumentLayout::NexusFormat) {
+    extractMappingInfoNew(mtd_entry);
+  } else {
+    g_log.information()
+        << "Instrument layout not recognised. Spectra mappings not loaded.";
+  }
+}
+void LoadNexusProcessed2::extractMappingInfoNew(
+    Mantid::NeXus::NXEntry &mtd_entry) {
+  using namespace Mantid::NeXus;
+  auto result = findEntriesOfType(mtd_entry, "NXinstrument");
+  if (result.size() != 1) {
+    g_log.warning("We are expecting a single NXinstrument. No mappings loaded");
+  }
+  auto inst = mtd_entry.openNXInstrument(result[0].nxname);
+
+  auto &spectrumNumbers = m_spectrumNumbers;
+  auto &detectorIds = m_detectorIds;
+  for (const auto &group : inst.groups()) {
+    if (group.nxclass == "NXdetector" || group.nxclass == "NXmonitor") {
+      NXDetector detgroup = inst.openNXDetector(group.nxname);
+
+      NXInt spectra_block = detgroup.openNXInt("spectra");
+      spectra_block.load();
+      const size_t nSpecEntries = spectra_block.dim0();
+      auto data = spectra_block.sharedBuffer();
+      size_t currentSize = spectrumNumbers.size();
+      spectrumNumbers.resize(currentSize + nSpecEntries, 0);
+      // Append spectrum numbers
+      for (size_t i = 0; i < nSpecEntries; ++i) {
+        spectrumNumbers[i + currentSize] = data[i];
+      }
+
+      NXInt det_index = detgroup.openNXInt("detector_list");
+      det_index.load();
+      size_t nDetEntries = det_index.dim0();
+
+      // TODO currently hard-coded for 1:1 mapping need to go via
+      // detector_indexes to fix
+      if (nSpecEntries != nDetEntries) {
+        throw std::runtime_error("Nexus mappings only support 1:1 at present");
+      }
+      currentSize = detectorIds.size();
+      data = det_index.sharedBuffer();
+      detectorIds.resize(currentSize + nDetEntries, 0);
+      for (size_t i = 0; i < nDetEntries; ++i) {
+        detectorIds[i + currentSize] = data[i];
+      }
+      detgroup.close();
+    }
+  }
+  inst.close();
+}
+/**
+ * Attempt to load nexus geometry. Should fail without exception if not
+ * possible.
+ *
+ * Caveats are:
+ * 1. Only works for input files where there is a single NXEntry. Does nothing
+ * otherwise.
+ * 2. Is only applied after attempted instrument loading in the legacy fashion
+ * that happens as part of loadEntry. So you will still get warning+error
+ * messages from that even if this succeeds
+ *
+ * @param ws : Input workspace onto which instrument will get attached
+ * @param nWorkspaceEntries : number of entries
+ * @param logger : to write to
+ * @param filename : filename to load from.
+ * @return true if successful
+ */
+bool LoadNexusProcessed2::loadNexusGeometry(Mantid::API::Workspace &ws,
+                                            const int nWorkspaceEntries,
+                                            Kernel::Logger &logger,
+                                            const std::string &filename) {
+  if (m_instrumentLayout == InstrumentLayout::NexusFormat &&
+      nWorkspaceEntries == 1) {
+    if (auto *matrixWs = dynamic_cast<Mantid::API::MatrixWorkspace *>(&ws)) {
+      try {
+        using namespace Mantid::NexusGeometry;
+        auto instrument = NexusGeometry::NexusGeometryParser::createInstrument(
+            filename, NexusGeometry::makeLogger(&logger));
+        matrixWs->setInstrument(
+            Geometry::Instrument_const_sptr(std::move(instrument)));
+
+        auto &detInfo = matrixWs->detectorInfo();
+        if (m_detectorIds.size() != detInfo.size()) {
+          logger.warning("New style mappings will not be loaded. Detector "
+                         "mappings do not match number of detectors in "
+                         "geometry");
+          return false;
+        }
+        Indexing::IndexInfo info(m_spectrumNumbers);
+        std::vector<SpectrumDefinition> definitions;
+        definitions.reserve(m_detectorIds.size());
+        for (const auto &id : m_detectorIds) {
+          // Assumes 1:1 mapping
+          definitions.push_back(SpectrumDefinition{detInfo.indexOf(id)});
+        }
+        info.setSpectrumDefinitions(definitions);
+        matrixWs->setIndexInfo(info);
+
+        return true;
+      } catch (std::exception &e) {
+        logger.warning(e.what());
+      } catch (H5::Exception &e) {
+        logger.warning(e.getDetailMsg());
+      }
+    }
+  }
+  return false;
+}
+
+int LoadNexusProcessed2::confidence(Kernel::NexusDescriptor &descriptor) const {
+  if (descriptor.pathExists("/mantid_workspace_1"))
+    return LoadNexusProcessed::confidence(descriptor) +
+           1; // incrementally better than v1.
+  else
+    return 0;
+}
+
+} // namespace DataHandling
+} // namespace Mantid
diff --git a/Framework/DataHandling/src/LoadRawHelper.cpp b/Framework/DataHandling/src/LoadRawHelper.cpp
index 56fe99c6a685a7de6807ddd682a969787d331c0c..47f300714ae38e28165cb2ac2393e9a0abed3431 100644
--- a/Framework/DataHandling/src/LoadRawHelper.cpp
+++ b/Framework/DataHandling/src/LoadRawHelper.cpp
@@ -510,9 +510,10 @@ LoadRawHelper::getTimeChannels(const int64_t &regimes,
       g_log.debug() << "Time regime " << i + 1 << " shifted by " << shift
                     << " microseconds\n";
       // Add on the shift for this vector
+      using std::placeholders::_1;
       std::transform(channelsVec->begin(), channelsVec->end(),
                      channelsVec->begin(),
-                     std::bind2nd(std::plus<double>(), shift));
+                     std::bind(std::plus<double>(), _1, shift));
       timeChannelsVec.push_back(channelsVec);
     }
     // In this case, also need to populate the map of spectrum-regime
diff --git a/Framework/DataHandling/src/SaveCanSAS1D.cpp b/Framework/DataHandling/src/SaveCanSAS1D.cpp
index b79e350b2e27f395d538f71c534a5f42080ce748..380a0ed53132a46a0b872ac0d4ba0f670441f735 100644
--- a/Framework/DataHandling/src/SaveCanSAS1D.cpp
+++ b/Framework/DataHandling/src/SaveCanSAS1D.cpp
@@ -560,8 +560,9 @@ void SaveCanSAS1D::createSASDetectorElement(std::string &sasDet) {
   }
 
   std::list<std::string> detList;
+  using std::placeholders::_1;
   boost::algorithm::split(detList, detectorNames,
-                          std::bind2nd(std::equal_to<char>(), ','));
+                          std::bind(std::equal_to<char>(), _1, ','));
   for (auto detectorName : detList) {
     boost::algorithm::trim(detectorName);
 
diff --git a/Framework/DataHandling/src/SaveISISNexus.cpp b/Framework/DataHandling/src/SaveISISNexus.cpp
index 277f7782ab3e612e8bd7a6d18ed3ab8e0ac942b5..cec7fefa984e155f9d157cad88c602dbc702cfbc 100644
--- a/Framework/DataHandling/src/SaveISISNexus.cpp
+++ b/Framework/DataHandling/src/SaveISISNexus.cpp
@@ -817,8 +817,9 @@ void SaveISISNexus::runlog() {
   fil.close();
 
   run_status_vec.resize(time_vec.size());
+  using std::placeholders::_1;
   std::transform(is_running_vec.begin(), is_running_vec.end(),
-                 run_status_vec.begin(), std::bind2nd(std::plus<int>(), 1));
+                 run_status_vec.begin(), std::bind(std::plus<int>(), _1, 1));
 
   NXmakegroup(handle, "runlog", "IXrunlog");
   NXopengroup(handle, "runlog", "IXrunlog");
diff --git a/Framework/DataHandling/src/SaveNexusESS.cpp b/Framework/DataHandling/src/SaveNexusESS.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6cdfabbae886a11842289020627bf8f75546a5d2
--- /dev/null
+++ b/Framework/DataHandling/src/SaveNexusESS.cpp
@@ -0,0 +1,101 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+
+#include "MantidDataHandling/SaveNexusESS.h"
+#include "MantidNexusGeometry/NexusGeometrySave.h"
+#include <H5Cpp.h>
+
+namespace Mantid {
+namespace DataHandling {
+using Mantid::API::WorkspaceProperty;
+using Mantid::Kernel::Direction;
+
+// Register the algorithm into the AlgorithmFactory
+DECLARE_ALGORITHM(SaveNexusESS)
+
+//----------------------------------------------------------------------------------------------
+
+/// Algorithms name for identification. @see Algorithm::name
+const std::string SaveNexusESS::name() const { return "SaveNexusESS"; }
+
+/// Algorithm's version for identification. @see Algorithm::version
+int SaveNexusESS::version() const { return 1; }
+
+/// Algorithm's category for identification. @see Algorithm::category
+const std::string SaveNexusESS::category() const {
+  return "DataHandling\\Nexus";
+}
+
+/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
+const std::string SaveNexusESS::summary() const {
+  return "Saves intermediate, also known as 'processed' nexus file with data "
+         "and geometry";
+}
+
+/**
+ * @brief SaveNexusESS::processGroups
+ * @return
+ */
+bool SaveNexusESS::processGroups() {
+  throw std::invalid_argument(
+      "SaveNexusESS does not currently support operations on groups");
+}
+
+void SaveNexusESS::saveNexusGeometry(const Mantid::API::MatrixWorkspace &ws,
+                                     const std::string &filename) {
+
+  try {
+    NexusGeometry::LogAdapter<Kernel::Logger> adapter(&g_log);
+    NexusGeometry::NexusGeometrySave::saveInstrument(
+        ws, filename, "mantid_workspace_1", adapter, true);
+  } catch (std::exception &e) {
+    g_log.error(std::string(e.what()) +
+                " Nexus Geometry may be absent or incomplete "
+                "from processed Nexus file");
+  } catch (H5::Exception &ex) {
+    g_log.error(ex.getDetailMsg() +
+                " Nexus Geometry may be absent or incomplete "
+                "from processed Nexus file");
+  }
+}
+
+bool SaveNexusESS::saveLegacyInstrument() {
+  /*A hard No on this one. Mantids's current NXDetector, NXMonitor ... types do
+   * not have information needed for loading and just cause down-stream
+   * problems. Best not to save them in the first place.*/
+  return false;
+}
+
+//----------------------------------------------------------------------------------------------
+/** Initialize the algorithm's properties.
+ */
+void SaveNexusESS::init() {
+  // Take same properties as base.
+  SaveNexusProcessed::init();
+}
+
+//----------------------------------------------------------------------------------------------
+/** Execute the algorithm.
+ */
+void SaveNexusESS::exec() {
+  // Run the base algorithm. Template method approach used to call ESS
+  // specifics.
+
+  API::Workspace_sptr ws = getProperty("InputWorkspace");
+  const std::string filename = getProperty("Filename");
+  auto matrixWs = boost::dynamic_pointer_cast<API::MatrixWorkspace>(ws);
+  if (!matrixWs)
+    throw std::runtime_error("SaveNexusESS expects a MatrixWorkspace as input");
+  SaveNexusProcessed::exec();
+
+  // Now append nexus geometry
+  saveNexusGeometry(*matrixWs, filename);
+  // Now write spectrum to detector maps;
+  return;
+}
+} // namespace DataHandling
+} // namespace Mantid
diff --git a/Framework/DataHandling/src/SaveNexusGeometry.cpp b/Framework/DataHandling/src/SaveNexusGeometry.cpp
index 440c88b799855a5553b3a22a1bf60be8d417d544..bb2b4d6c42f8eeaf9abeec11d6bb18608236b726 100644
--- a/Framework/DataHandling/src/SaveNexusGeometry.cpp
+++ b/Framework/DataHandling/src/SaveNexusGeometry.cpp
@@ -92,8 +92,9 @@ void SaveNexusGeometry::exec() {
   const auto &compInfo = ws->componentInfo();
   const auto &detInfo = ws->detectorInfo();
 
+  NexusGeometry::LogAdapter<Kernel::Logger> adapter(&g_log);
   Mantid::NexusGeometry::NexusGeometrySave::saveInstrument(
-      compInfo, detInfo, destinationFile, rootFileName);
+      compInfo, detInfo, destinationFile, rootFileName, adapter);
 }
 
 } // namespace DataHandling
diff --git a/Framework/DataHandling/src/SaveNexusProcessed.cpp b/Framework/DataHandling/src/SaveNexusProcessed.cpp
index 4ca1946aa4a5be829b6d6e35ab3e9f0036ae4577..e061fa88be10eb4b312fe80c3f304e89d533da5e 100644
--- a/Framework/DataHandling/src/SaveNexusProcessed.cpp
+++ b/Framework/DataHandling/src/SaveNexusProcessed.cpp
@@ -38,6 +38,77 @@ using optional_size_t = NeXus::NexusFileIO::optional_size_t;
 // Register the algorithm into the algorithm factory
 DECLARE_ALGORITHM(SaveNexusProcessed)
 
+namespace {
+
+/**
+ * Create containers for spectra-detector map writing
+ *
+ * Note that in-out vectors passed by ref will be dynamically resized
+ * internally. They do not need to be sized correctly prior to calling method.
+ *
+ * @param ws : input workspace
+ * @param ws_indices : workspace indices to write
+ * @param out_detector_index : detector indices to write
+ * @param out_detector_count : detector count to write
+ * @param out_detector_list : detector list to write
+ * @param numberSpec
+ * @param numberDetectors
+ * @return
+ */
+bool makeMappings(const MatrixWorkspace &ws, const std::vector<int> &ws_indices,
+                  std::vector<int32_t> &out_detector_index,
+                  std::vector<int32_t> &out_detector_count,
+                  std::vector<int32_t> &out_detector_list, int &numberSpec,
+                  size_t &numberDetectors) {
+
+  // Count the total number of detectors
+  numberDetectors = 0;
+  for (auto index : ws_indices) {
+    numberDetectors +=
+        ws.getSpectrum(static_cast<size_t>(index)).getDetectorIDs().size();
+  }
+  if (numberDetectors < 1) {
+    return false;
+  }
+  // Start the detector group
+
+  numberSpec = int(ws_indices.size());
+  // allocate space for the Nexus Muon format of spectra-detector mapping
+  // allow for writing one more than required
+  out_detector_index.resize(numberSpec + 1, 0);
+  out_detector_count.resize(numberSpec, 0);
+  out_detector_list.resize(numberDetectors, 0);
+  int id = 0;
+
+  int ndet = 0;
+  // get data from map into Nexus Muon format
+  for (int i = 0; i < numberSpec; i++) {
+    // Workspace index
+    const int si = ws_indices[i];
+    // Spectrum there
+    const auto &spectrum = ws.getSpectrum(si);
+
+    // The detectors in this spectrum
+    const auto &detectorgroup = spectrum.getDetectorIDs();
+    const auto ndet1 = static_cast<int>(detectorgroup.size());
+
+    // points to start of detector list for the next spectrum
+    out_detector_index[i + 1] = int32_t(out_detector_index[i] + ndet1);
+    out_detector_count[i] = int32_t(ndet1);
+    ndet += ndet1;
+
+    std::set<detid_t>::const_iterator it;
+    for (it = detectorgroup.begin(); it != detectorgroup.end(); ++it) {
+      out_detector_list[id++] = int32_t(*it);
+    }
+  }
+  // Cut the extra entry at the end of detector_index
+  out_detector_index.resize(numberSpec);
+  return true;
+}
+
+} // namespace
+
 /** Initialisation method.
  *
  */
@@ -240,7 +311,7 @@ void SaveNexusProcessed::doExec(
   // write instrument data, if present and writer enabled
   if (matrixWorkspace) {
     // Save the instrument names, ParameterMap, sample, run
-    matrixWorkspace->saveExperimentInfoNexus(&cppFile);
+    matrixWorkspace->saveExperimentInfoNexus(&cppFile, saveLegacyInstrument());
     prog_init.reportIncrement(1, "Writing sample and instrument");
 
     // check if all X() are in fact the same array
@@ -268,14 +339,18 @@ void SaveNexusProcessed::doExec(
           workspaceTypeGroupName.c_str(), true);
     }
 
-    cppFile.openGroup("instrument", "NXinstrument");
-    cppFile.makeGroup("detector", "NXdetector", true);
-    cppFile.putAttr("version", 1);
-    saveSpectraDetectorMapNexus(*matrixWorkspace, &cppFile, indices,
-                                ::NeXus::LZW);
-    saveSpectrumNumbersNexus(*matrixWorkspace, &cppFile, indices, ::NeXus::LZW);
-    cppFile.closeGroup();
-    cppFile.closeGroup();
+    if (saveLegacyInstrument()) {
+      cppFile.openGroup("instrument", "NXinstrument");
+      cppFile.makeGroup("detector", "NXdetector", true);
+
+      cppFile.putAttr("version", 1);
+      saveSpectraDetectorMapNexus(*matrixWorkspace, &cppFile, indices,
+                                  ::NeXus::LZW);
+      saveSpectrumNumbersNexus(*matrixWorkspace, &cppFile, indices,
+                               ::NeXus::LZW);
+      cppFile.closeGroup();
+      cppFile.closeGroup();
+    }
 
   } // finish matrix workspace specifics
 
@@ -319,6 +394,8 @@ void SaveNexusProcessed::exec() {
 
   // Perform the execution.
   doExec(inputWorkspace, nexusFile);
+
+  // nexusFile->closeNexusFile();
 }
 
 //-------------------------------------------------------------------------------------
@@ -549,50 +626,18 @@ void SaveNexusProcessed::saveSpectraDetectorMapNexus(
     const MatrixWorkspace &ws, ::NeXus::File *file,
     const std::vector<int> &wsIndices,
     const ::NeXus::NXcompression compression) const {
-  // Count the total number of detectors
-  std::size_t nDetectors = 0;
-  for (auto index : wsIndices) {
-    nDetectors +=
-        ws.getSpectrum(static_cast<size_t>(index)).getDetectorIDs().size();
-  }
-  if (nDetectors < 1) {
-    return;
-  }
-  // Start the detector group
-
-  const auto numberSpec = int(wsIndices.size());
-  // allocate space for the Nexus Muon format of spectra-detector mapping
-  // allow for writing one more than required
-  std::vector<int32_t> detector_index(numberSpec + 1, 0);
-  std::vector<int32_t> detector_count(numberSpec, 0);
-  std::vector<int32_t> detector_list(nDetectors, 0);
-  std::vector<double> detPos(nDetectors * 3);
-  int id = 0;
-
-  int ndet = 0;
-  // get data from map into Nexus Muon format
-  for (int i = 0; i < numberSpec; i++) {
-    // Workspace index
-    const int si = wsIndices[i];
-    // Spectrum there
-    const auto &spectrum = ws.getSpectrum(si);
-
-    // The detectors in this spectrum
-    const auto &detectorgroup = spectrum.getDetectorIDs();
-    const auto ndet1 = static_cast<int>(detectorgroup.size());
 
-    // points to start of detector list for the next spectrum
-    detector_index[i + 1] = int32_t(detector_index[i] + ndet1);
-    detector_count[i] = int32_t(ndet1);
-    ndet += ndet1;
-
-    std::set<detid_t>::const_iterator it;
-    for (it = detectorgroup.begin(); it != detectorgroup.end(); ++it) {
-      detector_list[id++] = int32_t(*it);
-    }
-  }
-  // Cut the extra entry at the end of detector_index
-  detector_index.resize(numberSpec);
+  std::vector<int32_t> detector_index;
+  std::vector<int32_t> detector_count;
+  std::vector<int32_t> detector_list;
+  int numberSpec = 0;
+  size_t nDetectors = 0;
+  /*Make the mappings needed for writing to disk*/
+  const bool mappingsToWrite =
+      makeMappings(ws, wsIndices, detector_index, detector_count, detector_list,
+                   numberSpec, nDetectors);
+  if (!mappingsToWrite)
+    return;
 
   // write data as Nexus sections detector{index,count,list}
   std::vector<int> dims(1, numberSpec);
@@ -604,6 +649,7 @@ void SaveNexusProcessed::saveSpectraDetectorMapNexus(
   file->writeCompData("detector_list", detector_list, dims, compression, dims);
   // Get all the positions
   try {
+    std::vector<double> detPos(nDetectors * 3);
     Geometry::Instrument_const_sptr inst = ws.getInstrument();
     Geometry::IComponent_const_sptr sample = inst->getSample();
     if (sample) {
diff --git a/Framework/DataHandling/src/SetSample.cpp b/Framework/DataHandling/src/SetSample.cpp
index 29d59331f7bee25c29158af14ed04670254f9674..7ae9a2d7ac424821e11229e401fa2220ed4c5c0b 100644
--- a/Framework/DataHandling/src/SetSample.cpp
+++ b/Framework/DataHandling/src/SetSample.cpp
@@ -8,6 +8,7 @@
 
 #include "MantidAPI/MatrixWorkspace.h"
 #include "MantidAPI/Sample.h"
+#include "MantidDataHandling/CreateSampleShape.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidGeometry/Instrument/Goniometer.h"
 #include "MantidGeometry/Instrument/ReferenceFrame.h"
@@ -29,6 +30,8 @@
 namespace Mantid {
 namespace DataHandling {
 
+using API::ExperimentInfo;
+using API::Workspace_sptr;
 using Geometry::Goniometer;
 using Geometry::ReferenceFrame;
 using Geometry::SampleEnvironment;
@@ -221,7 +224,6 @@ const std::string SetSample::summary() const {
 std::map<std::string, std::string> SetSample::validateInputs() {
   using Kernel::PropertyManager;
   using Kernel::PropertyManager_const_sptr;
-  std::map<std::string, std::string> errors;
 
   auto existsAndNotEmptyString = [](const PropertyManager &pm,
                                     const std::string &name) {
@@ -243,6 +245,17 @@ std::map<std::string, std::string> SetSample::validateInputs() {
     return false;
   };
 
+  std::map<std::string, std::string> errors;
+  // Check workspace type has ExperimentInfo fields
+  using API::ExperimentInfo_sptr;
+  using API::Workspace_sptr;
+  Workspace_sptr inputWS = getProperty(PropertyNames::INPUT_WORKSPACE);
+  if (!boost::dynamic_pointer_cast<ExperimentInfo>(inputWS)) {
+    errors[PropertyNames::INPUT_WORKSPACE] = "InputWorkspace type invalid. "
+                                             "Expected MatrixWorkspace, "
+                                             "PeaksWorkspace.";
+  }
+
   // Validate Environment
   const PropertyManager_const_sptr environArgs =
       getProperty(PropertyNames::ENVIRONMENT);
@@ -282,12 +295,13 @@ std::map<std::string, std::string> SetSample::validateInputs() {
  * Initialize the algorithm's properties.
  */
 void SetSample::init() {
+  using API::Workspace;
   using API::WorkspaceProperty;
   using Kernel::Direction;
   using Kernel::PropertyManagerProperty;
 
   // Inputs
-  declareProperty(std::make_unique<WorkspaceProperty<>>(
+  declareProperty(std::make_unique<WorkspaceProperty<Workspace>>(
                       PropertyNames::INPUT_WORKSPACE, "", Direction::InOut),
                   "A workspace whose sample properties will be updated");
   declareProperty(std::make_unique<PropertyManagerProperty>(
@@ -307,27 +321,29 @@ void SetSample::init() {
  * Execute the algorithm.
  */
 void SetSample::exec() {
-  using API::MatrixWorkspace_sptr;
+  using API::ExperimentInfo_sptr;
   using Kernel::PropertyManager_sptr;
 
-  MatrixWorkspace_sptr workspace = getProperty(PropertyNames::INPUT_WORKSPACE);
+  Workspace_sptr workspace = getProperty(PropertyNames::INPUT_WORKSPACE);
   PropertyManager_sptr environArgs = getProperty(PropertyNames::ENVIRONMENT);
   PropertyManager_sptr geometryArgs = getProperty(PropertyNames::GEOMETRY);
   PropertyManager_sptr materialArgs = getProperty(PropertyNames::MATERIAL);
 
-  // The order here is important. Se the environment first. If this
+  // validateInputs guarantees this will be an ExperimentInfo object
+  auto experiment = boost::dynamic_pointer_cast<ExperimentInfo>(workspace);
+  // The order here is important. Set the environment first. If this
   // defines a sample geometry then we can process the Geometry flags
   // combined with this
   const SampleEnvironment *sampleEnviron(nullptr);
   if (environArgs) {
-    sampleEnviron = setSampleEnvironment(workspace, environArgs);
+    sampleEnviron = setSampleEnvironment(*experiment, environArgs);
   }
 
   double sampleVolume = 0.;
   if (geometryArgs || sampleEnviron) {
-    setSampleShape(workspace, geometryArgs, sampleEnviron);
+    setSampleShape(*experiment, geometryArgs, sampleEnviron);
     // get the volume back out to use in setting the material
-    sampleVolume = CUBIC_METRE_TO_CM * workspace->sample().getShape().volume();
+    sampleVolume = CUBIC_METRE_TO_CM * experiment->sample().getShape().volume();
   }
 
   // Finally the material arguments
@@ -348,12 +364,12 @@ void SetSample::exec() {
 
 /**
  * Set the requested sample environment on the workspace
- * @param workspace A pointer to the workspace to be affected
+ * @param exptInfo A reference to the ExperimentInfo to receive the environment
  * @param args The dictionary of flags for the environment
  * @return A pointer to the new sample environment
  */
 const Geometry::SampleEnvironment *SetSample::setSampleEnvironment(
-    API::MatrixWorkspace_sptr &workspace,
+    API::ExperimentInfo &exptInfo,
     const Kernel::PropertyManager_const_sptr &args) {
   using Geometry::SampleEnvironmentFactory;
   using Geometry::SampleEnvironmentSpecFileFinder;
@@ -364,7 +380,7 @@ const Geometry::SampleEnvironment *SetSample::setSampleEnvironment(
   // The specifications need to be qualified by the facility and instrument.
   // Check instrument for name and then lookup facility if facility
   // is unknown then set to default facility & instrument.
-  auto instrument = workspace->getInstrument();
+  auto instrument = exptInfo.getInstrument();
   const auto &instOnWS = instrument->getName();
   const auto &config = ConfigService::Instance();
   std::string facilityName, instrumentName;
@@ -387,18 +403,18 @@ const Geometry::SampleEnvironment *SetSample::setSampleEnvironment(
   SampleEnvironmentFactory factory(std::move(finder));
   auto sampleEnviron =
       factory.create(facilityName, instrumentName, envName, canName);
-  workspace->mutableSample().setEnvironment(std::move(sampleEnviron));
-  return &(workspace->sample().getEnvironment());
+  exptInfo.mutableSample().setEnvironment(std::move(sampleEnviron));
+  return &(exptInfo.sample().getEnvironment());
 }
 
 /**
- * @param workspace A pointer to the workspace to be affected
+ * @param experiment A reference to the experiment to be affected
  * @param args The user-supplied dictionary of flags
  * @param sampleEnv A pointer to the sample environment if one exists, otherwise
  * null
  * @return A string containing the XML definition of the shape
  */
-void SetSample::setSampleShape(API::MatrixWorkspace_sptr &workspace,
+void SetSample::setSampleShape(API::ExperimentInfo &experiment,
                                const Kernel::PropertyManager_const_sptr &args,
                                const Geometry::SampleEnvironment *sampleEnv) {
   using Geometry::Container;
@@ -410,10 +426,10 @@ void SetSample::setSampleShape(API::MatrixWorkspace_sptr &workspace,
 
   // Try known shapes or CSG first if supplied
   if (args) {
-    const auto refFrame = workspace->getInstrument()->getReferenceFrame();
+    const auto refFrame = experiment.getInstrument()->getReferenceFrame();
     const auto xml = tryCreateXMLFromArgsOnly(*args, *refFrame);
     if (!xml.empty()) {
-      runSetSampleShape(workspace, xml);
+      CreateSampleShape::setSampleShape(experiment, xml);
       return;
     }
   }
@@ -436,12 +452,12 @@ void SetSample::setSampleShape(API::MatrixWorkspace_sptr &workspace,
       // Given that the object is a CSG object, set the object
       // directly on the sample ensuring we preserve the
       // material.
-      const auto mat = workspace->sample().getMaterial();
+      const auto mat = experiment.sample().getMaterial();
       if (auto csgObj =
               boost::dynamic_pointer_cast<Geometry::CSGObject>(shapeObject)) {
         csgObj->setMaterial(mat);
       }
-      workspace->mutableSample().setShape(shapeObject);
+      experiment.mutableSample().setShape(shapeObject);
     } else {
       throw std::runtime_error("The can does not define the sample shape. "
                                "Please either provide a 'Shape' argument "
@@ -620,19 +636,6 @@ SetSample::createCylinderLikeXML(const Kernel::PropertyManager &args,
   return xmlShapeStream.str();
 }
 
-/**
- * Run SetSampleShape as an algorithm to set the shape of the sample
- * @param workspace A reference to the workspace
- * @param xml A string containing the XML definition
- */
-void SetSample::runSetSampleShape(API::MatrixWorkspace_sptr &workspace,
-                                  const std::string &xml) {
-  auto alg = createChildAlgorithm("CreateSampleShape");
-  alg->setProperty(PropertyNames::INPUT_WORKSPACE, workspace);
-  alg->setProperty("ShapeXML", xml);
-  alg->executeAsChildAlg();
-}
-
 /**
  * Run the named child algorithm on the given workspace. It assumes an in/out
  * workspace property called InputWorkspace
@@ -641,7 +644,7 @@ void SetSample::runSetSampleShape(API::MatrixWorkspace_sptr &workspace,
  * @param args A PropertyManager specifying the required arguments
  */
 void SetSample::runChildAlgorithm(const std::string &name,
-                                  API::MatrixWorkspace_sptr &workspace,
+                                  API::Workspace_sptr &workspace,
                                   const Kernel::PropertyManager &args) {
   auto alg = createChildAlgorithm(name);
   alg->setProperty(PropertyNames::INPUT_WORKSPACE, workspace);
diff --git a/Framework/DataHandling/src/SetSampleMaterial.cpp b/Framework/DataHandling/src/SetSampleMaterial.cpp
index 9e5378d859744442ca6eb3671e0333849ec37689..11baaa205d067d31bbac43d8de868ab31dcdd4d3 100644
--- a/Framework/DataHandling/src/SetSampleMaterial.cpp
+++ b/Framework/DataHandling/src/SetSampleMaterial.cpp
@@ -231,8 +231,7 @@ void SetSampleMaterial::exec() {
     g_log.information("Unknown value for number density");
   } else {
     const double rho = material->numberDensity();
-    double smu =
-        material->totalScatterXSection(NeutronAtom::ReferenceLambda) * rho;
+    double smu = material->totalScatterXSection() * rho;
     double amu = material->absorbXSection(NeutronAtom::ReferenceLambda) * rho;
     g_log.information() << "Anvred LinearScatteringCoef = " << smu << " 1/cm\n"
                         << "Anvred LinearAbsorptionCoef = " << amu << " 1/cm\n";
diff --git a/Framework/DataHandling/test/CMakeLists.txt b/Framework/DataHandling/test/CMakeLists.txt
index 074c7c033f92fc4c203d4bf7e65f8ad9b59a55ac..75f98634a22221d34ffad2469c2c323e8ec7fb05 100644
--- a/Framework/DataHandling/test/CMakeLists.txt
+++ b/Framework/DataHandling/test/CMakeLists.txt
@@ -30,6 +30,7 @@ if(CXXTEST_FOUND)
                         Catalog
                         DataHandling
                         Nexus
+                        HistogramData
                         ${NEXUS_LIBRARIES}
                         ${HDF5_LIBRARIES}
                         ${HDF5_HL_LIBRARIES})
diff --git a/Framework/DataHandling/test/LoadNexusProcessed2Test.h b/Framework/DataHandling/test/LoadNexusProcessed2Test.h
new file mode 100644
index 0000000000000000000000000000000000000000..a5c0ebaac09d013eb4f6e37e214322f0b85a18cc
--- /dev/null
+++ b/Framework/DataHandling/test/LoadNexusProcessed2Test.h
@@ -0,0 +1,188 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_DATAHANDLING_LOADNEXUSPROCESSED2TEST_H_
+#define MANTID_DATAHANDLING_LOADNEXUSPROCESSED2TEST_H_
+
+#include <cxxtest/TestSuite.h>
+
+#include "MantidAPI/AlgorithmManager.h"
+#include "MantidAPI/MatrixWorkspace.h"
+#include "MantidAPI/SpectrumInfo.h"
+#include "MantidAPI/Workspace.h"
+#include "MantidDataHandling/LoadEmptyInstrument.h"
+#include "MantidDataHandling/LoadNexusProcessed.h"
+#include "MantidDataHandling/LoadNexusProcessed2.h"
+#include "MantidDataHandling/SaveNexusESS.h"
+#include "MantidGeometry/Instrument/DetectorInfo.h"
+#include "MantidIndexing/IndexInfo.h"
+#include "MantidTestHelpers/FileResource.h"
+#include "MantidTestHelpers/WorkspaceCreationHelper.h"
+#include "MantidTypes/SpectrumDefinition.h"
+
+using namespace Mantid::DataHandling;
+using namespace Mantid::API;
+
+namespace {
+
+template <typename Alg>
+Mantid::API::MatrixWorkspace_sptr do_load(const std::string &filename) {
+  Alg loader;
+  loader.setChild(true);
+  loader.setRethrows(true);
+  loader.initialize();
+  loader.setProperty("Filename", filename);
+  loader.setPropertyValue("OutputWorkspace", "dummy");
+  loader.execute();
+  Workspace_sptr out = loader.getProperty("OutputWorkspace");
+  auto matrixWSOut =
+      boost::dynamic_pointer_cast<Mantid::API::MatrixWorkspace>(out);
+  return matrixWSOut;
+}
+
+Mantid::API::MatrixWorkspace_sptr do_load_v2(const std::string &filename) {
+  return do_load<LoadNexusProcessed2>(filename);
+}
+
+Mantid::API::MatrixWorkspace_sptr do_load_v1(const std::string &filename) {
+  return do_load<LoadNexusProcessed>(filename);
+}
+
+namespace test_utility {
+template <typename T> void save(const std::string filename, T &ws) {
+  SaveNexusESS alg;
+  alg.setChild(true);
+  alg.setRethrows(true);
+  alg.initialize();
+  alg.isInitialized();
+  alg.setProperty("InputWorkspace", ws);
+  alg.setProperty("Filename", filename);
+  alg.execute();
+  alg.isExecuted();
+}
+
+Mantid::API::MatrixWorkspace_sptr make_workspace(const std::string &filename) {
+  LoadEmptyInstrument loader;
+  loader.setChild(true);
+  loader.initialize();
+  loader.setProperty("Filename", filename);
+  loader.setPropertyValue("OutputWorkspace", "dummy");
+  loader.execute();
+  MatrixWorkspace_sptr ws = loader.getProperty("OutputWorkspace");
+  return ws;
+}
+} // namespace test_utility
+} // namespace
+
+class LoadNexusProcessed2Test : public CxxTest::TestSuite {
+public:
+  // This pair of boilerplate methods prevent the suite being created statically
+  // This means the constructor isn't called when running other tests
+  static LoadNexusProcessed2Test *createSuite() {
+    return new LoadNexusProcessed2Test();
+  }
+  static void destroySuite(LoadNexusProcessed2Test *suite) { delete suite; }
+
+  void test_checkVersion() {
+    LoadNexusProcessed2 alg;
+    TS_ASSERT_EQUALS(alg.version(), 2);
+  }
+
+  void test_defaultVersion() {
+    auto alg =
+        Mantid::API::AlgorithmManager::Instance().create("LoadNexusProcessed");
+    TS_ASSERT_EQUALS(alg->version(), 2);
+  }
+  void test_with_ess_instrument() {
+
+    using namespace Mantid::HistogramData;
+
+    ScopedFileHandle fileInfo("test_ess_instrument.nxs");
+
+    auto wsIn =
+        test_utility::make_workspace("V20_4-tubes_90deg_Definition_v01.xml");
+    for (size_t i = 0; i < wsIn->getNumberHistograms(); ++i) {
+      wsIn->setCounts(i, Counts{double(i)});
+    }
+
+    test_utility::save(fileInfo.fullPath(), wsIn);
+    auto wsOut = do_load_v2(fileInfo.fullPath());
+
+    // Quick geometry Test
+    TS_ASSERT(wsOut->detectorInfo().isEquivalent(wsIn->detectorInfo()));
+
+    // Quick data test.
+    for (size_t i = 0; i < wsIn->getNumberHistograms(); ++i) {
+      TS_ASSERT_EQUALS(wsIn->counts(i)[0], wsOut->counts(i)[0]);
+    }
+  }
+
+  void test_reading_mappings() {
+    using Mantid::SpectrumDefinition;
+    using namespace Mantid::Indexing;
+    ScopedFileHandle fileInfo("test_no_spectra_mapping.nxs");
+    auto wsIn =
+        WorkspaceCreationHelper::create2DWorkspaceWithRectangularInstrument(
+            2 /*numBanks*/, 10 /*numPixels*/, 12 /*numBins*/);
+
+    std::vector<SpectrumDefinition> specDefinitions;
+    std::vector<SpectrumNumber> spectrumNumbers;
+    size_t i = wsIn->getNumberHistograms() - 1;
+    for (size_t j = 0; j < wsIn->getNumberHistograms(); --i, ++j) {
+      specDefinitions.push_back(SpectrumDefinition(i));
+      spectrumNumbers.push_back(SpectrumNumber(static_cast<int>(j)));
+    }
+    IndexInfo info(spectrumNumbers);
+    info.setSpectrumDefinitions(specDefinitions);
+    wsIn->setIndexInfo(info);
+    test_utility::save(fileInfo.fullPath(), wsIn);
+
+    // Reload it.
+    auto matrixWSOut = do_load_v2(fileInfo.fullPath());
+
+    const auto &inSpecInfo = wsIn->spectrumInfo();
+    const auto &outSpecInfo = matrixWSOut->spectrumInfo();
+
+    // Note we do not guarantee the preseveration of spectrum indexes during
+    // deserialisation, so we need the maps to ensure we compare like for like.
+    auto specToIndexOut = matrixWSOut->getSpectrumToWorkspaceIndexMap();
+    auto specToIndexIn = wsIn->getSpectrumToWorkspaceIndexMap();
+
+    auto indexInfo = matrixWSOut->indexInfo();
+
+    TS_ASSERT_EQUALS(outSpecInfo.size(), inSpecInfo.size());
+    for (size_t i = 0; i < outSpecInfo.size(); ++i) {
+
+      auto specNumber = int(indexInfo.spectrumNumber(i));
+
+      auto indexInInput = specToIndexIn.at(specNumber);
+      auto indexInOutput = specToIndexOut.at(specNumber);
+
+      // Output has no mapping, so for each spectrum have 0 detector indices
+      TS_ASSERT_EQUALS(outSpecInfo.spectrumDefinition(indexInOutput).size(),
+                       inSpecInfo.spectrumDefinition(indexInInput).size());
+      // Compare actual detector indices for each spectrum when fixed as below
+      TS_ASSERT_EQUALS(outSpecInfo.spectrumDefinition(indexInOutput)[0],
+                       inSpecInfo.spectrumDefinition(indexInInput)[0]);
+    }
+  }
+
+  void test_demonstrate_old_loader_incompatible() {
+
+    ScopedFileHandle fileInfo("test_demo_file_for_incompatible.nxs");
+
+    auto wsIn =
+        test_utility::make_workspace("V20_4-tubes_90deg_Definition_v01.xml");
+
+    test_utility::save(fileInfo.fullPath(), wsIn);
+    auto wsOut = do_load_v1(fileInfo.fullPath());
+    // Should fail to handle ESS layout. Algorithm runs, but output not same as
+    // input. i.e. No geometry
+    TS_ASSERT(!wsOut->detectorInfo().isEquivalent(wsIn->detectorInfo()));
+  }
+};
+
+#endif /* MANTID_DATAHANDLING_LOADNEXUSPROCESSED2TEST_H_ */
diff --git a/Framework/DataHandling/test/SaveNexusESSTest.h b/Framework/DataHandling/test/SaveNexusESSTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..a9421a9831ecda5524eb79e8b40c4234d120bdb4
--- /dev/null
+++ b/Framework/DataHandling/test/SaveNexusESSTest.h
@@ -0,0 +1,242 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_DATAHANDLING_SAVENEXUSESSTEST_H_
+#define MANTID_DATAHANDLING_SAVENEXUSESSTEST_H_
+
+#include <cxxtest/TestSuite.h>
+
+#include "MantidAPI/SpectrumInfo.h"
+#include "MantidDataHandling/LoadEmptyInstrument.h"
+#include "MantidDataHandling/LoadNexusProcessed2.h"
+#include "MantidDataHandling/SaveNexusESS.h"
+#include "MantidDataHandling/SaveNexusProcessed.h"
+#include "MantidGeometry/Instrument.h"
+#include "MantidGeometry/Instrument/ComponentInfo.h"
+#include "MantidGeometry/Instrument/DetectorInfo.h"
+#include "MantidIndexing/IndexInfo.h"
+#include "MantidKernel/Logger.h"
+#include "MantidNexusGeometry/NexusGeometryParser.h"
+#include "MantidNexusGeometry/NexusGeometrySave.h"
+#include "MantidTestHelpers/FileResource.h"
+#include "MantidTestHelpers/NexusFileReader.h"
+#include "MantidTestHelpers/WorkspaceCreationHelper.h"
+#include <boost/filesystem.hpp>
+#include <memory>
+
+using namespace Mantid::DataHandling;
+using namespace Mantid::API;
+
+namespace {
+template <typename T> void do_execute(const std::string filename, T &ws) {
+  SaveNexusESS alg;
+  alg.setChild(true);
+  alg.setRethrows(true);
+  alg.initialize();
+  alg.isInitialized();
+  alg.setProperty("InputWorkspace", ws);
+  alg.setProperty("Filename", filename);
+  alg.execute();
+  alg.isExecuted();
+}
+
+namespace test_utility {
+Mantid::API::MatrixWorkspace_sptr reload(const std::string &filename) {
+  LoadNexusProcessed2 loader;
+  loader.setChild(true);
+  loader.setRethrows(true);
+  loader.initialize();
+  loader.setProperty("Filename", filename);
+  loader.setPropertyValue("OutputWorkspace", "dummy");
+  loader.execute();
+  Workspace_sptr out = loader.getProperty("OutputWorkspace");
+  auto matrixWSOut =
+      boost::dynamic_pointer_cast<Mantid::API::MatrixWorkspace>(out);
+  return matrixWSOut;
+}
+
+Mantid::API::MatrixWorkspace_sptr
+from_instrument_file(const std::string &filename) {
+  LoadEmptyInstrument loader;
+  loader.setChild(true);
+  loader.initialize();
+  loader.setProperty("Filename", filename);
+  loader.setPropertyValue("OutputWorkspace", "dummy");
+  loader.execute();
+  MatrixWorkspace_sptr ws = loader.getProperty("OutputWorkspace");
+  return ws;
+}
+Mantid::API::MatrixWorkspace_sptr
+from_instrument_file2(const std::string &name) {
+  LoadEmptyInstrument loader;
+  loader.setChild(true);
+  loader.initialize();
+  loader.setProperty("InstrumentName", name);
+  loader.setPropertyValue("OutputWorkspace", "dummy");
+  loader.execute();
+  MatrixWorkspace_sptr ws = loader.getProperty("OutputWorkspace");
+  return ws;
+}
+} // namespace test_utility
+} // namespace
+class SaveNexusESSTest : public CxxTest::TestSuite {
+public:
+  // This pair of boilerplate methods prevent the suite being created statically
+  // This means the constructor isn't called when running other tests
+  static SaveNexusESSTest *createSuite() { return new SaveNexusESSTest(); }
+  static void destroySuite(SaveNexusESSTest *suite) { delete suite; }
+
+  void test_Init() {
+    SaveNexusESS alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize());
+    TS_ASSERT(alg.isInitialized())
+  }
+
+  void test_exec_rectangular_instrument_details() {
+    using namespace Mantid::NexusGeometry;
+    ScopedFileHandle fileInfo("test_rectangular_instrument.nxs");
+    auto ws =
+        WorkspaceCreationHelper::create2DWorkspaceWithRectangularInstrument(
+            1 /*numBanks*/, 10 /*numPixels*/, 10 /*numBins*/);
+
+    const auto &inDetInfo = ws->detectorInfo();
+    const auto &inCompInfo = ws->componentInfo();
+
+    do_execute(fileInfo.fullPath(), ws);
+
+    // Load and check instrument geometry
+    Mantid::Kernel::Logger logger("test_logger");
+    auto instr = NexusGeometryParser::createInstrument(fileInfo.fullPath(),
+                                                       makeLogger(&logger));
+    Mantid::Geometry::ParameterMap pmap;
+    auto beamline = instr->makeBeamline(pmap);
+    const auto &outDetInfo = beamline.second;
+    const auto &outCompInfo = beamline.first;
+    // Same detector info
+    TS_ASSERT(outDetInfo->isEquivalent(inDetInfo));
+    // We have a 10 by 10 Rectangular Detector Bank, this means 10 columns.
+    // SaveNexusGeometry (via SaveNexusESS) will not save columns of a
+    // Rectangular detector bank. Hence subtranction from output.
+    TS_ASSERT_EQUALS(outCompInfo->size(), inCompInfo.size() - 10);
+  }
+
+  void test_exec_rectangular_data() {
+    ScopedFileHandle fileInfo("test_rectangular_data.nxs");
+    auto wsIn =
+        WorkspaceCreationHelper::create2DWorkspaceWithRectangularInstrument(
+            1 /*numBanks*/, 10 /*numPixels*/, 12 /*numBins*/);
+
+    do_execute(fileInfo.fullPath(), wsIn);
+
+    auto matrixWSOut = test_utility::reload(fileInfo.fullPath());
+
+    TS_ASSERT_EQUALS(matrixWSOut->blocksize(), 12);
+    TS_ASSERT_EQUALS(matrixWSOut->getNumberHistograms(), 10 * 10);
+    TS_ASSERT(matrixWSOut->detectorInfo().isEquivalent(wsIn->detectorInfo()));
+  }
+
+  void test_with_ess_instrument() {
+
+    using namespace Mantid::HistogramData;
+
+    ScopedFileHandle fileInfo("test_ess_instrument.nxs");
+
+    auto wsIn = test_utility::from_instrument_file(
+        "V20_4-tubes_90deg_Definition_v01.xml");
+    for (size_t i = 0; i < wsIn->getNumberHistograms(); ++i) {
+      wsIn->setCounts(i, Counts{double(i)});
+    }
+
+    do_execute(fileInfo.fullPath(), wsIn);
+    auto wsOut = test_utility::reload(fileInfo.fullPath());
+
+    // Quick geometry Test
+    TS_ASSERT(wsOut->detectorInfo().isEquivalent(wsIn->detectorInfo()));
+
+    // Quick data test.
+    for (size_t i = 0; i < wsIn->getNumberHistograms(); ++i) {
+      TS_ASSERT_EQUALS(wsIn->counts(i)[0], wsOut->counts(i)[0]);
+    }
+  }
+
+  void test_demonstrate_spectra_detector_map_saved() {
+
+    using namespace Mantid::Indexing;
+    ScopedFileHandle fileInfo("test_no_spectra_mapping.nxs");
+    auto wsIn =
+        WorkspaceCreationHelper::create2DWorkspaceWithRectangularInstrument(
+            2 /*numBanks*/, 10 /*numPixels*/, 12 /*numBins*/);
+
+    std::vector<SpectrumDefinition> specDefinitions;
+    std::vector<SpectrumNumber> spectrumNumbers;
+    size_t i = wsIn->getNumberHistograms() - 1;
+    for (size_t j = 0; j < wsIn->getNumberHistograms(); --i, ++j) {
+      specDefinitions.push_back(SpectrumDefinition(i));
+      spectrumNumbers.push_back(SpectrumNumber(static_cast<int>(j)));
+    }
+    IndexInfo info(spectrumNumbers);
+    info.setSpectrumDefinitions(specDefinitions);
+    wsIn->setIndexInfo(info);
+    do_execute(fileInfo.fullPath(), wsIn);
+
+    {
+      const auto rootName =
+          wsIn->componentInfo().name(wsIn->componentInfo().root());
+      // Check that mapping datasets are written
+      Mantid::NexusGeometry::NexusFileReader validator(fileInfo.fullPath());
+      TS_ASSERT(validator.hasDataset(
+          "spectra", {"mantid_workspace_1", rootName, "bank1"}));
+      TS_ASSERT(validator.hasDataset(
+          "detector_list", {"mantid_workspace_1", rootName, "bank1"}));
+      TS_ASSERT(validator.hasDataset(
+          "detector_index", {"mantid_workspace_1", rootName, "bank1"}));
+      TS_ASSERT(validator.hasDataset(
+          "detector_count", {"mantid_workspace_1", rootName, "bank1"}));
+      TS_ASSERT(validator.hasDataset(
+          "spectra", {"mantid_workspace_1", rootName, "bank2"}));
+      TS_ASSERT(validator.hasDataset(
+          "detector_list", {"mantid_workspace_1", rootName, "bank2"}));
+      TS_ASSERT(validator.hasDataset(
+          "detector_index", {"mantid_workspace_1", rootName, "bank2"}));
+      TS_ASSERT(validator.hasDataset(
+          "detector_count", {"mantid_workspace_1", rootName, "bank2"}));
+    }
+  }
+
+  void test_base_function_with_workspace() {
+
+    // This is testing the core routine, but we put it here and not in
+    // NexusGeometrySave because we need access to WorkspaceCreationHelpers for
+    // this.
+    ScopedFileHandle fileResource("test_with_full_workspace.hdf5");
+    std::string destinationFile = fileResource.fullPath();
+    // auto ws = WorkspaceCreationHelper::create2DWorkspaceWithFullInstrument(
+    //    10 /*histograms*/, 100 /*bins*/);
+    auto ws =
+        WorkspaceCreationHelper::create2DWorkspaceWithRectangularInstrument(
+            2, 10, 20);
+    Mantid::Kernel::Logger logger("logger");
+    Mantid::NexusGeometry::LogAdapter<Mantid::Kernel::Logger> adapter(&logger);
+    Mantid::NexusGeometry::NexusGeometrySave::saveInstrument(
+        *ws, destinationFile, "entry", adapter);
+  }
+
+  void test_regression_iris() {
+    ScopedFileHandle handle(
+        "test_regression_iris.nxs"); // IRIS has single monitors
+    auto iris = test_utility::from_instrument_file2("IRIS");
+    do_execute(handle.fullPath(), iris);
+    auto iris_reloaded = test_utility::reload(handle.fullPath());
+    const auto &indexInfo = iris->indexInfo();
+    const auto &indexInfoReload = iris_reloaded->indexInfo();
+    const auto &outDetInfo = iris_reloaded->detectorInfo();
+    const auto &inDetInfo = iris->detectorInfo();
+    TS_ASSERT_EQUALS(inDetInfo.size(), outDetInfo.size());
+    TS_ASSERT_EQUALS(indexInfo.size(), indexInfoReload.size());
+  }
+};
+
+#endif /* MANTID_DATAHANDLING_SAVENEXUSESSTEST_H_ */
diff --git a/Framework/DataHandling/test/SaveNexusGeometryTest.h b/Framework/DataHandling/test/SaveNexusGeometryTest.h
index b01b9ac363bcc6c9c919dd2f2f49ebea98193bc4..cd7efc5e1353ed14d220f9c8ef77ca5625ab7e5d 100644
--- a/Framework/DataHandling/test/SaveNexusGeometryTest.h
+++ b/Framework/DataHandling/test/SaveNexusGeometryTest.h
@@ -18,7 +18,6 @@
 #include "MantidTestHelpers/WorkspaceCreationHelper.h"
 
 #include <H5Cpp.h>
-#include <boost/filesystem.hpp>
 #include <cxxtest/TestSuite.h>
 
 using Mantid::DataHandling::SaveNexusGeometry;
diff --git a/Framework/DataHandling/test/SetSampleMaterialTest.h b/Framework/DataHandling/test/SetSampleMaterialTest.h
index 11599c2be0a35efd1b66b19cef2e97d589490e34..6adb0380a530c638b3b5bcf24b82c15d8a423f9e 100644
--- a/Framework/DataHandling/test/SetSampleMaterialTest.h
+++ b/Framework/DataHandling/test/SetSampleMaterialTest.h
@@ -79,9 +79,7 @@ public:
     // can get away with holding pointer as it is an inout ws property
     const auto sampleMaterial = testWS->sample().getMaterial();
     TS_ASSERT_DELTA(sampleMaterial.numberDensity(), 0.1183245, 0.0001);
-    TS_ASSERT_DELTA(
-        sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda),
-        3.1404, 0.0001);
+    TS_ASSERT_DELTA(sampleMaterial.totalScatterXSection(), 3.1404, 0.0001);
     TS_ASSERT_DELTA(sampleMaterial.absorbXSection(NeutronAtom::ReferenceLambda),
                     0.0925, 0.0001);
     TS_ASSERT_DELTA(
@@ -128,17 +126,11 @@ public:
     // can get away with holding pointer as it is an inout ws property
     const auto sampleMaterial = testWS->sample().getMaterial();
     TS_ASSERT_DELTA(sampleMaterial.numberDensity(), 0.1183245, 0.0001);
-    TS_ASSERT_DELTA(
-        sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda),
-        3.1404, 0.0001);
+    TS_ASSERT_DELTA(sampleMaterial.totalScatterXSection(), 3.1404, 0.0001);
     TS_ASSERT_DELTA(sampleMaterial.absorbXSection(NeutronAtom::ReferenceLambda),
                     0.0925, 0.0001);
-    TS_ASSERT_DELTA(
-        sampleMaterial.cohScatterLength(NeutronAtom::ReferenceLambda), 4.8614,
-        0.0001);
-    TS_ASSERT_DELTA(
-        sampleMaterial.totalScatterLengthSqrd(NeutronAtom::ReferenceLambda),
-        24.9905, 0.0001);
+    TS_ASSERT_DELTA(sampleMaterial.cohScatterLength(), 4.8614, 0.0001);
+    TS_ASSERT_DELTA(sampleMaterial.totalScatterLengthSqrd(), 24.9905, 0.0001);
 
     AnalysisDataService::Instance().remove(wsName);
   }
@@ -173,9 +165,7 @@ public:
     // can get away with holding pointer as it is an inout ws property
     const auto sampleMaterial = testWS->sample().getMaterial();
     TS_ASSERT_DELTA(sampleMaterial.numberDensity(), 0.1183245, 0.0001);
-    TS_ASSERT_DELTA(
-        sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda),
-        4.0852, 0.0001);
+    TS_ASSERT_DELTA(sampleMaterial.totalScatterXSection(), 4.0852, 0.0001);
     TS_ASSERT_DELTA(sampleMaterial.absorbXSection(NeutronAtom::ReferenceLambda),
                     1.4381, 0.0001);
     TS_ASSERT_DELTA(
@@ -219,9 +209,7 @@ public:
 
     const auto sampleMaterial = testWS->sample().getMaterial();
     TS_ASSERT_DELTA(sampleMaterial.numberDensity(), 0.1183245, 0.0001);
-    TS_ASSERT_DELTA(
-        sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda),
-        3.1404, 0.0001);
+    TS_ASSERT_DELTA(sampleMaterial.totalScatterXSection(), 3.1404, 0.0001);
     TS_ASSERT_DELTA(sampleMaterial.absorbXSection(NeutronAtom::ReferenceLambda),
                     0.0925, 0.0001);
     TS_ASSERT_DELTA(
@@ -261,9 +249,7 @@ public:
 
     const auto sampleMaterial = testWS->sample().getMaterial();
     TS_ASSERT_DELTA(sampleMaterial.numberDensity(), 0.0913375, 0.0001);
-    TS_ASSERT_DELTA(
-        sampleMaterial.totalScatterXSection(NeutronAtom::ReferenceLambda), 18.5,
-        0.0001);
+    TS_ASSERT_DELTA(sampleMaterial.totalScatterXSection(), 18.5, 0.0001);
     TS_ASSERT_DELTA(sampleMaterial.absorbXSection(NeutronAtom::ReferenceLambda),
                     4.49, 0.0001);
     TS_ASSERT_DELTA(
diff --git a/Framework/DataHandling/test/SetSampleTest.h b/Framework/DataHandling/test/SetSampleTest.h
index 9d599c6855ce4cd7992a5f62a855cdba7680f30b..8a8544c605ee3a18bc61694642b7f2cbd3ae4542 100644
--- a/Framework/DataHandling/test/SetSampleTest.h
+++ b/Framework/DataHandling/test/SetSampleTest.h
@@ -11,6 +11,8 @@
 
 #include "MantidAPI/Sample.h"
 #include "MantidDataHandling/SetSample.h"
+#include "MantidDataObjects/PeaksWorkspace.h"
+#include "MantidDataObjects/TableWorkspace.h"
 #include "MantidGeometry/Instrument/ReferenceFrame.h"
 #include "MantidGeometry/Instrument/SampleEnvironment.h"
 #include "MantidGeometry/Objects/CSGObject.h"
@@ -422,10 +424,26 @@ public:
     TS_ASSERT_EQUALS(false, sampleShape.isValid(V3D(0, 0.041, 0.021)));
     TS_ASSERT_EQUALS(false, sampleShape.isValid(V3D(0, 0.041, -0.001)));
   }
+
+  void test_PeaksWorkspace_Is_Accepted_Workspace_Type() {
+    auto inputWS = WorkspaceCreationHelper::createPeaksWorkspace(1);
+    auto alg = createAlgorithm(inputWS);
+    alg->setProperty("Geometry",
+                     createHollowCylinderWithIndexedAxisGeometryProps());
+  }
+
   //----------------------------------------------------------------------------
   // Failure tests
   //----------------------------------------------------------------------------
 
+  void test_validateArgs_Gives_Errors_For_Incorrect_WorkspaceType() {
+    using Mantid::DataObjects::TableWorkspace;
+    auto alg = createAlgorithm(boost::make_shared<TableWorkspace>(0));
+
+    auto helpMessages = alg->validateInputs();
+    TS_ASSERT(helpMessages.find("InputWorkspace") != helpMessages.cend());
+  }
+
   void test_Environment_Args_Without_Name_Invalid() {
     using Mantid::Kernel::PropertyManager;
     using StringProperty = Mantid::Kernel::PropertyWithValue<std::string>;
@@ -538,8 +556,8 @@ public:
   //----------------------------------------------------------------------------
 private:
   Mantid::API::IAlgorithm_uptr
-  createAlgorithm(const Mantid::API::MatrixWorkspace_sptr &inputWS =
-                      Mantid::API::MatrixWorkspace_sptr()) {
+  createAlgorithm(const Mantid::API::Workspace_sptr &inputWS =
+                      Mantid::API::Workspace_sptr()) {
     auto alg = std::make_unique<SetSample>();
     alg->setChild(true);
     alg->setRethrows(true);
diff --git a/Framework/Geometry/inc/MantidGeometry/Objects/ShapeFactory.h b/Framework/Geometry/inc/MantidGeometry/Objects/ShapeFactory.h
index 76407c3a986fa157d4aa5ef923f21538adf88382..48eef45b5f4426fac39695b70c2f5b20e3a8faad 100644
--- a/Framework/Geometry/inc/MantidGeometry/Objects/ShapeFactory.h
+++ b/Framework/Geometry/inc/MantidGeometry/Objects/ShapeFactory.h
@@ -74,11 +74,14 @@ public:
   boost::shared_ptr<CSGObject> createShape(std::string shapeXML,
                                            bool addTypeTag = true);
 
+  static boost::shared_ptr<CSGObject> createSphere(const Kernel::V3D &centre,
+                                                   double radius);
   static boost::shared_ptr<CSGObject>
   createHexahedralShape(double xlb, double xlf, double xrf, double xrb,
                         double ylb, double ylf, double yrf, double yrb);
 
 private:
+  static std::string sphereAlgebra(const int surfaceID);
   std::string parseSphere(Poco::XML::Element *pElem,
                           std::map<int, boost::shared_ptr<Surface>> &prim,
                           int &l_id);
diff --git a/Framework/Geometry/inc/MantidGeometry/Surfaces/Sphere.h b/Framework/Geometry/inc/MantidGeometry/Surfaces/Sphere.h
index 5b420ff3b24dbc3ba98c3338cbb7596b6879a18c..d06fb4740876dcf7b5adb3957c9b2fd1c4090169 100644
--- a/Framework/Geometry/inc/MantidGeometry/Surfaces/Sphere.h
+++ b/Framework/Geometry/inc/MantidGeometry/Surfaces/Sphere.h
@@ -29,8 +29,8 @@ namespace Geometry {
 
 class MANTID_GEOMETRY_DLL Sphere : public Quadratic {
 private:
-  Kernel::V3D Centre; ///< Point for centre
-  double Radius;      ///< Radius of sphere
+  Kernel::V3D m_centre; ///< Point for centre
+  double m_radius;      ///< Radius of sphere
   void rotate(const Kernel::Matrix<double> &) override;
   void displace(const Kernel::V3D &) override;
   /// Compute the distance from the centre of the sphere to the given point
@@ -43,6 +43,7 @@ protected:
 
 public:
   Sphere();
+  Sphere(Kernel::V3D centre, double radius);
   std::unique_ptr<Sphere> clone() const;
   /// Effective typename
   std::string className() const override { return "Sphere"; }
@@ -60,12 +61,12 @@ public:
   /// Setter for centre of sphere
   void setCentre(const Kernel::V3D &);
   /// Get Centre
-  Kernel::V3D getCentre() const { return Centre; }
+  Kernel::V3D getCentre() const { return m_centre; }
   /// Get Radius
-  double getRadius() const { return Radius; }
+  double getRadius() const { return m_radius; }
   /// Set Radius
   void setRadius(const double &r) {
-    Radius = r;
+    m_radius = r;
     setBaseEqn();
   }
   /// Generates the quadratic equation.
diff --git a/Framework/Geometry/src/Math/Acomp.cpp b/Framework/Geometry/src/Math/Acomp.cpp
index 932e9810471351cd978f4ed93eeb689e4fc62438..b9f4fcbf9ad124cbeb8727d80334b702ce122ab8 100644
--- a/Framework/Geometry/src/Math/Acomp.cpp
+++ b/Framework/Geometry/src/Math/Acomp.cpp
@@ -758,7 +758,7 @@ literals
   }
   // Doesn't work because literal map is a reference
   //  for_each(Comp.begin(),Comp.end(),
-  // std::bind2nd(std::mem_fun(&Acomp::getLiterals),literalMap));
+  // std::bind2nd(std::mem_fun(&Acomp::getLiterals), literalMap));
 }
 
 int Acomp::isSimple() const
@@ -836,8 +836,9 @@ i.e. one pass.
     Work.erase(uend, Work.end());
     Tmod.clear(); // erase all at the start
     // set PI status to 1
+    using std::placeholders::_1;
     for_each(Work.begin(), Work.end(),
-             std::bind2nd(std::mem_fun_ref(&BnId::setPI), 1));
+             std::bind(std::mem_fun_ref(&BnId::setPI), _1, 1));
 
     // Collect into pairs which have a difference of +/- one
     // object
@@ -951,8 +952,9 @@ It is set on exit (to the EPI)
     }
   }
   // Remove dead items from active list
+  using std::placeholders::_1;
   DNFactive.erase(remove_if(DNFactive.begin(), DNFactive.end(),
-                            std::bind2nd(std::less<int>(), 0)),
+                            std::bind(std::less<int>(), _1, 0)),
                   DNFactive.end());
 
   /// DEBUG PRINT
@@ -1515,8 +1517,9 @@ ab -> a'+b'
 */
 {
   Intersect = 1 - Intersect;
+  using std::placeholders::_1;
   transform(Units.begin(), Units.end(), Units.begin(),
-            std::bind2nd(std::multiplies<int>(), -1));
+            std::bind(std::multiplies<int>(), _1, -1));
   sort(Units.begin(), Units.end()); /// Resort the list. use reverse?
 
   for_each(Comp.begin(), Comp.end(), std::mem_fun_ref(&Acomp::complement));
diff --git a/Framework/Geometry/src/Math/BnId.cpp b/Framework/Geometry/src/Math/BnId.cpp
index c56f300daa95cf8390577dbb1a742c47091cf75f..f9a901804d17a544596cd3a7774950086b0d6f73 100644
--- a/Framework/Geometry/src/Math/BnId.cpp
+++ b/Framework/Geometry/src/Math/BnId.cpp
@@ -318,8 +318,9 @@ void BnId::reverse()
   Transform 1 -> -1
 */
 {
+  using std::placeholders::_1;
   transform(Tval.begin(), Tval.end(), Tval.begin(),
-            std::bind2nd(std::multiplies<int>(), -1));
+            std::bind(std::multiplies<int>(), _1, -1));
   setCounters();
 }
 
diff --git a/Framework/Geometry/src/Math/PolyBase.cpp b/Framework/Geometry/src/Math/PolyBase.cpp
index 70afb8d3fd36998d6d75c3f498cf9b442484a5cd..0b667e74b8db36218cdbfe83785234efc781db88 100644
--- a/Framework/Geometry/src/Math/PolyBase.cpp
+++ b/Framework/Geometry/src/Math/PolyBase.cpp
@@ -279,8 +279,9 @@ PolyBase &PolyBase::operator*=(const double V)
   @return (*this*V);
  */
 {
+  using std::placeholders::_1;
   transform(afCoeff.begin(), afCoeff.end(), afCoeff.begin(),
-            std::bind2nd(std::multiplies<double>(), V));
+            std::bind(std::multiplies<double>(), _1, V));
   return *this;
 }
 
@@ -291,8 +292,9 @@ PolyBase &PolyBase::operator/=(const double V)
   @return (*this/V);
  */
 {
+  using std::placeholders::_1;
   transform(afCoeff.begin(), afCoeff.end(), afCoeff.begin(),
-            std::bind2nd(std::divides<double>(), V));
+            std::bind(std::divides<double>(), _1, V));
   return *this;
 }
 
diff --git a/Framework/Geometry/src/Objects/ShapeFactory.cpp b/Framework/Geometry/src/Objects/ShapeFactory.cpp
index e3b2e863422d21b6efbab39d4bd470b687805412..2495186a0bae16f6d7c49233a68c945ea6f0f050 100644
--- a/Framework/Geometry/src/Objects/ShapeFactory.cpp
+++ b/Framework/Geometry/src/Objects/ShapeFactory.cpp
@@ -319,21 +319,22 @@ ShapeFactory::parseSphere(Poco::XML::Element *pElem,
                           int &l_id) {
   Element *pElemCentre = getOptionalShapeElement(pElem, "centre");
   Element *pElemRadius = getShapeElement(pElem, "radius");
-
-  // getDoubleAttribute can throw - put the calls above any new
   const double radius = getDoubleAttribute(pElemRadius, "val");
-
-  // create sphere
   const V3D centre = pElemCentre ? parsePosition(pElemCentre) : DEFAULT_CENTRE;
-  auto pSphere = boost::make_shared<Sphere>();
-  pSphere->setCentre(centre);
-  pSphere->setRadius(radius);
-  prim[l_id] = pSphere;
 
-  std::stringstream retAlgebraMatch;
-  retAlgebraMatch << "(-" << l_id << ")";
+  prim[l_id] = boost::make_shared<Sphere>(centre, radius);
+  const auto algebra = sphereAlgebra(l_id);
   l_id++;
-  return retAlgebraMatch.str();
+  return algebra;
+}
+
+/**
+ * Create the algebra string for a Sphere
+ * @param surfaceID ID of surface in map lookup
+ * @return A CSG surface algebra string
+ */
+std::string ShapeFactory::sphereAlgebra(const int surfaceID) {
+  return "(-" + std::to_string(surfaceID) + ")";
 }
 
 /** Parse XML 'infinite-plane' element
@@ -1363,6 +1364,32 @@ V3D ShapeFactory::parsePosition(Poco::XML::Element *pElem) {
   return retVal;
 }
 
+/**
+ * @brief Create a Sphere
+ * @param centre The center of the sphere
+ * @param radius The radius in metres
+ * @return A new CSGObject defining a sphere
+ */
+boost::shared_ptr<CSGObject>
+ShapeFactory::createSphere(const Kernel::V3D &centre, double radius) {
+  const int surfaceID = 1;
+  const std::map<int, boost::shared_ptr<Surface>> primitives{
+      {surfaceID, boost::make_shared<Sphere>(centre, radius)}};
+
+  auto shape = boost::make_shared<CSGObject>();
+  shape->setObject(21, sphereAlgebra(surfaceID));
+  shape->populate(primitives);
+
+  auto handler = boost::make_shared<GeometryHandler>(shape);
+  shape->setGeometryHandler(handler);
+  detail::ShapeInfo shapeInfo;
+  shapeInfo.setSphere(centre, radius);
+  handler->setShapeInfo(std::move(shapeInfo));
+
+  shape->defineBoundingBox(radius, radius, radius, -radius, -radius, -radius);
+  return shape;
+}
+
 /** Create a hexahedral shape object
 @param xlb :: Left-back x point or hexahedron
 @param xlf :: Left-front x point of hexahedron
diff --git a/Framework/Geometry/src/Surfaces/LineIntersectVisit.cpp b/Framework/Geometry/src/Surfaces/LineIntersectVisit.cpp
index 53cb18a9d768d474fd19a99fc7d13f89a171bb6b..ad25cc8d4c395f4df138ada5a9f02fa78f7c9d52 100644
--- a/Framework/Geometry/src/Surfaces/LineIntersectVisit.cpp
+++ b/Framework/Geometry/src/Surfaces/LineIntersectVisit.cpp
@@ -107,8 +107,9 @@ void LineIntersectVisit::procTrack()
 {
   // Calculate the distances to the points
   DOut.resize(PtOut.size());
+  using std::placeholders::_1;
   std::transform(PtOut.begin(), PtOut.end(), DOut.begin(),
-                 boost::bind(&Kernel::V3D::distance, ATrack.getOrigin(), _1));
+                 std::bind(&Kernel::V3D::distance, ATrack.getOrigin(), _1));
 }
 
 } // namespace Geometry
diff --git a/Framework/Geometry/src/Surfaces/Sphere.cpp b/Framework/Geometry/src/Surfaces/Sphere.cpp
index 1005d9d9cd6a7301ab077cab450c53c4454a991e..cc2a791aa18208d320bc718e6fcd3212b06122bc 100644
--- a/Framework/Geometry/src/Surfaces/Sphere.cpp
+++ b/Framework/Geometry/src/Surfaces/Sphere.cpp
@@ -39,45 +39,46 @@ int Sphere::g_nslices = 5;
 // The number of slices to use to approximate a sphere
 int Sphere::g_nstacks = 5;
 
-Sphere::Sphere()
-    : Quadratic(), Centre(0, 0, 0), Radius(0.0)
 /**
-Default constructor
-make sphere at the origin radius zero
-*/
-{
+ * Default constructor
+ * make sphere at the origin radius zero
+ */
+Sphere::Sphere() : Sphere({0, 0, 0}, 0.0) {}
+
+/**
+ * @brief Sphere::Sphere
+ * @param centre
+ * @param radius
+ */
+Sphere::Sphere(Kernel::V3D centre, double radius)
+    : Quadratic(), m_centre{centre}, m_radius{radius} {
   setBaseEqn();
 }
 
-Sphere *Sphere::doClone() const
 /**
-Makes a clone (implicit virtual copy constructor)
-@return new (*this)
-*/
-{
-  return new Sphere(*this);
-}
+ * Makes a clone (implicit virtual copy constructor)
+ * @return new (*this)
+ */
+Sphere *Sphere::doClone() const { return new Sphere(*this); }
 
-std::unique_ptr<Sphere> Sphere::clone() const
 /**
-Makes a clone (implicit virtual copy constructor)
-@return new (*this)
-*/
-{
+ * Makes a clone (implicit virtual copy constructor)
+ * @return new (*this)
+ */
+std::unique_ptr<Sphere> Sphere::clone() const {
   return std::unique_ptr<Sphere>(doClone());
 }
 
-int Sphere::setSurface(const std::string &Pstr)
 /**
-Processes a standard MCNPX cone string
-Recall that cones can only be specified on an axis
-Valid input is:
-- so radius
-- s cen_x cen_y cen_z radius
-- sx - cen_x radius
-@return : 0 on success, neg of failure
-*/
-{
+ * Processes a standard MCNPX cone string
+ * Recall that cones can only be specified on an axis
+ * Valid input is:
+ * - so radius
+ * - s cen_x cen_y cen_z radius
+ * - sx - cen_x radius
+ * @return : 0 on success, neg of failure
+ */
+int Sphere::setSurface(const std::string &Pstr) {
   std::string Line = Pstr;
   std::string item;
   if (!Mantid::Kernel::Strings::section(Line, item) ||
@@ -108,135 +109,130 @@ Valid input is:
   if (!Mantid::Kernel::Strings::section(Line, R))
     return -7;
 
-  Centre = Kernel::V3D(cent[0], cent[1], cent[2]);
-  Radius = R;
+  m_centre = Kernel::V3D(cent[0], cent[1], cent[2]);
+  m_radius = R;
   setBaseEqn();
   return 0;
 }
 
-int Sphere::side(const Kernel::V3D &Pt) const
 /**
-Calculate where the point Pt is relative to the
-sphere.
-@param Pt :: Point to test
-@retval -1 :: Pt within sphere
-@retval 0 :: point on the surface (within CTolerance)
-@retval 1 :: Pt outside the sphere
-*/
+ * Calculate where the point Pt is relative to the
+ * sphere.
+ * @param Pt :: Point to test
+ * @retval -1 :: Pt within sphere
+ * @retval 0 :: point on the surface (within CTolerance)
+ * @retval 1 :: Pt outside the sphere
+ */
+int Sphere::side(const Kernel::V3D &Pt) const
+
 {
   // MG:  Surface test  - This does not use onSurface since it would double the
   // amount of
   // computation if the object is not on the surface which is most likely
-  const double xdiff(Pt.X() - Centre.X()), ydiff(Pt.Y() - Centre.Y()),
-      zdiff(Pt.Z() - Centre.Z());
+  const double xdiff(Pt.X() - m_centre.X()), ydiff(Pt.Y() - m_centre.Y()),
+      zdiff(Pt.Z() - m_centre.Z());
   const double displace =
-      sqrt(xdiff * xdiff + ydiff * ydiff + zdiff * zdiff) - Radius;
+      sqrt(xdiff * xdiff + ydiff * ydiff + zdiff * zdiff) - m_radius;
   if (fabs(displace) < Tolerance) {
     return 0;
   }
   return (displace > 0.0) ? 1 : -1;
 }
 
-int Sphere::onSurface(const Kernel::V3D &Pt) const
 /**
-Calculate if the point Pt on the surface of the sphere
-(within tolerance CTolerance)
-@param Pt :: Point to check
-@return 1 :: on the surfacae or 0 if not.
-*/
-{
+ * Calculate if the point Pt on the surface of the sphere
+ * (within tolerance CTolerance)
+ * @param Pt :: Point to check
+ * @return 1 :: on the surfacae or 0 if not.
+ */
+int Sphere::onSurface(const Kernel::V3D &Pt) const {
   if (distance(Pt) > Tolerance) {
     return 0;
   }
   return 1;
 }
 
-double Sphere::distance(const Kernel::V3D &Pt) const
 /**
-Determine the shortest distance from the Surface
-to the Point.
-@param Pt :: Point to calculate distance from
-@return distance (Positive only)
-*/
-{
-  const Kernel::V3D disp_vec = Pt - Centre;
-  return std::abs(disp_vec.norm() - Radius);
+ * Determine the shortest distance from the Surface
+ * to the Point.
+ * @param Pt :: Point to calculate distance from
+ * @return distance (Positive only)
+ */
+double Sphere::distance(const Kernel::V3D &Pt) const {
+  const Kernel::V3D disp_vec = Pt - m_centre;
+  return std::abs(disp_vec.norm() - m_radius);
 }
 
-void Sphere::displace(const Kernel::V3D &Pt)
 /**
-Apply a shift of the centre
-@param Pt :: distance to add to the centre
-*/
-{
-  Centre += Pt;
+ * Apply a shift of the centre
+ * @param Pt :: distance to add to the centre
+ */
+void Sphere::displace(const Kernel::V3D &Pt) {
+  m_centre += Pt;
   Quadratic::displace(Pt);
 }
 
-void Sphere::rotate(const Kernel::Matrix<double> &MA)
 /**
-Apply a Rotation matrix
-@param MA :: matrix to rotate by
-*/
-{
-  Centre.rotate(MA);
+ * Apply a Rotation matrix
+ * @param MA :: matrix to rotate by
+ */
+void Sphere::rotate(const Kernel::Matrix<double> &MA) {
+  m_centre.rotate(MA);
   Quadratic::rotate(MA);
 }
 
+/**
+ * Compute the distance between the given point and the centre of the sphere
+ * @param pt :: The chosen point
+ */
 double Sphere::centreToPoint(const V3D &pt) const {
-  /**
-  Compute the distance between the given point and the centre of the sphere
-  @param pt :: The chosen point
-  */
-  const Kernel::V3D displace_vec = pt - Centre;
+  const Kernel::V3D displace_vec = pt - m_centre;
   return displace_vec.norm();
 }
 
-void Sphere::setCentre(const Kernel::V3D &A)
 /**
-Set the centre point
-@param A :: New Centre Point
-*/
-{
-  Centre = A;
+ * Set the centre point
+ * @param A :: New Centre Point
+ */
+void Sphere::setCentre(const Kernel::V3D &A) {
+  m_centre = A;
   setBaseEqn();
 }
 
-void Sphere::setBaseEqn()
 /**
-Sets an equation of type (general sphere)
-\f[ x^2+y^2+z^2+Gx+Hy+Jz+K=0 \f]
-*/
-{
-  BaseEqn[0] = 1.0;                                          // A x^2
-  BaseEqn[1] = 1.0;                                          // B y^2
-  BaseEqn[2] = 1.0;                                          // C z^2
-  BaseEqn[3] = 0.0;                                          // D xy
-  BaseEqn[4] = 0.0;                                          // E xz
-  BaseEqn[5] = 0.0;                                          // F yz
-  BaseEqn[6] = -2.0 * Centre[0];                             // G x
-  BaseEqn[7] = -2.0 * Centre[1];                             // H y
-  BaseEqn[8] = -2.0 * Centre[2];                             // J z
-  BaseEqn[9] = Centre.scalar_prod(Centre) - Radius * Radius; // K const
+ * Sets an equation of type (general sphere)
+ * \f[ x^2+y^2+z^2+Gx+Hy+Jz+K=0 \f]
+ */
+void Sphere::setBaseEqn() {
+  BaseEqn[0] = 1.0;                                                  // A x^2
+  BaseEqn[1] = 1.0;                                                  // B y^2
+  BaseEqn[2] = 1.0;                                                  // C z^2
+  BaseEqn[3] = 0.0;                                                  // D xy
+  BaseEqn[4] = 0.0;                                                  // E xz
+  BaseEqn[5] = 0.0;                                                  // F yz
+  BaseEqn[6] = -2.0 * m_centre[0];                                   // G x
+  BaseEqn[7] = -2.0 * m_centre[1];                                   // H y
+  BaseEqn[8] = -2.0 * m_centre[2];                                   // J z
+  BaseEqn[9] = m_centre.scalar_prod(m_centre) - m_radius * m_radius; // K const
 }
 
-void Sphere::write(std::ostream &OX) const
 /**
-Object of write is to output a MCNPX plane info
-@param OX :: Output stream (required for multiple std::endl)
-\todo (Needs precision)
-*/
-{
+ * Object of write is to output a MCNPX plane info
+ * @param OX :: Output stream (required for multiple std::endl)
+ * \todo (Needs precision)
+ */
+void Sphere::write(std::ostream &OX) const {
   std::ostringstream cx;
   Quadratic::writeHeader(cx);
   cx.precision(Surface::Nprecision);
-  if (Centre.distance(Kernel::V3D(0, 0, 0)) < Tolerance) {
-    cx << "so " << Radius;
+  if (m_centre.distance(Kernel::V3D(0, 0, 0)) < Tolerance) {
+    cx << "so " << m_radius;
   } else {
-    cx << "s " << Centre << " " << Radius;
+    cx << "s " << m_centre << " " << m_radius;
   }
   Mantid::Kernel::Strings::writeMCNPX(cx.str(), OX);
 }
+
 /**
  * Calculates the bounding box for the sphere and returns the bounding box
  * values.
@@ -249,17 +245,18 @@ Object of write is to output a MCNPX plane info
  */
 void Sphere::getBoundingBox(double &xmax, double &ymax, double &zmax,
                             double &xmin, double &ymin, double &zmin) {
-  xmax = Centre[0] + Radius;
-  ymax = Centre[1] + Radius;
-  zmax = Centre[2] + Radius;
-  xmin = Centre[0] - Radius;
-  ymin = Centre[1] - Radius;
-  zmin = Centre[2] - Radius;
+  xmax = m_centre[0] + m_radius;
+  ymax = m_centre[1] + m_radius;
+  zmax = m_centre[2] + m_radius;
+  xmin = m_centre[0] - m_radius;
+  ymin = m_centre[1] - m_radius;
+  zmin = m_centre[2] - m_radius;
 }
 
 #ifdef ENABLE_OPENCASCADE
 TopoDS_Shape Sphere::createShape() {
-  return BRepPrimAPI_MakeSphere(gp_Pnt(Centre[0], Centre[1], Centre[2]), Radius)
+  return BRepPrimAPI_MakeSphere(gp_Pnt(m_centre[0], m_centre[1], m_centre[2]),
+                                m_radius)
       .Shape();
 }
 #endif
diff --git a/Framework/Geometry/test/SphereTest.h b/Framework/Geometry/test/SphereTest.h
index 481fbe5af376cdac710be251be2dbd8154765f0d..aa17f3f08df2caaff772616dc504ec5e2ef0201e 100644
--- a/Framework/Geometry/test/SphereTest.h
+++ b/Framework/Geometry/test/SphereTest.h
@@ -26,7 +26,7 @@ using Mantid::Kernel::V3D;
 class SphereTest : public CxxTest::TestSuite {
 
 public:
-  void testConstructor() {
+  void testDefaultConstructor() {
     Sphere A;
     // both centre and radius = 0
     TS_ASSERT_EQUALS(extractString(A), "-1 so 0\n");
@@ -34,6 +34,16 @@ public:
     TS_ASSERT_EQUALS(A.getRadius(), 0);
   }
 
+  void testConstructor() {
+    const V3D centre{0, 0, 1};
+    const double radius{0.5};
+    Sphere A{centre, radius};
+
+    TS_ASSERT_EQUALS(extractString(A), "-1 s [0,0,1] 0.5\n");
+    TS_ASSERT_EQUALS(A.getCentre(), centre);
+    TS_ASSERT_EQUALS(A.getRadius(), radius);
+  }
+
   void testsetSurface() {
     Sphere A;
     A.setSurface("s 1.1 -2.1 1.1 2");
diff --git a/Framework/Kernel/CMakeLists.txt b/Framework/Kernel/CMakeLists.txt
index d154eec872aadd7f2598e40044a2d78f5a0d7ac9..cb5de710173b97c010ec97ff11ae8a4b22a58ebe 100644
--- a/Framework/Kernel/CMakeLists.txt
+++ b/Framework/Kernel/CMakeLists.txt
@@ -267,7 +267,7 @@ set(INC_FILES
     inc/MantidKernel/RegexStrings.h
     inc/MantidKernel/RegistrationHelper.h
     inc/MantidKernel/RemoteJobManager.h
-    inc/MantidKernel/SimpleJSON.h 
+    inc/MantidKernel/SimpleJSON.h
     inc/MantidKernel/SingletonHolder.h
     inc/MantidKernel/SobolSequence.h
     inc/MantidKernel/SpecialCoordinateSystem.h
@@ -594,7 +594,7 @@ set(
   ${ExternalData_BINARY_ROOT}/Testing/Data/UnitTest;${ExternalData_BINARY_ROOT}/Testing/Data/DocTest;${MANTID_ROOT}/instrument
   )
 set(COLORMAPS_FOLDER ${MANTID_ROOT}/installers/colormaps/)
-set(MANTIDPUBLISHER "http://upload.mantidproject.org/scriptrepository?debug=1")
+set(MANTIDPUBLISHER "https://upload.mantidproject.org/scriptrepository?debug=1")
 set(HTML_ROOT ${DOCS_BUILDDIR}/html)
 
 # For an mpi-enabled build, do not log to file, format console output (which
@@ -665,7 +665,7 @@ set(UPDATE_INSTRUMENT_DEFINTITIONS "${ENABLE_NETWORK_ACCESS}")
 set(CHECK_FOR_NEW_MANTID_VERSION "${ENABLE_NETWORK_ACCESS}")
 set(ENABLE_USAGE_REPORTS "${ENABLE_NETWORK_ACCESS}")
 set(DATADIRS "")
-set(MANTIDPUBLISHER "http://upload.mantidproject.org/scriptrepository")
+set(MANTIDPUBLISHER "https://upload.mantidproject.org/scriptrepository")
 set(HTML_ROOT ../share/doc/html)
 
 # For a framework-only (e.g. MPI) build some of these are not relevant and
diff --git a/Framework/Kernel/inc/MantidKernel/Material.h b/Framework/Kernel/inc/MantidKernel/Material.h
index 78f35450658963fa3bb18ee05bacf8d4d372739d..fce89375ee03eab8545e200f357e8549ab13821a 100644
--- a/Framework/Kernel/inc/MantidKernel/Material.h
+++ b/Framework/Kernel/inc/MantidKernel/Material.h
@@ -88,21 +88,19 @@ public:
   /// Get the pressure
   double pressure() const;
   /// Get the coherent scattering cross section for a given wavelength in barns.
-  double
-  cohScatterXSection(const double lambda =
-                         PhysicalConstants::NeutronAtom::ReferenceLambda) const;
+  double cohScatterXSection() const;
   /// Get the incoherent cross section for a given wavelength in barns.
-  double incohScatterXSection(
-      const double lambda =
-          PhysicalConstants::NeutronAtom::ReferenceLambda) const;
+  double incohScatterXSection() const;
   /// Return the total scattering cross section for a given wavelength in barns.
-  double totalScatterXSection(
-      const double lambda =
-          PhysicalConstants::NeutronAtom::ReferenceLambda) const;
+  double totalScatterXSection() const;
   /// Get the absorption cross section at a given wavelength in barns.
   double
   absorbXSection(const double lambda =
                      PhysicalConstants::NeutronAtom::ReferenceLambda) const;
+  /// Compute the attenuation at a given wavelegnth over the given distance
+  double attenuation(const double distance,
+                     const double lambda =
+                         PhysicalConstants::NeutronAtom::ReferenceLambda) const;
 
   /**
    * Returns the linear coefficient of absorption for the material in units of
diff --git a/Framework/Kernel/src/ConfigService.cpp b/Framework/Kernel/src/ConfigService.cpp
index a88cd8aeaa2fba38527793038fd5fc7290bca91e..8e95aa07b84dc9a915abe678ba5baacd67a8d1d9 100644
--- a/Framework/Kernel/src/ConfigService.cpp
+++ b/Framework/Kernel/src/ConfigService.cpp
@@ -618,9 +618,10 @@ bool ConfigServiceImpl::isInDataSearchList(const std::string &path) const {
   std::string correctedPath = path;
   replace(correctedPath.begin(), correctedPath.end(), '\\', '/');
 
+  using std::placeholders::_1;
   auto it =
       std::find_if(m_DataSearchDirs.cbegin(), m_DataSearchDirs.cend(),
-                   std::bind2nd(std::equal_to<std::string>(), correctedPath));
+                   std::bind(std::equal_to<std::string>(), _1, correctedPath));
   return (it != m_DataSearchDirs.end());
 }
 
diff --git a/Framework/Kernel/src/Material.cpp b/Framework/Kernel/src/Material.cpp
index 4749f5e1ac30843ca847fe07b75cf317a446b250..72ba3c08fd6f236a14a874281956ed642f8aff2c 100644
--- a/Framework/Kernel/src/Material.cpp
+++ b/Framework/Kernel/src/Material.cpp
@@ -151,13 +151,9 @@ double Material::pressure() const { return m_pressure; }
 /**
  * Get the coherent scattering cross section according to Sears eqn 7.
  *
- * @param lambda :: The wavelength to evaluate the cross section
- * @returns The value of the coherent scattering cross section at
- * the given wavelength
+ * @returns The value of the coherent scattering cross section.
  */
-double Material::cohScatterXSection(const double lambda) const {
-  UNUSED_ARG(lambda);
-
+double Material::cohScatterXSection() const {
   if (m_chemicalFormula.size() == 1)
     return m_chemicalFormula.front().atom->neutron.coh_scatt_xs;
 
@@ -167,13 +163,9 @@ double Material::cohScatterXSection(const double lambda) const {
 /**
  * Get the incoherent scattering cross section according to Sears eqn 16
  *
- * @param lambda :: The wavelength to evaluate the cross section
- * @returns The value of the coherent scattering cross section at
- * the given wavelength
+ * @returns The value of the coherent scattering cross section.
  */
-double Material::incohScatterXSection(const double lambda) const {
-  UNUSED_ARG(lambda);
-
+double Material::incohScatterXSection() const {
   if (m_chemicalFormula.size() == 1)
     return m_chemicalFormula.front().atom->neutron.inc_scatt_xs;
 
@@ -183,13 +175,9 @@ double Material::incohScatterXSection(const double lambda) const {
 /**
  * Get the total scattering cross section following Sears eqn 13.
  *
- * @param lambda :: The wavelength to evaluate the cross section
- * @returns The value of the total scattering cross section at
- * the given wavelength
+ * @returns The value of the total scattering cross section.
  */
-double Material::totalScatterXSection(const double lambda) const {
-  UNUSED_ARG(lambda);
-
+double Material::totalScatterXSection() const {
   if (m_chemicalFormula.size() == 1)
     return m_chemicalFormula.front().atom->neutron.tot_scatt_xs;
 
@@ -243,11 +231,21 @@ double Material::absorbXSection(const double lambda) const {
   }
 }
 
+/**
+ * @param distance Distance (m) travelled
+ * @param lambda Wavelength (Angstroms) to compute the attenuation (default =
+ * reference lambda)
+ * @return The dimensionless attenuation coefficient
+ */
+double Material::attenuation(const double distance, const double lambda) const {
+  return exp(-100 * numberDensity() *
+             (totalScatterXSection() + absorbXSection(lambda)) * distance);
+}
+
 // NOTE: the angstrom^-2 to barns and the angstrom^-1 to cm^-1
 // will cancel for mu to give units: cm^-1
 double Material::linearAbsorpCoef(const double lambda) const {
-  return absorbXSection(NeutronAtom::ReferenceLambda) * 100. * numberDensity() *
-         lambda / NeutronAtom::ReferenceLambda;
+  return absorbXSection(lambda) * 100. * numberDensity();
 }
 
 // This must match the values that come from the scalar version
diff --git a/Framework/Kernel/test/MaterialTest.h b/Framework/Kernel/test/MaterialTest.h
index b76e05ea01440c599d389f7de47554032951deb9..8cd88863370676213f592c3b9278e28dae3dafe1 100644
--- a/Framework/Kernel/test/MaterialTest.h
+++ b/Framework/Kernel/test/MaterialTest.h
@@ -30,8 +30,6 @@ public:
     TS_ASSERT_EQUALS(empty.pressure(), 0.0);
 
     const double lambda(2.1);
-    TS_ASSERT_EQUALS(empty.cohScatterXSection(lambda), 0.0);
-    TS_ASSERT_EQUALS(empty.incohScatterXSection(lambda), 0.0);
     TS_ASSERT_EQUALS(empty.absorbXSection(lambda), 0.0);
   }
 
@@ -80,11 +78,9 @@ public:
 
     // check everything against another wavelength, only affects absorption
     const double lambda(2.1);
-    TS_ASSERT_DELTA(material.cohScatterXSection(lambda),
-                    material.cohScatterXSection(), 1e-04);
-    TS_ASSERT_DELTA(material.incohScatterXSection(lambda),
-                    material.incohScatterXSection(), 1e-04);
     TS_ASSERT_DELTA(material.absorbXSection(lambda), 5.93, 1e-02);
+    const double distance(0.05);
+    TS_ASSERT_DELTA(material.attenuation(distance, lambda), 0.01884, 1e-4);
   }
 
   // highly absorbing material
@@ -124,6 +120,8 @@ public:
 
     const double totXS = TiZr.totalScatterXSection();
     TS_ASSERT_DELTA(TiZr.totalScatterLengthSqrd(), 25. * totXS / M_PI, 1.e-4);
+    const double distance(0.05);
+    TS_ASSERT_DELTA(TiZr.attenuation(distance), 0., 1e-4);
   }
 
   /** Save then re-load from a NXS file */
@@ -146,8 +144,6 @@ public:
     TS_ASSERT_DELTA(testB.pressure(), 1.234, 1e-6);
     // This (indirectly) checks that the right element was found
     const double lambda(2.1);
-    TS_ASSERT_DELTA(testB.cohScatterXSection(lambda), 0.0184, 1e-02);
-    TS_ASSERT_DELTA(testB.incohScatterXSection(lambda), 5.08, 1e-02);
     TS_ASSERT_DELTA(testB.absorbXSection(lambda), 5.93, 1e-02);
   }
 
diff --git a/Framework/Kernel/test/MatrixTest.h b/Framework/Kernel/test/MatrixTest.h
index 4607e1316e31e73402ce91c45968b824c56ab13d..dea93d9e0c1b89ab832f31522d42faddeafd6b05 100644
--- a/Framework/Kernel/test/MatrixTest.h
+++ b/Framework/Kernel/test/MatrixTest.h
@@ -141,8 +141,9 @@ public:
     X[2] = Eval[2][1];
 
     std::vector<double> out = A * X;
+    using std::placeholders::_1;
     transform(X.begin(), X.end(), X.begin(),
-              std::bind2nd(std::multiplies<double>(), Diag[1][1]));
+              std::bind(std::multiplies<double>(), _1, Diag[1][1]));
     TS_ASSERT_DELTA(X[0], out[0], 0.0001);
     TS_ASSERT_DELTA(X[1], out[1], 0.0001);
     TS_ASSERT_DELTA(X[2], out[2], 0.0001);
diff --git a/Framework/Nexus/src/NexusClasses.cpp b/Framework/Nexus/src/NexusClasses.cpp
index 68d13fcc916a6f0095ca011b25e35482ac191e70..180de52517a69951e0d66331bfb6303b9594dce1 100644
--- a/Framework/Nexus/src/NexusClasses.cpp
+++ b/Framework/Nexus/src/NexusClasses.cpp
@@ -650,8 +650,9 @@ Kernel::Property *NXLog::createTimeSeries(const std::string &start_time,
     times.load();
     std::string units = times.attributes("units");
     if (units == "minutes") {
+      using std::placeholders::_1;
       std::transform(times(), times() + times.dim0(), times(),
-                     std::bind2nd(std::multiplies<double>(), 60));
+                     std::bind(std::multiplies<double>(), _1, 60));
     } else if (!units.empty() && units.substr(0, 6) != "second") {
       return nullptr;
     }
diff --git a/Framework/NexusGeometry/CMakeLists.txt b/Framework/NexusGeometry/CMakeLists.txt
index 52722b2e8e4f576415d77dceac12de4fbe096bec..c8f006933ed80f9e950ce73c5b6e903e4aa752fe 100644
--- a/Framework/NexusGeometry/CMakeLists.txt
+++ b/Framework/NexusGeometry/CMakeLists.txt
@@ -1,28 +1,31 @@
 set(SRC_FILES
+    src/H5ForwardCompatibility.cpp
     src/Hdf5Version.cpp
     src/InstrumentBuilder.cpp
+    src/JSONGeometryParser.cpp
     src/JSONInstrumentBuilder.cpp
     src/NexusGeometryParser.cpp
     src/NexusGeometrySave.cpp
-    src/JSONGeometryParser.cpp
+    src/NexusGeometryUtilities.cpp
     src/NexusShapeFactory.cpp
     src/TubeBuilder.cpp
-    src/TubeHelpers.cpp
-	src/H5ForwardCompatibility.cpp)
+    src/TubeHelpers.cpp)
 
 set(INC_FILES
+    inc/MantidNexusGeometry/AbstractLogger.h
     inc/MantidNexusGeometry/DllConfig.h
+    inc/MantidNexusGeometry/H5ForwardCompatibility.h
     inc/MantidNexusGeometry/Hdf5Version.h
     inc/MantidNexusGeometry/InstrumentBuilder.h
+    inc/MantidNexusGeometry/JSONGeometryParser.h
     inc/MantidNexusGeometry/JSONInstrumentBuilder.h
+    inc/MantidNexusGeometry/NexusGeometryDefinitions.h
     inc/MantidNexusGeometry/NexusGeometryParser.h
     inc/MantidNexusGeometry/NexusGeometrySave.h
-    inc/MantidNexusGeometry/JSONGeometryParser.h
+    inc/MantidNexusGeometry/NexusGeometryUtilities.h
     inc/MantidNexusGeometry/NexusShapeFactory.h
     inc/MantidNexusGeometry/TubeBuilder.h
-    inc/MantidNexusGeometry/TubeHelpers.h
-    inc/MantidNexusGeometry/NexusGeometryDefinitions.h
-	inc/MantidNexusGeometry/H5ForwardCompatibility.h)
+    inc/MantidNexusGeometry/TubeHelpers.h)
 
 set(TEST_FILES
     InstrumentBuilderTest.h
diff --git a/Framework/NexusGeometry/inc/MantidNexusGeometry/AbstractLogger.h b/Framework/NexusGeometry/inc/MantidNexusGeometry/AbstractLogger.h
new file mode 100644
index 0000000000000000000000000000000000000000..f06543d9f0bd99bf959189689ab302c589ef393c
--- /dev/null
+++ b/Framework/NexusGeometry/inc/MantidNexusGeometry/AbstractLogger.h
@@ -0,0 +1,67 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_NEXUSGEOMETRY_ABSTRACTLOGGER_H_
+#define MANTID_NEXUSGEOMETRY_ABSTRACTLOGGER_H_
+
+#include "MantidNexusGeometry/DllConfig.h"
+#include <memory>
+#include <string>
+
+namespace Mantid {
+namespace NexusGeometry {
+
+/**
+ * Abstract logger. Avoid hard-coded logging dependencies.
+ */
+class MANTID_NEXUSGEOMETRY_DLL AbstractLogger {
+public:
+  virtual void warning(const std::string &warning) = 0;
+  virtual void error(const std::string &error) = 0;
+  virtual ~AbstractLogger() {}
+};
+
+template <typename T> class LogAdapter : public AbstractLogger {
+private:
+  T *m_adaptee;
+
+public:
+  LogAdapter(T *adaptee) : m_adaptee(adaptee) {}
+  virtual void warning(const std::string &message) override {
+    m_adaptee->warning(message);
+  }
+  virtual void error(const std::string &message) override {
+    m_adaptee->error(message);
+  }
+};
+
+/**
+ * Creates an Adapter and instantiates and returns one as a unique_ptr
+ *
+ * Make it easy to wrap existing logging frameworks. Note that ownership of
+ * adaptee is NOT transferred to returned Logger.
+ */
+template <typename T> std::unique_ptr<AbstractLogger> makeLogger(T *adaptee) {
+  class Adapter : public AbstractLogger {
+  private:
+    T *m_adaptee;
+
+  public:
+    Adapter(T *adaptee) : m_adaptee(adaptee) {}
+    virtual void warning(const std::string &message) override {
+      m_adaptee->warning(message);
+    }
+    virtual void error(const std::string &message) override {
+      m_adaptee->error(message);
+    }
+  };
+  return std::make_unique<Adapter>(adaptee);
+}
+
+} // namespace NexusGeometry
+} // namespace Mantid
+
+#endif /* MANTID_NEXUSGEOMETRY_ABSTRACTLOGGER_H_ */
diff --git a/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusGeometryDefinitions.h b/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusGeometryDefinitions.h
index 1827613227647b6adb535c0b920e8b6dedae5146..2c930988f566ef1da37c6d9dff475a3e801d2c7e 100644
--- a/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusGeometryDefinitions.h
+++ b/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusGeometryDefinitions.h
@@ -9,6 +9,7 @@
 
 #include <H5Cpp.h>
 #include <string>
+#include <vector>
 
 namespace Mantid {
 namespace NexusGeometry {
@@ -26,7 +27,6 @@ const H5std_string NX_ENTRY = "NXentry";
 const H5std_string NX_CYLINDER = "NXcylindrical_geometry";
 const H5std_string NX_OFF = "NXoff_geometry";
 const H5std_string NX_INSTRUMENT = "NXinstrument";
-const H5std_string NX_CHAR = "NX_CHAR";
 const H5std_string NX_SOURCE = "NXsource";
 const H5std_string NX_TRANSFORMATIONS = "NXtransformations";
 
@@ -51,6 +51,18 @@ const H5std_string SHAPE = "shape";
 const H5std_string DETECTOR_IDS = "detector_number";
 const H5std_string DETECTOR_ID = "detector_id";
 
+// Processed Nexus definitions
+const H5std_string SPECTRA_COUNTS =
+    "detector_count"; // Number of detectors contributing to each spectra. NOT
+                      // Nexus compliant
+const H5std_string SPECTRA_NUMBERS = "spectra";     // NOT Nexus compliant
+const H5std_string DETECTOR_LIST = "detector_list"; // Note this is identical to
+                                                    // DETECTOR_ID, but is NOT
+                                                    // Nexus compliant
+const H5std_string DETECTOR_INDEX =
+    "detector_index"; // indices of detectors contributing to spectra. NOT Nexus
+                      // compliant
+
 const H5std_string TRANSFORMATION_TYPE = "transformation_type";
 
 const H5std_string METRES = "m";
@@ -61,9 +73,8 @@ const H5std_string VECTOR = "vector";
 
 const double DEGREES_IN_SEMICIRCLE = 180.0;
 const double PRECISION = 1e-5;
-const double PI = M_PI;
 
-const H5std_string DEFAULT_ROOT_PATH = "raw_data_1";
+const H5std_string DEFAULT_ROOT_ENTRY_NAME = "raw_data_1";
 const H5::DataSpace SCALAR(H5S_SCALAR);
 
 // JSON PARSER SPECIFICS
diff --git a/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusGeometryParser.h b/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusGeometryParser.h
index 0eb4c0b3e58ecde389fdbc7e1e4e6de424affa17..8fa60dbd1700e9ecd6484eab973d328a4cc600d9 100644
--- a/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusGeometryParser.h
+++ b/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusGeometryParser.h
@@ -7,6 +7,7 @@
 #ifndef MANTIDNEXUSGEOMETRY_PARSER_H_
 #define MANTIDNEXUSGEOMETRY_PARSER_H_
 
+#include "MantidNexusGeometry/AbstractLogger.h"
 #include "MantidNexusGeometry/DllConfig.h"
 #include <memory>
 #include <string>
@@ -17,43 +18,13 @@ class Instrument;
 }
 namespace NexusGeometry {
 
-/**
- * Abstract logger. Avoid hard-coded logging dependencies.
- */
-class MANTID_NEXUSGEOMETRY_DLL Logger {
-public:
-  virtual void warning(const std::string &warning) = 0;
-  virtual void error(const std::string &error) = 0;
-  virtual ~Logger() {}
-};
-
-/**
- * Creates an Adapter and instantiates and returns one as a unique_ptr
- *
- * Make it easy to wrap existing logging frameworks. Note that ownership of
- * adaptee is NOT transferred to returned Logger.
- */
-template <typename T> std::unique_ptr<Logger> makeLogger(T *adaptee) {
-
-  struct Adapter : public Logger {
-    T *m_adaptee;
-    Adapter(T *adaptee) : m_adaptee(adaptee) {}
-    virtual void warning(const std::string &message) override {
-      m_adaptee->warning(message);
-    }
-    virtual void error(const std::string &message) override {
-      m_adaptee->error(message);
-    }
-  };
-  return std::make_unique<Adapter>(adaptee);
-}
-
 namespace NexusGeometryParser {
 /** createInstrument : Responsible for parsing a nexus geometry file and
   creating an in-memory Mantid instrument.
 */
 MANTID_NEXUSGEOMETRY_DLL std::unique_ptr<const Mantid::Geometry::Instrument>
-createInstrument(const std::string &fileName, std::unique_ptr<Logger> logger);
+createInstrument(const std::string &fileName,
+                 std::unique_ptr<AbstractLogger> logger);
 MANTID_NEXUSGEOMETRY_DLL std::string
 getMangledName(const std::string &fileName, const std::string &instName);
 } // namespace NexusGeometryParser
diff --git a/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusGeometrySave.h b/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusGeometrySave.h
index cc0593b02d7498c274d059a4baebbec4dba30244..413e1da1058e4e0cd00cdc4d987ab3f9c07d0626 100644
--- a/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusGeometrySave.h
+++ b/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusGeometrySave.h
@@ -17,9 +17,12 @@
 #ifndef MANTID_NEXUSGEOMETRY_NEXUSGEOMETRYSAVE_H_
 #define MANTID_NEXUSGEOMETRY_NEXUSGEOMETRYSAVE_H_
 
+#include "MantidAPI/MatrixWorkspace.h"
+#include "MantidNexusGeometry/AbstractLogger.h"
 #include "MantidNexusGeometry/DllConfig.h"
 #include <memory>
 #include <string>
+#include <vector>
 
 namespace Mantid {
 
@@ -35,6 +38,9 @@ namespace Geometry {
 class ComponentInfo;
 class DetectorInfo;
 } // namespace Geometry
+namespace API {
+class MatrixWorkspace;
+}
 
 namespace NexusGeometry {
 namespace NexusGeometrySave {
@@ -42,13 +48,21 @@ namespace NexusGeometrySave {
 MANTID_NEXUSGEOMETRY_DLL void
 saveInstrument(const Geometry::ComponentInfo &compInfo,
                const Geometry::DetectorInfo &detInfo,
-               const std::string &fullPath, const std::string &rootPath,
+               const std::string &fullPath, const std::string &rootName,
+               AbstractLogger &logger, bool append = false,
+               Kernel::ProgressBase *reporter = nullptr);
+
+MANTID_NEXUSGEOMETRY_DLL void
+saveInstrument(const Mantid::API::MatrixWorkspace &ws,
+               const std::string &fullPath, const std::string &rootName,
+               AbstractLogger &logger, bool append = false,
                Kernel::ProgressBase *reporter = nullptr);
 
 MANTID_NEXUSGEOMETRY_DLL void saveInstrument(
     const std::pair<std::unique_ptr<Geometry::ComponentInfo>,
                     std::unique_ptr<Geometry::DetectorInfo>> &instrPair,
-    const std::string &fullPath, const std::string &rootPath,
+    const std::string &fullPath, const std::string &rootName,
+    AbstractLogger &logger, bool append = false,
     Kernel::ProgressBase *reporter = nullptr);
 } // namespace NexusGeometrySave
 } // namespace NexusGeometry
diff --git a/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusGeometryUtilities.h b/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusGeometryUtilities.h
new file mode 100644
index 0000000000000000000000000000000000000000..b3a8c605c319d60c3021e9be9627a51ad03b6041
--- /dev/null
+++ b/Framework/NexusGeometry/inc/MantidNexusGeometry/NexusGeometryUtilities.h
@@ -0,0 +1,36 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+#ifndef MANTID_NEXUSGEOMETRY_NEXUSGEOMETRYUTILITIES_H_
+#define MANTID_NEXUSGEOMETRY_NEXUSGEOMETRYUTILITIES_H_
+
+#include "MantidNexusGeometry/DllConfig.h"
+#include <H5Cpp.h>
+#include <boost/optional.hpp>
+#include <vector>
+
+namespace Mantid {
+namespace NexusGeometry {
+
+// Utilities common for parsing and saving
+namespace utilities {
+
+boost::optional<H5::DataSet> findDataset(const H5::Group &parentGroup,
+                                         const H5std_string &name);
+
+boost::optional<H5::Group> findGroup(const H5::Group &parentGroup,
+                                     const H5std_string &classType);
+
+H5::Group findGroupOrThrow(const H5::Group &parentGroup,
+                           const H5std_string &classType);
+
+std::vector<H5::Group> findGroups(const H5::Group &parentGroup,
+                                  const H5std_string &classType);
+} // namespace utilities
+} // namespace NexusGeometry
+} // namespace Mantid
+
+#endif /* MANTID_NEXUSGEOMETRY_NEXUSGEOMETRYUTILITIES_H_ */
diff --git a/Framework/NexusGeometry/src/JSONGeometryParser.cpp b/Framework/NexusGeometry/src/JSONGeometryParser.cpp
index 9529bd55cff1f6d46643836394ba53c5363d979a..cd2a0956938c3622d0b73cc16b3481f72760f8fc 100644
--- a/Framework/NexusGeometry/src/JSONGeometryParser.cpp
+++ b/Framework/NexusGeometry/src/JSONGeometryParser.cpp
@@ -590,7 +590,7 @@ void JSONGeometryParser::parse(const std::string &jsonGeometry) {
 }
 
 double JSONGeometryParser::degreesToRadians(const double degrees) noexcept {
-  return degrees * PI / DEGREES_IN_SEMICIRCLE;
+  return degrees * M_PI / DEGREES_IN_SEMICIRCLE;
 }
 } // namespace NexusGeometry
 } // namespace Mantid
diff --git a/Framework/NexusGeometry/src/NexusGeometryParser.cpp b/Framework/NexusGeometry/src/NexusGeometryParser.cpp
index ce0a195ee47257800bda0b104729c961686896cd..b051e8bd49575a174d0e9eba3c6a6bfbf9b34038 100644
--- a/Framework/NexusGeometry/src/NexusGeometryParser.cpp
+++ b/Framework/NexusGeometry/src/NexusGeometryParser.cpp
@@ -12,10 +12,11 @@
 #include "MantidGeometry/Rendering/ShapeInfo.h"
 #include "MantidKernel/ChecksumHelper.h"
 #include "MantidKernel/EigenConversionHelpers.h"
-#include "MantidNexusGeometry/NexusGeometryDefinitions.h"
-
+#include "MantidNexusGeometry/AbstractLogger.h"
 #include "MantidNexusGeometry/Hdf5Version.h"
 #include "MantidNexusGeometry/InstrumentBuilder.h"
+#include "MantidNexusGeometry/NexusGeometryDefinitions.h"
+#include "MantidNexusGeometry/NexusGeometryUtilities.h"
 #include "MantidNexusGeometry/NexusShapeFactory.h"
 #include "MantidNexusGeometry/TubeHelpers.h"
 #include <Eigen/Core>
@@ -106,7 +107,7 @@ std::vector<ValueType> extractVector(const DataSet &data) {
 class Parser {
 private:
   // Logger object
-  std::unique_ptr<Logger> m_logger;
+  std::unique_ptr<AbstractLogger> m_logger;
 
   /**
    * The function allows us to determine where problems are and logs key
@@ -194,60 +195,10 @@ private:
     return subGroups;
   }
 
-  /// Find a single dataset inside parent group (returns first match). Unset for
-  /// no match.
-  boost::optional<DataSet> findDataset(const Group &parentGroup,
-                                       const H5std_string &name) {
-    // Iterate over children, and determine if a group
-    for (hsize_t i = 0; i < parentGroup.getNumObjs(); ++i) {
-      if (parentGroup.getObjTypeByIdx(i) == DATASET_TYPE) {
-        H5std_string childPath = parentGroup.getObjnameByIdx(i);
-        // Open the sub group
-        if (childPath == name) {
-          auto childDataset = openDataSet(parentGroup, childPath);
-          return boost::optional<DataSet>(childDataset);
-        }
-      }
-    }
-    return boost::optional<DataSet>{}; // Empty
-  }
-  /// Find a single group inside parent (returns first match). class type must
-  /// match NX_class. Unset for no match.
-  boost::optional<Group> findGroup(const Group &parentGroup,
-                                   const H5std_string &CLASS_TYPE) {
-    // Iterate over children, and determine if a group
-    for (hsize_t i = 0; i < parentGroup.getNumObjs(); ++i) {
-      if (parentGroup.getObjTypeByIdx(i) == GROUP_TYPE) {
-        H5std_string childPath = parentGroup.getObjnameByIdx(i);
-        // Open the sub group
-        auto childGroup = parentGroup.openGroup(childPath);
-        // Iterate through attributes to find NX_class
-        for (uint32_t attribute_index = 0;
-             attribute_index < static_cast<uint32_t>(childGroup.getNumAttrs());
-             ++attribute_index) {
-          // Test attribute at current index for NX_class
-          Attribute attribute = childGroup.openAttribute(attribute_index);
-          if (attribute.getName() == NX_CLASS) {
-            // Get attribute data type
-            DataType dataType = attribute.getDataType();
-            // Get the NX_class type
-            H5std_string classType;
-            attribute.read(dataType, classType);
-            // If group of correct type, return the childGroup
-            if (classType == CLASS_TYPE) {
-              return boost::optional<Group>(childGroup);
-            }
-          }
-        }
-      }
-    }
-    return boost::optional<Group>{}; // Empty
-  }
-
   // Get the instrument name
   std::string instrumentName(const Group &root) {
-    Group entryGroup = *findGroup(root, NX_ENTRY);
-    Group instrumentGroup = *findGroup(entryGroup, NX_INSTRUMENT);
+    Group entryGroup = *utilities::findGroup(root, NX_ENTRY);
+    Group instrumentGroup = *utilities::findGroup(entryGroup, NX_INSTRUMENT);
     return get1DStringDataset("name", instrumentGroup);
   }
 
@@ -426,7 +377,7 @@ private:
         double angle = magnitude;
         if (isDegrees(transformUnits)) {
           // Convert angle from degrees to radians
-          angle *= PI / DEGREES_IN_SEMICIRCLE;
+          angle *= M_PI / DEGREES_IN_SEMICIRCLE;
         }
         Eigen::AngleAxisd rotation(angle, transformVector);
         transforms = rotation * transforms;
@@ -442,7 +393,7 @@ private:
   std::vector<Mantid::detid_t> getDetectorIds(const Group &detectorGroup) {
 
     std::vector<Mantid::detid_t> detIds;
-    if (!findDataset(detectorGroup, DETECTOR_IDS))
+    if (!utilities::findDataset(detectorGroup, DETECTOR_IDS))
       throw std::invalid_argument("Mantid requires the following named dataset "
                                   "to be present in NXDetectors: " +
                                   DETECTOR_IDS);
@@ -632,11 +583,12 @@ private:
   // Parse source and add to instrument
   void parseAndAddSource(const H5File &file, const Group &root,
                          InstrumentBuilder &builder) {
-    Group entryGroup = *findGroup(root, NX_ENTRY);
-    Group instrumentGroup = *findGroup(entryGroup, NX_INSTRUMENT);
-    Group sourceGroup = *findGroup(instrumentGroup, NX_SOURCE);
+    Group entryGroup = utilities::findGroupOrThrow(root, NX_ENTRY);
+    Group instrumentGroup =
+        utilities::findGroupOrThrow(entryGroup, NX_INSTRUMENT);
+    Group sourceGroup = utilities::findGroupOrThrow(instrumentGroup, NX_SOURCE);
     std::string sourceName = "Unspecfied";
-    if (findDataset(sourceGroup, "name"))
+    if (utilities::findDataset(sourceGroup, "name"))
       sourceName = get1DStringDataset("name", sourceGroup);
     auto sourceTransformations = getTransformations(file, sourceGroup);
     auto defaultPos = Eigen::Vector3d(0.0, 0.0, 0.0);
@@ -646,13 +598,13 @@ private:
   // Parse sample and add to instrument
   void parseAndAddSample(const H5File &file, const Group &root,
                          InstrumentBuilder &builder) {
-    Group entryGroup = *findGroup(root, NX_ENTRY);
-    Group sampleGroup = *findGroup(entryGroup, NX_SAMPLE);
+    Group entryGroup = utilities::findGroupOrThrow(root, NX_ENTRY);
+    Group sampleGroup = utilities::findGroupOrThrow(entryGroup, NX_SAMPLE);
     auto sampleTransforms = getTransformations(file, sampleGroup);
     Eigen::Vector3d samplePos =
         sampleTransforms * Eigen::Vector3d(0.0, 0.0, 0.0);
     std::string sampleName = "Unspecified";
-    if (findDataset(sampleGroup, "name"))
+    if (utilities::findDataset(sampleGroup, "name"))
       sampleName = get1DStringDataset("name", sampleGroup);
     builder.addSample(sampleName, samplePos);
   }
@@ -668,7 +620,7 @@ private:
       for (auto &inst : instrumentGroups) {
         std::vector<Group> monitorGroups = openSubGroups(inst, NX_MONITOR);
         for (auto &monitor : monitorGroups) {
-          if (!findDataset(monitor, DETECTOR_ID))
+          if (!utilities::findDataset(monitor, DETECTOR_ID))
             throw std::invalid_argument("NXmonitors must have " + DETECTOR_ID);
           auto detectorId = get1DDataset<int64_t>(monitor, DETECTOR_ID)[0];
           bool proxy = false;
@@ -683,7 +635,7 @@ private:
   }
 
 public:
-  explicit Parser(std::unique_ptr<Logger> &&logger)
+  explicit Parser(std::unique_ptr<AbstractLogger> &&logger)
       : m_logger(std::move(logger)) {}
 
   std::unique_ptr<const Mantid::Geometry::Instrument>
@@ -701,7 +653,7 @@ public:
       // Absolute bank rotation
       auto bankRotation = Eigen::Quaterniond(transforms.rotation());
       std::string bankName;
-      if (findDataset(detectorGroup, BANK_NAME))
+      if (utilities::findDataset(detectorGroup, BANK_NAME))
         bankName = get1DStringDataset(BANK_NAME,
                                       detectorGroup); // local_name is optional
       builder.addBank(bankName, bankPos, bankRotation);
@@ -750,7 +702,7 @@ public:
 
 std::unique_ptr<const Geometry::Instrument>
 NexusGeometryParser::createInstrument(const std::string &fileName,
-                                      std::unique_ptr<Logger> logger) {
+                                      std::unique_ptr<AbstractLogger> logger) {
 
   const H5File file(fileName, H5F_ACC_RDONLY);
   auto rootGroup = file.openGroup("/");
diff --git a/Framework/NexusGeometry/src/NexusGeometrySave.cpp b/Framework/NexusGeometry/src/NexusGeometrySave.cpp
index 36c4ff1b2882bdebba4daf9a1e768914d908145c..d7bd8e9cdea24080c4f22c7a29d526ae8120b873 100644
--- a/Framework/NexusGeometry/src/NexusGeometrySave.cpp
+++ b/Framework/NexusGeometry/src/NexusGeometrySave.cpp
@@ -15,25 +15,41 @@
  */
 
 #include "MantidNexusGeometry/NexusGeometrySave.h"
+#include "MantidAPI/SpectraDetectorTypes.h"
+#include "MantidAPI/SpectrumInfo.h"
 #include "MantidGeometry/Instrument/ComponentInfo.h"
 #include "MantidGeometry/Instrument/ComponentInfoBankHelpers.h"
 #include "MantidGeometry/Instrument/DetectorInfo.h"
+#include "MantidIndexing/IndexInfo.h"
 #include "MantidKernel/EigenConversionHelpers.h"
 #include "MantidKernel/ProgressBase.h"
 #include "MantidNexusGeometry/H5ForwardCompatibility.h"
 #include "MantidNexusGeometry/NexusGeometryDefinitions.h"
+#include "MantidNexusGeometry/NexusGeometryUtilities.h"
 #include <H5Cpp.h>
 #include <algorithm>
 #include <boost/filesystem/operations.hpp>
 #include <cmath>
 #include <list>
 #include <memory>
+#include <regex>
 #include <string>
 
 namespace Mantid {
 namespace NexusGeometry {
 namespace NexusGeometrySave {
 
+/*
+ * Helper container for spectrum mapping information info
+ */
+struct SpectraMappings {
+  std::vector<int32_t> detector_index;
+  std::vector<int32_t> detector_count;
+  std::vector<int32_t> detector_list;
+  std::vector<int32_t> spectra_ids;
+  size_t number_spec = 0;
+  size_t number_dets = 0;
+};
 /*
  * Function toStdVector (Overloaded). Store data in Mantid::Kernel::V3D vector
  * into std::vector<double> vector. Used by saveInstrument to write array-type
@@ -43,7 +59,7 @@ namespace NexusGeometrySave {
  * @return std::vector<double> vector containing data values in
  * Mantid::Kernel::V3D format.
  */
-inline std::vector<double> toStdVector(const V3D &data) {
+std::vector<double> toStdVector(const V3D &data) {
   std::vector<double> stdVector;
   stdVector.reserve(3);
   stdVector.push_back(data.X());
@@ -61,7 +77,7 @@ inline std::vector<double> toStdVector(const V3D &data) {
  * @return std::vector<double> vector containing data values in
  * Eigen::Vector3d format
  */
-inline std::vector<double> toStdVector(const Eigen::Vector3d &data) {
+std::vector<double> toStdVector(const Eigen::Vector3d &data) {
   return toStdVector(Kernel::toV3D(data));
 }
 
@@ -75,8 +91,7 @@ inline std::vector<double> toStdVector(const Eigen::Vector3d &data) {
  * @param precision : double precision specifier
  * @return true if all elements are approx zero, else false.
  */
-inline bool isApproxZero(const std::vector<double> &data,
-                         const double &precision) {
+bool isApproxZero(const std::vector<double> &data, const double &precision) {
 
   return std::all_of(data.begin(), data.end(),
                      [&precision](const double &element) {
@@ -85,13 +100,12 @@ inline bool isApproxZero(const std::vector<double> &data,
 }
 
 // overload. return true if vector is approx to zero
-inline bool isApproxZero(const Eigen::Vector3d &data, const double &precision) {
+bool isApproxZero(const Eigen::Vector3d &data, const double &precision) {
   return data.isApprox(Eigen::Vector3d(0, 0, 0), precision);
 }
 
 // overload. returns true is angle is approx to zero
-inline bool isApproxZero(const Eigen::Quaterniond &data,
-                         const double &precision) {
+bool isApproxZero(const Eigen::Quaterniond &data, const double &precision) {
   return data.isApprox(Eigen::Quaterniond(1, 0, 0, 0), precision);
 }
 
@@ -103,7 +117,7 @@ inline bool isApproxZero(const Eigen::Quaterniond &data,
  * @param str : std::string
  * @return string datatype of size = length of input string
  */
-inline H5::StrType strTypeOfSize(const std::string &str) {
+H5::StrType strTypeOfSize(const std::string &str) {
   H5::StrType stringType(H5::PredType::C_S1, str.size());
   return stringType;
 }
@@ -116,12 +130,15 @@ inline H5::StrType strTypeOfSize(const std::string &str) {
  * @param attrname : attribute name.
  * @param attrVal : string attribute value to be stored in attribute.
  */
-inline void writeStrDataset(H5::Group &grp, const std::string &dSetName,
-                            const std::string &dSetVal,
-                            const H5::DataSpace &dataSpace = SCALAR) {
-  H5::StrType dataType = strTypeOfSize(dSetVal);
-  H5::DataSet dSet = grp.createDataSet(dSetName, dataType, dataSpace);
-  dSet.write(dSetVal, dataType);
+void writeStrDataset(H5::Group &grp, const std::string &dSetName,
+                     const std::string &dSetVal,
+                     const H5::DataSpace &dataSpace = SCALAR) {
+  // TODO. may need to review if we shoud in fact replace.
+  if (!utilities::findDataset(grp, dSetName)) {
+    H5::StrType dataType = strTypeOfSize(dSetVal);
+    H5::DataSet dSet = grp.createDataSet(dSetName, dataType, dataSpace);
+    dSet.write(dSetVal, dataType);
+  }
 }
 
 /*
@@ -132,12 +149,15 @@ inline void writeStrDataset(H5::Group &grp, const std::string &dSetName,
  * @param attrname : attribute name.
  * @param attrVal : string attribute value to be stored in attribute.
  */
-inline void writeStrAttribute(H5::Group &grp, const std::string &attrName,
-                              const std::string &attrVal,
-                              const H5::DataSpace &dataSpace = SCALAR) {
-  H5::StrType dataType = strTypeOfSize(attrVal);
-  H5::Attribute attribute = grp.createAttribute(attrName, dataType, dataSpace);
-  attribute.write(dataType, attrVal);
+void writeStrAttribute(H5::Group &grp, const std::string &attrName,
+                       const std::string &attrVal,
+                       const H5::DataSpace &dataSpace = SCALAR) {
+  if (!grp.attrExists(attrName)) {
+    H5::StrType dataType = strTypeOfSize(attrVal);
+    H5::Attribute attribute =
+        grp.createAttribute(attrName, dataType, dataSpace);
+    attribute.write(dataType, attrVal);
+  }
 }
 
 /*
@@ -149,21 +169,14 @@ inline void writeStrAttribute(H5::Group &grp, const std::string &attrName,
  * @param attrname : attribute name.
  * @param attrVal : string attribute value to be stored in attribute.
  */
-inline void writeStrAttribute(H5::DataSet &dSet, const std::string &attrName,
-                              const std::string &attrVal,
-                              const H5::DataSpace &dataSpace = SCALAR) {
-  H5::StrType dataType = strTypeOfSize(attrVal);
-  auto attribute = dSet.createAttribute(attrName, dataType, dataSpace);
-  attribute.write(dataType, attrVal);
-}
-
-// function to create a simple sub-group that has a nexus class attribute,
-// inside a parent group.
-inline H5::Group simpleNXSubGroup(H5::Group &parent, const std::string &name,
-                                  const std::string &nexusAttribute) {
-  H5::Group subGroup = parent.createGroup(name);
-  writeStrAttribute(subGroup, NX_CLASS, nexusAttribute);
-  return subGroup;
+void writeStrAttribute(H5::DataSet &dSet, const std::string &attrName,
+                       const std::string &attrVal,
+                       const H5::DataSpace &dataSpace = SCALAR) {
+  if (!dSet.attrExists(attrName)) {
+    H5::StrType dataType = strTypeOfSize(attrVal);
+    auto attribute = dSet.createAttribute(attrName, dataType, dataSpace);
+    attribute.write(dataType, attrVal);
+  }
 }
 
 /*
@@ -175,9 +188,9 @@ inline H5::Group simpleNXSubGroup(H5::Group &parent, const std::string &name,
  * @param compInfo : Component Info Instrument cache
  * @param idx : index of bank in cache.
  */
-inline void writeXYZPixeloffset(H5::Group &grp,
-                                const Geometry::ComponentInfo &compInfo,
-                                const size_t idx) {
+void writeXYZPixeloffset(H5::Group &grp,
+                         const Geometry::ComponentInfo &compInfo,
+                         const size_t idx) {
 
   H5::DataSet xPixelOffset, yPixelOffset, zPixelOffset;
   auto childrenDetectors = compInfo.detectorsInSubtree(idx);
@@ -235,6 +248,18 @@ inline void writeXYZPixeloffset(H5::Group &grp,
   }
 }
 
+template <typename T>
+void write1DIntDataset(H5::Group &grp, const H5std_string &name,
+                       const std::vector<T> &container) {
+  const int rank = 1;
+  hsize_t dims[1] = {static_cast<hsize_t>(container.size())};
+
+  H5::DataSpace space = H5Screate_simple(rank, dims, nullptr);
+
+  auto dataset = grp.createDataSet(name, H5::PredType::NATIVE_INT, space);
+  dataset.write(container.data(), H5::PredType::NATIVE_INT, space);
+}
+
 /*
  * Function: writeNXDetectorNumber
  * For use with NXdetector group. Writes the detector numbers for all detector
@@ -264,17 +289,28 @@ void writeNXDetectorNumber(H5::Group &grp,
                   bankDetIDs.push_back(detectorIDs[index]);
                 });
 
-  const auto nDetectorsInBank = static_cast<hsize_t>(bankDetIDs.size());
+  write1DIntDataset(grp, DETECTOR_IDS, bankDetIDs);
+}
 
-  int rank = 1;
-  hsize_t dims[static_cast<hsize_t>(1)];
-  dims[0] = nDetectorsInBank;
+// Write the count of how many detectors contribute to each spectra
+void writeDetectorCount(H5::Group &grp, const SpectraMappings &mappings) {
+  write1DIntDataset(grp, SPECTRA_COUNTS, mappings.detector_count);
+}
 
-  H5::DataSpace space = H5Screate_simple(rank, dims, nullptr);
+// Write the detectors ids ordered by spectra index 0 - N for each NXDetector
+void writeDetectorList(H5::Group &grp, const SpectraMappings &mappings) {
+  write1DIntDataset(grp, DETECTOR_LIST, mappings.detector_list);
+}
+
+// Write the detector indexes. This provides offsets into the detector_list and
+// is sized to the number of spectra
+void writeDetectorIndex(H5::Group &grp, const SpectraMappings &mappings) {
+  write1DIntDataset(grp, DETECTOR_INDEX, mappings.detector_index);
+}
 
-  detectorNumber =
-      grp.createDataSet(DETECTOR_IDS, H5::PredType::NATIVE_INT, space);
-  detectorNumber.write(bankDetIDs.data(), H5::PredType::NATIVE_INT, space);
+// Write the spectra numbers for each spectra
+void writeSpectra(H5::Group &grp, const SpectraMappings &mappings) {
+  write1DIntDataset(grp, SPECTRA_NUMBERS, mappings.spectra_ids);
 }
 
 /*
@@ -301,12 +337,17 @@ void writeNXMonitorNumber(H5::Group &grp, const int monitorID) {
 
   // these DataSets are duplicates of each other. written to the group to
   // handle the naming inconsistency. probably temporary.
-  detectorNumber =
-      grp.createDataSet(DETECTOR_IDS, H5::PredType::NATIVE_INT, space);
-  detectorNumber.write(&monitorID, H5::PredType::NATIVE_INT, space);
+  if (!utilities::findDataset(grp, DETECTOR_IDS)) {
+    detectorNumber =
+        grp.createDataSet(DETECTOR_IDS, H5::PredType::NATIVE_INT, space);
+    detectorNumber.write(&monitorID, H5::PredType::NATIVE_INT, space);
+  }
+  if (!utilities::findDataset(grp, DETECTOR_ID)) {
 
-  detector_id = grp.createDataSet(DETECTOR_ID, H5::PredType::NATIVE_INT, space);
-  detector_id.write(&monitorID, H5::PredType::NATIVE_INT, space);
+    detector_id =
+        grp.createDataSet(DETECTOR_ID, H5::PredType::NATIVE_INT, space);
+    detector_id.write(&monitorID, H5::PredType::NATIVE_INT, space);
+  }
 }
 
 /*
@@ -412,7 +453,7 @@ inline void writeOrientation(H5::Group &grp, const Eigen::Quaterniond &rotation,
   hsize_t ddims[static_cast<hsize_t>(1)]; // dimensions of dataset
   ddims[0] = static_cast<hsize_t>(1);     // datapoints in dataset dimension 0
 
-  angle = std::acos(rotation.w()) * (360.0 / PI); // angle magnitude
+  angle = std::acos(rotation.w()) * (360.0 / M_PI); // angle magnitude
   Eigen::Vector3d axisOfRotation = rotation.vec().normalized(); // angle axis
   std::vector<double> stdNormAxis =
       toStdVector(axisOfRotation); // convert to std::vector
@@ -450,277 +491,60 @@ inline void writeOrientation(H5::Group &grp, const Eigen::Quaterniond &rotation,
   dependsOn.write(strSize, dependency);
 }
 
-/*
- * Function: NXInstrument
- * for NXentry parent (root group). Produces an NXinstrument group in the
- * parent group, and writes Nexus compliant datasets and metadata stored in
- * attributes to the new group.
- *
- * @param parent : parent group in which to write the NXinstrument group.
- * @param compInfo : componentInfo object.
- * @return NXinstrument group, to be passed into children save methods.
- */
-H5::Group NXInstrument(const H5::Group &parent,
-                       const Geometry::ComponentInfo &compInfo) {
-
-  std::string nameInCache = compInfo.name(compInfo.root());
-  std::string instrName =
-      nameInCache.empty() ? "unspecified_instrument" : nameInCache;
-  H5::Group childGroup = parent.createGroup(instrName);
-
-  writeStrDataset(childGroup, NAME, instrName);
-  writeStrAttribute(childGroup, NX_CLASS, NX_INSTRUMENT);
-
-  std::string defaultShortName = instrName.substr(0, 3);
-  H5::DataSet name = childGroup.openDataSet(NAME);
-  writeStrAttribute(name, SHORT_NAME, defaultShortName);
-  return childGroup;
-}
-
-/*
- * Function: saveNXSample
- * For NXentry parent (root group). Produces an NXsample group in the parent
- * group, and writes the Nexus compliant datasets and metadata stored in
- * attributes to the new group.
- *
- * @param parent : parent group in which to write the NXinstrument group.
- * @param compInfo : componentInfo object.
- */
-void saveNXSample(const H5::Group &parentGroup,
-                  const Geometry::ComponentInfo &compInfo) {
-
-  std::string nameInCache = compInfo.name(compInfo.sample());
-  std::string sampleName =
-      nameInCache.empty() ? "unspecified_sample" : nameInCache;
-
-  H5::Group childGroup = parentGroup.createGroup(sampleName);
-  writeStrAttribute(childGroup, NX_CLASS, NX_SAMPLE);
-  writeStrDataset(childGroup, NAME, sampleName);
-}
-
-/*
- * Function: saveNXSource
- * For NXentry (root group). Produces an NXsource group in the parent group,
- * and writes the Nexus compliant datasets and metadata stored in attributes
- * to the new group.
- *
- * @param parent : parent group in which to write the NXinstrument group.
- * @param compInfo : componentInfo object.
- */
-void saveNXSource(const H5::Group &parentGroup,
-                  const Geometry::ComponentInfo &compInfo) {
-
-  size_t index = compInfo.source();
-
-  std::string nameInCache = compInfo.name(index);
-  std::string sourceName =
-      nameInCache.empty() ? "unspecified_source" : nameInCache;
-
-  std::string dependency = NO_DEPENDENCY;
-
-  Eigen::Vector3d position =
-      Mantid::Kernel::toVector3d(compInfo.position(index));
-  Eigen::Quaterniond rotation =
-      Mantid::Kernel::toQuaterniond(compInfo.rotation(index));
-
-  bool locationIsOrigin = isApproxZero(position, PRECISION);
-  bool orientationIsZero = isApproxZero(rotation, PRECISION);
-
-  H5::Group childGroup = parentGroup.createGroup(sourceName);
-  writeStrAttribute(childGroup, NX_CLASS, NX_SOURCE);
-
-  // do not write NXtransformations if there is no translation or rotation
-  if (!(locationIsOrigin && orientationIsZero)) {
-    H5::Group transformations =
-        simpleNXSubGroup(childGroup, TRANSFORMATIONS, NX_TRANSFORMATIONS);
-
-    // self, ".", is the default first NXsource dependency in the chain. first
-    // check translation in NXsource is non-zero, and set dependency to
-    // location if true and write location. Then check if orientation in
-    // NXsource is non-zero, replace dependency with orientation if true. If
-    // neither orientation nor location are non-zero, NXsource is self
-    // dependent.
-    if (!locationIsOrigin) {
-      dependency = H5_OBJ_NAME(transformations) + "/" + LOCATION;
-      writeLocation(transformations, position);
-    }
-    if (!orientationIsZero) {
-      dependency = H5_OBJ_NAME(transformations) + "/" + ORIENTATION;
-
-      // If location dataset is written to group also, then dependency for
-      // orientation dataset containg the rotation transformation will be
-      // location. Else dependency for orientation is self.
-      std::string rotationDependency =
-          locationIsOrigin ? NO_DEPENDENCY
-                           : H5_OBJ_NAME(transformations) + "/" + LOCATION;
-      writeOrientation(transformations, rotation, rotationDependency);
-    }
+SpectraMappings makeMappings(const Geometry::ComponentInfo &compInfo,
+                             const detid2index_map &detToIndexMap,
+                             const Indexing::IndexInfo &indexInfo,
+                             const API::SpectrumInfo &specInfo,
+                             const std::vector<Mantid::detid_t> &detIds,
+                             size_t index) {
+  auto childrenDetectors = compInfo.detectorsInSubtree(index);
+  // local to this nxdetector
+  std::map<size_t, int> detector_count_map;
+  // We start knowing only the detector index, we have to establish spectra from
+  // that.
+  for (const auto det_index : childrenDetectors) {
+    auto detector_id = detIds[det_index];
+
+    auto spectrum_index = detToIndexMap.at(detector_id);
+    detector_count_map[spectrum_index]++; // Attribute detector to a give
+                                          // spectrum_index
   }
-
-  writeStrDataset(childGroup, NAME, sourceName);
-  writeStrDataset(childGroup, DEPENDS_ON, dependency);
-}
-
-/*
- * Function: saveNXMonitor
- * For NXinstrument parent (component info root). Produces an NXmonitor
- * groups from Component info, and saves it in the parent
- * group, along with the Nexus compliant datasets, and metadata stored in
- * attributes to the new group.
- *
- * @param parentGroup : parent group in which to write the NXinstrument group.
- * @param compInfo : componentInfo object.
- * @param monitorID :  ID of the specific monitor.
- * @param index :  index of the specific monitor in the Instrument cache.
- */
-void saveNXMonitor(const H5::Group &parentGroup,
-                   const Geometry::ComponentInfo &compInfo, const int monitorId,
-                   const size_t index) {
-
-  // if the component is unnamed sets the name as unspecified with the
-  // location of the component in the cache
-  std::string nameInCache = compInfo.name(index);
-  std::string monitorName = nameInCache.empty()
-                                ? "unspecified_monitor_" + std::to_string(index)
-                                : nameInCache;
-
-  Eigen::Vector3d position =
-      Mantid::Kernel::toVector3d(compInfo.position(index));
-  Eigen::Quaterniond rotation =
-      Mantid::Kernel::toQuaterniond(compInfo.rotation(index));
-
-  std::string dependency = NO_DEPENDENCY; // dependency initialiser
-
-  bool locationIsOrigin = isApproxZero(position, PRECISION);
-  bool orientationIsZero = isApproxZero(rotation, PRECISION);
-
-  H5::Group childGroup = parentGroup.createGroup(monitorName);
-  writeStrAttribute(childGroup, NX_CLASS, NX_MONITOR);
-
-  // do not write NXtransformations if there is no translation or rotation
-  if (!(locationIsOrigin && orientationIsZero)) {
-    H5::Group transformations =
-        simpleNXSubGroup(childGroup, TRANSFORMATIONS, NX_TRANSFORMATIONS);
-
-    // self, ".", is the default first NXmonitor dependency in the chain. first
-    // check translation in NXmonitor is non-zero, and set dependency to
-    // location if true and write location. Then check if orientation in
-    // NXmonitor is non-zero, replace dependency with orientation if true. If
-    // neither orientation nor location are non-zero, NXmonitor is self
-    // dependent.
-    if (!locationIsOrigin) {
-      dependency = H5_OBJ_NAME(transformations) + "/" + LOCATION;
-      writeLocation(transformations, position);
-    }
-    if (!orientationIsZero) {
-      dependency = H5_OBJ_NAME(transformations) + "/" + ORIENTATION;
-
-      // If location dataset is written to group also, then dependency for
-      // orientation dataset containg the rotation transformation will be
-      // location. Else dependency for orientation is self.
-      std::string rotationDependency =
-          locationIsOrigin ? NO_DEPENDENCY
-                           : H5_OBJ_NAME(transformations) + "/" + LOCATION;
-      writeOrientation(transformations, rotation, rotationDependency);
+  // Sized to spectra in bank
+  SpectraMappings mappings;
+  mappings.detector_list.resize(childrenDetectors.size());
+  mappings.detector_count.resize(detector_count_map.size(), 0);
+  mappings.detector_index.resize(detector_count_map.size() + 1, 0);
+  mappings.spectra_ids.resize(detector_count_map.size(), 0);
+  mappings.number_dets = childrenDetectors.size();
+  mappings.number_spec = detector_count_map.size();
+  size_t counter = 0;
+
+  for (auto &pair : detector_count_map) {
+    // using sort order of map to ensure we are ordered by lowest to highest
+    // spectrum index
+    mappings.detector_count[counter] = (pair.second); // Counts
+    mappings.detector_index[counter + 1] =
+        mappings.detector_index[counter] + (pair.second);
+    mappings.spectra_ids[counter] =
+        int32_t(indexInfo.spectrumNumber(pair.first));
+
+    // We will list everything by spectrum index, so we need to add the detector
+    // ids in the same order.
+    const auto &specDefintion = specInfo.spectrumDefinition(pair.first);
+    for (const auto &def : specDefintion) {
+      mappings.detector_list[counter] = detIds[def.first];
     }
-  }
-
-  H5::StrType dependencyStrType = strTypeOfSize(dependency);
-  writeNXMonitorNumber(childGroup, monitorId);
-
-  writeStrDataset(childGroup, BANK_NAME, monitorName);
-  writeStrDataset(childGroup, DEPENDS_ON, dependency);
-}
 
-/*
- * Function: saveNXDetectors
- * For NXinstrument parent (component info root). Save method which produces a
- * set of NXdetctor groups from Component info detector banks, and saves it in
- * the parent group, along with the Nexus compliant datasets, and metadata
- * stored in attributes to the new group.
- *
- * @param parentGroup : parent group in which to write the NXinstrument group.
- * @param compInfo : componentInfo object.
- * @param detIDs : global detector IDs, from which those specific to the
- * NXdetector will be extracted.
- */
-void saveNXDetector(const H5::Group &parentGroup,
-                    const Geometry::ComponentInfo &compInfo,
-                    const std::vector<int> &detIds, const size_t index) {
-
-  // if the component is unnamed sets the name as unspecified with the
-  // location of the component in the cache
-  std::string nameInCache = compInfo.name(index);
-  std::string detectorName =
-      nameInCache.empty() ? "unspecified_detector_at_" + std::to_string(index)
-                          : nameInCache;
-
-  Eigen::Vector3d position =
-      Mantid::Kernel::toVector3d(compInfo.position(index));
-  Eigen::Quaterniond rotation =
-      Mantid::Kernel::toQuaterniond(compInfo.rotation(index));
-
-  std::string dependency = NO_DEPENDENCY; // dependency initialiser
-
-  bool locationIsOrigin = isApproxZero(position, PRECISION);
-  bool orientationIsZero = isApproxZero(rotation, PRECISION);
-
-  H5::Group childGroup = parentGroup.createGroup(detectorName);
-  writeStrAttribute(childGroup, NX_CLASS, NX_DETECTOR);
-
-  // do not write NXtransformations if there is no translation or rotation
-  if (!(locationIsOrigin && orientationIsZero)) {
-    H5::Group transformations =
-        simpleNXSubGroup(childGroup, TRANSFORMATIONS, NX_TRANSFORMATIONS);
-
-    // self, ".", is the default first NXdetector dependency in the chain. first
-    // check translation in NXdetector is non-zero, and set dependency to
-    // location if true and write location. Then check if orientation in
-    // NXdetector is non-zero, replace dependency with orientation if true. If
-    // neither orientation nor location are non-zero, NXdetector is self
-    // dependent.
-    if (!locationIsOrigin) {
-      dependency = H5_OBJ_NAME(transformations) + "/" + LOCATION;
-      writeLocation(transformations, position);
-    }
-    if (!orientationIsZero) {
-      dependency = H5_OBJ_NAME(transformations) + "/" + ORIENTATION;
-
-      // If location dataset is written to group also, then dependency for
-      // orientation dataset containg the rotation transformation will be
-      // location. Else dependency for orientation is self.
-      std::string rotationDependency =
-          locationIsOrigin ? NO_DEPENDENCY
-                           : H5_OBJ_NAME(transformations) + "/" + LOCATION;
-      writeOrientation(transformations, rotation, rotationDependency);
-    }
+    ++counter;
   }
+  mappings.detector_index.resize(
+      detector_count_map.size()); // cut-off last item
 
-  H5::StrType dependencyStrType = strTypeOfSize(dependency);
-  writeXYZPixeloffset(childGroup, compInfo, index);
-  writeNXDetectorNumber(childGroup, compInfo, detIds, index);
-
-  writeStrDataset(childGroup, BANK_NAME, detectorName);
-  writeStrDataset(childGroup, DEPENDS_ON, dependency);
+  return mappings;
 }
 
-/*
- * Function: saveInstrument
- * calls the save methods to write components to file after exception checking.
- * Produces a Nexus format file containing the Instrument geometry and metadata.
- *
- * @param compInfo : componentInfo object.
- * @param detInfo : detectorInfo object.
- * @param fullPath : save destination as full path.
- * @param rootPath : name of root entry
- * @param reporter : (optional) report to progressBase.
- */
-void saveInstrument(const Geometry::ComponentInfo &compInfo,
-                    const Geometry::DetectorInfo &detInfo,
-                    const std::string &fullPath, const std::string &rootPath,
-                    Kernel::ProgressBase *reporter) {
-
-  // Exception handling.
+void validateInputs(AbstractLogger &logger, const std::string &fullPath,
+                    const Geometry::ComponentInfo &compInfo) {
   boost::filesystem::path tmp(fullPath);
   if (!boost::filesystem::is_directory(tmp.root_directory())) {
     throw std::invalid_argument(
@@ -736,56 +560,456 @@ void saveInstrument(const Geometry::ComponentInfo &compInfo,
 
   // throw if the file extension is invalid
   if (!isValidExt) {
-
     // string of valid extensions to output in exception
     std::string extensions;
     std::for_each(
         nexus_geometry_extensions.begin(), nexus_geometry_extensions.end(),
         [&extensions](const std::string &str) { extensions += " " + str; });
-    throw std::invalid_argument("invalid extension for file: '" +
-                                ext.generic_string() +
-                                "'. Expected any of: " + extensions);
+    std::string message = "invalid extension for file: '" +
+                          ext.generic_string() +
+                          "'. Expected any of: " + extensions;
+    logger.error(message);
+    throw std::invalid_argument(message);
   }
 
   if (!compInfo.hasDetectorInfo()) {
-    throw std::invalid_argument(
-        "No detector info was found in the Instrument cache.\n");
+    logger.error(
+        "No detector info was found in the Instrument. Instrument not saved.");
+    throw std::invalid_argument("No detector info was found in the Instrument");
   }
   if (!compInfo.hasSample()) {
-    throw std::invalid_argument(
-        "No sample was found in the Instrument cache.\n");
+    logger.error(
+        "No sample was found in the Instrument. Instrument not saved.");
+    throw std::invalid_argument("No sample was found in the Instrument");
   }
 
   if (Mantid::Kernel::V3D{0, 0, 0} != compInfo.samplePosition()) {
+    logger.error("The sample positon is required to be at the origin. "
+                 "Instrument not saved.");
     throw std::invalid_argument(
-        "The sample posiiton is required to be at the origin.\n");
+        "The sample positon is required to be at the origin");
   }
 
   if (!compInfo.hasSource()) {
-    throw std::invalid_argument("No source was found in the Instrument cache.");
+    logger.error("No source was found in the Instrument. "
+                 "Instrument not saved.");
+    throw std::invalid_argument("No source was found in the Instrument");
   }
+}
 
-  // IDs of all detectors in Instrument cache
-  const auto detIds = detInfo.detectorIDs();
+class NexusGeometrySaveImpl {
+public:
+  enum class Mode { Trunc, Append };
+
+private:
+  Mode m_mode;
+
+  H5::Group openOrCreateGroup(const H5::Group &parent, const std::string &name,
+                              const std::string &classType) {
+
+    if (m_mode == Mode::Append) {
+      // Find by class and by name
+      auto results = utilities::findGroups(parent, classType);
+      for (auto &result : results) {
+        auto resultName = H5ForwardCompatibility::getObjName(result);
+        // resultName gives full path. We match the last name on the path
+        if (std::regex_match(resultName, std::regex(".*/" + name + "$"))) {
+          return result;
+        }
+      }
+    }
+    // We can't find it, or we are writing from scratch anyway
+    return parent.createGroup(name);
+  }
+
+  // function to create a simple sub-group that has a nexus class attribute,
+  // inside a parent group.
+  H5::Group simpleNXSubGroup(H5::Group &parent, const std::string &name,
+                             const std::string &nexusAttribute) {
+    H5::Group subGroup = openOrCreateGroup(parent, name, nexusAttribute);
+    writeStrAttribute(subGroup, NX_CLASS, nexusAttribute);
+    return subGroup;
+  }
+
+public:
+  explicit NexusGeometrySaveImpl(Mode mode) : m_mode(mode) {}
+
+  /*
+   * Function: NXInstrument
+   * for NXentry parent (root group). Produces an NXinstrument group in the
+   * parent group, and writes Nexus compliant datasets and metadata stored in
+   * attributes to the new group.
+   *
+   * @param parent : parent group in which to write the NXinstrument group.
+   * @param compInfo : componentinfo
+   * @return NXinstrument group, to be passed into children save methods.
+   */
+  H5::Group instrument(const H5::Group &parent,
+                       const Geometry::ComponentInfo &compInfo) {
+
+    std::string nameInCache = compInfo.name(compInfo.root());
+    std::string instrName =
+        nameInCache.empty() ? "unspecified_instrument" : nameInCache;
+
+    H5::Group childGroup = openOrCreateGroup(parent, instrName, NX_INSTRUMENT);
+
+    writeStrDataset(childGroup, NAME, instrName);
+    writeStrAttribute(childGroup, NX_CLASS, NX_INSTRUMENT);
+
+    std::string defaultShortName = instrName.substr(0, 3);
+    H5::DataSet name = childGroup.openDataSet(NAME);
+    writeStrAttribute(name, SHORT_NAME, defaultShortName);
+    return childGroup;
+  }
+
+  /*
+   * Function: saveNXSample
+   * For NXentry parent (root group). Produces an NXsample group in the parent
+   * group, and writes the Nexus compliant datasets and metadata stored in
+   * attributes to the new group.
+   *
+   * @param parent : parent group in which to write the NXinstrument group.
+   * @param compInfo : componentInfo object.
+   */
+  void sample(const H5::Group &parentGroup,
+              const Geometry::ComponentInfo &compInfo) {
+
+    std::string nameInCache = compInfo.name(compInfo.sample());
+    std::string sampleName =
+        nameInCache.empty() ? "unspecified_sample" : nameInCache;
+
+    H5::Group childGroup =
+        openOrCreateGroup(parentGroup, sampleName, NX_SAMPLE);
+    writeStrAttribute(childGroup, NX_CLASS, NX_SAMPLE);
+    writeStrDataset(childGroup, NAME, sampleName);
+  }
+
+  /*
+   * Function: saveNXSource
+   * For NXentry (root group). Produces an NXsource group in the parent group,
+   * and writes the Nexus compliant datasets and metadata stored in attributes
+   * to the new group.
+   *
+   * @param parent : parent group in which to write the NXinstrument group.
+   * @param compInfo : componentInfo object.
+   */
+  void source(const H5::Group &parentGroup,
+              const Geometry::ComponentInfo &compInfo) {
+
+    size_t index = compInfo.source();
+
+    std::string nameInCache = compInfo.name(index);
+    std::string sourceName =
+        nameInCache.empty() ? "unspecified_source" : nameInCache;
+
+    std::string dependency = NO_DEPENDENCY;
+
+    Eigen::Vector3d position =
+        Mantid::Kernel::toVector3d(compInfo.position(index));
+    Eigen::Quaterniond rotation =
+        Mantid::Kernel::toQuaterniond(compInfo.rotation(index));
+
+    bool locationIsOrigin = isApproxZero(position, PRECISION);
+    bool orientationIsZero = isApproxZero(rotation, PRECISION);
+
+    H5::Group childGroup =
+        openOrCreateGroup(parentGroup, sourceName, NX_SOURCE);
+    writeStrAttribute(childGroup, NX_CLASS, NX_SOURCE);
+
+    // do not write NXtransformations if there is no translation or rotation
+    if (!(locationIsOrigin && orientationIsZero)) {
+      H5::Group transformations =
+          simpleNXSubGroup(childGroup, TRANSFORMATIONS, NX_TRANSFORMATIONS);
+
+      // self, ".", is the default first NXsource dependency in the chain.
+      // first check translation in NXsource is non-zero, and set dependency
+      // to location if true and write location. Then check if orientation in
+      // NXsource is non-zero, replace dependency with orientation if true. If
+      // neither orientation nor location are non-zero, NXsource is self
+      // dependent.
+      if (!locationIsOrigin) {
+        dependency = H5_OBJ_NAME(transformations) + "/" + LOCATION;
+        writeLocation(transformations, position);
+      }
+      if (!orientationIsZero) {
+        dependency = H5_OBJ_NAME(transformations) + "/" + ORIENTATION;
+
+        // If location dataset is written to group also, then dependency for
+        // orientation dataset containg the rotation transformation will be
+        // location. Else dependency for orientation is self.
+        std::string rotationDependency =
+            locationIsOrigin ? NO_DEPENDENCY
+                             : H5_OBJ_NAME(transformations) + "/" + LOCATION;
+        writeOrientation(transformations, rotation, rotationDependency);
+      }
+    }
+
+    writeStrDataset(childGroup, NAME, sourceName);
+    writeStrDataset(childGroup, DEPENDS_ON, dependency);
+  }
+
+  /*
+   * Function: monitor
+   * For NXinstrument parent (component info root). Produces an NXmonitor
+   * groups from Component info, and saves it in the parent
+   * group, along with the Nexus compliant datasets, and metadata stored in
+   * attributes to the new group.
+   *
+   * @param parentGroup : parent group in which to write the NXinstrument
+   * group.
+   * @param compInfo : componentInfo object.
+   * @param monitorID :  ID of the specific monitor.
+   * @param index :  index of the specific monitor in the Instrument cache.
+   * @return child group for further additions
+   */
+  H5::Group monitor(const H5::Group &parentGroup,
+                    const Geometry::ComponentInfo &compInfo,
+                    const int monitorId, const size_t index) {
+
+    // if the component is unnamed sets the name as unspecified with the
+    // location of the component in the cache
+    std::string nameInCache = compInfo.name(index);
+    std::string monitorName =
+        nameInCache.empty() ? "unspecified_monitor_" + std::to_string(index)
+                            : nameInCache;
+
+    Eigen::Vector3d position =
+        Mantid::Kernel::toVector3d(compInfo.position(index));
+    Eigen::Quaterniond rotation =
+        Mantid::Kernel::toQuaterniond(compInfo.rotation(index));
+
+    std::string dependency = NO_DEPENDENCY; // dependency initialiser
+
+    bool locationIsOrigin = isApproxZero(position, PRECISION);
+    bool orientationIsZero = isApproxZero(rotation, PRECISION);
+
+    H5::Group childGroup =
+        openOrCreateGroup(parentGroup, monitorName, NX_MONITOR);
+    writeStrAttribute(childGroup, NX_CLASS, NX_MONITOR);
+
+    // do not write NXtransformations if there is no translation or rotation
+    if (!(locationIsOrigin && orientationIsZero)) {
+      H5::Group transformations =
+          simpleNXSubGroup(childGroup, TRANSFORMATIONS, NX_TRANSFORMATIONS);
+
+      // self, ".", is the default first NXmonitor dependency in the chain.
+      // first check translation in NXmonitor is non-zero, and set dependency
+      // to location if true and write location. Then check if orientation in
+      // NXmonitor is non-zero, replace dependency with orientation if true.
+      // If neither orientation nor location are non-zero, NXmonitor is self
+      // dependent.
+      if (!locationIsOrigin) {
+        dependency = H5_OBJ_NAME(transformations) + "/" + LOCATION;
+        writeLocation(transformations, position);
+      }
+      if (!orientationIsZero) {
+        dependency = H5_OBJ_NAME(transformations) + "/" + ORIENTATION;
+
+        // If location dataset is written to group also, then dependency for
+        // orientation dataset containg the rotation transformation will be
+        // location. Else dependency for orientation is self.
+        std::string rotationDependency =
+            locationIsOrigin ? NO_DEPENDENCY
+                             : H5_OBJ_NAME(transformations) + "/" + LOCATION;
+        writeOrientation(transformations, rotation, rotationDependency);
+      }
+    }
+
+    H5::StrType dependencyStrType = strTypeOfSize(dependency);
+    writeNXMonitorNumber(childGroup, monitorId);
 
-  H5::H5File file(fullPath, H5F_ACC_TRUNC); // open file
+    writeStrDataset(childGroup, BANK_NAME, monitorName);
+    writeStrDataset(childGroup, DEPENDS_ON, dependency);
+    return childGroup;
+  }
+
+  /* For NXinstrument parent (component info root). Produces an NXmonitor
+   * groups from Component info, and saves it in the parent
+   * group, along with the Nexus compliant datasets, and metadata stored in
+   * attributes to the new group.
+   *
+   * Saves detector-spectra mappings too
+   *
+   * @param parentGroup : parent group in which to write the NXinstrument
+   * group.
+   * @param compInfo : componentInfo object.
+   * @param monitorId :  ID of the specific monitor.
+   * @param index :  index of the specific monitor in the Instrument cache.
+   * @param mappings : Spectra to detector mappings
+   */
+  void monitor(const H5::Group &parentGroup,
+               const Geometry::ComponentInfo &compInfo, const int monitorId,
+               const size_t index, SpectraMappings &mappings) {
+
+    auto childGroup = monitor(parentGroup, compInfo, monitorId, index);
+    // Additional mapping information written.
+    writeDetectorCount(childGroup, mappings);
+    // Note that the detector list is the same as detector_number, but it is
+    // ordered by spectrum index 0 - N, whereas detector_number is just written
+    // out in the order the detectors are encountered in the bank.
+    writeDetectorList(childGroup, mappings);
+    writeDetectorIndex(childGroup, mappings);
+    writeSpectra(childGroup, mappings);
+  }
 
-  H5::Group rootGroup, instrument;
+  /*
+   * Function: detectors
+   * For NXinstrument parent (component info root). Save method which produces
+   * a set of NXdetctor groups from Component info detector banks, and saves
+   * it in the parent group, along with the Nexus compliant datasets, and
+   * metadata stored in attributes to the new group.
+   *
+   * @param parentGroup : parent group in which to write the NXinstrument
+   * group.
+   * @param compInfo : componentInfo object.
+   * @param detIDs : global detector IDs, from which those specific to the
+   * NXdetector will be extracted.
+   * @return childGroup for futher additions
+   */
+  H5::Group detector(const H5::Group &parentGroup,
+                     const Geometry::ComponentInfo &compInfo,
+                     const std::vector<int> &detIds, const size_t index) {
+
+    // if the component is unnamed sets the name as unspecified with the
+    // location of the component in the cache
+    std::string nameInCache = compInfo.name(index);
+    std::string detectorName =
+        nameInCache.empty() ? "unspecified_detector_at_" + std::to_string(index)
+                            : nameInCache;
+
+    Eigen::Vector3d position =
+        Mantid::Kernel::toVector3d(compInfo.position(index));
+    Eigen::Quaterniond rotation =
+        Mantid::Kernel::toQuaterniond(compInfo.rotation(index));
+
+    std::string dependency = NO_DEPENDENCY; // dependency initialiser
+
+    bool locationIsOrigin = isApproxZero(position, PRECISION);
+    bool orientationIsZero = isApproxZero(rotation, PRECISION);
+
+    H5::Group childGroup =
+        openOrCreateGroup(parentGroup, detectorName, NX_DETECTOR);
+    writeStrAttribute(childGroup, NX_CLASS, NX_DETECTOR);
+
+    // do not write NXtransformations if there is no translation or rotation
+    if (!(locationIsOrigin && orientationIsZero)) {
+      H5::Group transformations =
+          simpleNXSubGroup(childGroup, TRANSFORMATIONS, NX_TRANSFORMATIONS);
+
+      // self, ".", is the default first NXdetector dependency in the chain.
+      // first check translation in NXdetector is non-zero, and set dependency
+      // to location if true and write location. Then check if orientation in
+      // NXdetector is non-zero, replace dependency with orientation if true.
+      // If neither orientation nor location are non-zero, NXdetector is self
+      // dependent.
+      if (!locationIsOrigin) {
+        dependency = H5_OBJ_NAME(transformations) + "/" + LOCATION;
+        writeLocation(transformations, position);
+      }
+      if (!orientationIsZero) {
+        dependency = H5_OBJ_NAME(transformations) + "/" + ORIENTATION;
+
+        // If location dataset is written to group also, then dependency for
+        // orientation dataset containg the rotation transformation will be
+        // location. Else dependency for orientation is self.
+        std::string rotationDependency =
+            locationIsOrigin ? NO_DEPENDENCY
+                             : H5_OBJ_NAME(transformations) + "/" + LOCATION;
+        writeOrientation(transformations, rotation, rotationDependency);
+      }
+    }
+
+    H5::StrType dependencyStrType = strTypeOfSize(dependency);
+    writeXYZPixeloffset(childGroup, compInfo, index);
+    writeNXDetectorNumber(childGroup, compInfo, detIds, index);
 
-  // create and capture NXentry (file root)
-  rootGroup = file.createGroup(rootPath);
-  NexusGeometrySave::writeStrAttribute(rootGroup, NX_CLASS, NX_ENTRY);
+    writeStrDataset(childGroup, BANK_NAME, detectorName);
+    writeStrDataset(childGroup, DEPENDS_ON, dependency);
+    return childGroup;
+  }
 
+  /*
+   * Function: detectors
+   * For NXinstrument parent (component info root). Save method which produces
+   * a set of NXdetctor groups from Component info detector banks, and saves
+   * it in the parent group, along with the Nexus compliant datasets, and
+   * metadata stored in attributes to the new group.
+   *
+   * @param parentGroup : parent group in which to write the NXinstrument
+   * group.
+   * @param compInfo : componentInfo object.
+   * @param detIDs : global detector IDs, from which those specific to the
+   * @param index : current component index
+   * @param mappings : Spectra to detector mappings
+   * NXdetector will be extracted.
+   */
+  void detector(const H5::Group &parentGroup,
+                const Geometry::ComponentInfo &compInfo,
+                const std::vector<int> &detIds, const size_t index,
+                SpectraMappings &mappings) {
+
+    auto childGroup = detector(parentGroup, compInfo, detIds, index);
+
+    // Additional mapping information written.
+    writeDetectorCount(childGroup, mappings);
+    // Note that the detector list is the same as detector_number, but it is
+    // ordered by spectrum index 0 - N, whereas detector_number is just written
+    // out in the order the detectors are encountered in the bank.
+    writeDetectorList(childGroup, mappings);
+    writeDetectorIndex(childGroup, mappings);
+    writeSpectra(childGroup, mappings);
+  }
+}; // namespace
+
+/*
+ * Function: saveInstrument
+ * calls the save methods to write components to file after exception
+ * checking. Produces a Nexus format file containing the Instrument geometry
+ * and metadata.
+ *
+ * @param compInfo : componentInfo object.
+ * @param detInfo : detectorInfo object.
+ * @param fullPath : save destination as full path.
+ * @param rootName : name of root entry
+ * @param logger : logging object
+ * @param append : append mode, means opening and appending to existing file.
+ * If false, creates new file.
+ * @param reporter : (optional) report to progressBase.
+ */
+void saveInstrument(const Geometry::ComponentInfo &compInfo,
+                    const Geometry::DetectorInfo &detInfo,
+                    const std::string &fullPath, const std::string &rootName,
+                    AbstractLogger &logger, bool append,
+                    Kernel::ProgressBase *reporter) {
+
+  validateInputs(logger, fullPath, compInfo);
+  // IDs of all detectors in Instrument
+  H5::Group rootGroup;
+  H5::H5File file;
+  if (append) {
+    file = H5::H5File(fullPath, H5F_ACC_RDWR); // open file
+    rootGroup = file.openGroup(rootName);
+  } else {
+    file = H5::H5File(fullPath, H5F_ACC_TRUNC); // open file
+    rootGroup = file.createGroup(rootName);
+  }
+
+  writeStrAttribute(rootGroup, NX_CLASS, NX_ENTRY);
+
+  using Mode = NexusGeometrySaveImpl::Mode;
+  NexusGeometrySaveImpl writer(append ? Mode::Append : Mode::Trunc);
   // save and capture NXinstrument (component root)
-  instrument = NexusGeometrySave::NXInstrument(rootGroup, compInfo);
+  H5::Group instrument = writer.instrument(rootGroup, compInfo);
 
   // save NXsource
-  NexusGeometrySave::saveNXSource(instrument, compInfo);
+  writer.source(instrument, compInfo);
 
   // save NXsample
-  NexusGeometrySave::saveNXSample(rootGroup, compInfo);
+  writer.sample(rootGroup, compInfo);
 
+  const auto &detIds = detInfo.detectorIDs();
   // save NXdetectors
+
   std::list<size_t> nxDetectorCandidates;
   for (size_t index = compInfo.root() - 1; index >= detInfo.size(); --index) {
     if (Geometry::ComponentInfoBankHelpers::isSaveableBank(compInfo, index)) {
@@ -804,7 +1028,7 @@ void saveInstrument(const Geometry::ComponentInfo &compInfo,
     if (saveable || nxDetectorCandidates.size() == 1) {
       if (reporter != nullptr)
         reporter->report();
-      NexusGeometrySave::saveNXDetector(instrument, compInfo, detIds, *index);
+      writer.detector(instrument, compInfo, detIds, *index);
     }
   }
 
@@ -813,8 +1037,7 @@ void saveInstrument(const Geometry::ComponentInfo &compInfo,
     if (detInfo.isMonitor(index)) {
       if (reporter != nullptr)
         reporter->report();
-      NexusGeometrySave::saveNXMonitor(instrument, compInfo, detIds[index],
-                                       index);
+      writer.monitor(instrument, compInfo, detIds[index], index);
     }
   }
 
@@ -824,25 +1047,102 @@ void saveInstrument(const Geometry::ComponentInfo &compInfo,
 
 /*
  * Function: saveInstrument (overload)
- * calls the save methods to write components to file after exception checking.
- * Produces a Nexus format file containing the Instrument geometry and metadata.
+ * calls the save methods to write components to file after exception
+ * checking. Produces a Nexus format file containing the Instrument geometry
+ * and metadata.
  *
  * @param instrPair : instrument 2.0  object.
  * @param fullPath : save destination as full path.
- * @param rootPath : name of root entry
+ * @param rootName : name of root entry
+ * @param logger : logging object
+ * @param append : append mode, means openting and appending to existing file.
+ * If false, creates new file.
  * @param reporter : (optional) report to progressBase.
  */
 void saveInstrument(
     const std::pair<std::unique_ptr<Geometry::ComponentInfo>,
                     std::unique_ptr<Geometry::DetectorInfo>> &instrPair,
-    const std::string &fullPath, const std::string &rootPath,
-    Kernel::ProgressBase *reporter) {
+    const std::string &fullPath, const std::string &rootName,
+    AbstractLogger &logger, bool append, Kernel::ProgressBase *reporter) {
 
   const Geometry::ComponentInfo &compInfo = (*instrPair.first);
   const Geometry::DetectorInfo &detInfo = (*instrPair.second);
 
-  return saveInstrument(compInfo, detInfo, fullPath, rootPath, reporter);
-} // saveInstrument
+  return saveInstrument(compInfo, detInfo, fullPath, rootName, logger, append,
+                        reporter);
+}
+
+void saveInstrument(const Mantid::API::MatrixWorkspace &ws,
+                    const std::string &fullPath, const std::string &rootName,
+                    AbstractLogger &logger, bool append,
+                    Kernel::ProgressBase *reporter) {
+
+  const auto &detInfo = ws.detectorInfo();
+  const auto &compInfo = ws.componentInfo();
+
+  // Exception handling.
+  validateInputs(logger, fullPath, compInfo);
+  // IDs of all detectors in Instrument
+  const auto &detIds = detInfo.detectorIDs();
+
+  H5::Group rootGroup;
+  H5::H5File file;
+  if (append) {
+    file = H5::H5File(fullPath, H5F_ACC_RDWR); // open file
+    rootGroup = file.openGroup(rootName);
+  } else {
+    file = H5::H5File(fullPath, H5F_ACC_TRUNC); // open file
+    rootGroup = file.createGroup(rootName);
+  }
+
+  writeStrAttribute(rootGroup, NX_CLASS, NX_ENTRY);
+
+  using Mode = NexusGeometrySaveImpl::Mode;
+  NexusGeometrySaveImpl writer(append ? Mode::Append : Mode::Trunc);
+  // save and capture NXinstrument (component root)
+  H5::Group instrument = writer.instrument(rootGroup, compInfo);
+
+  // save NXsource
+  writer.source(instrument, compInfo);
+
+  // save NXsample
+  writer.sample(rootGroup, compInfo);
+
+  // save NXdetectors
+  auto detToIndexMap =
+      ws.getDetectorIDToWorkspaceIndexMap(true /*throw if multiples*/);
+  for (size_t index = compInfo.root() - 1; index >= detInfo.size(); --index) {
+    if (Geometry::ComponentInfoBankHelpers::isSaveableBank(compInfo, index)) {
+
+      // Make spectra detector mappings that can be used
+      SpectraMappings mappings =
+          makeMappings(compInfo, detToIndexMap, ws.indexInfo(),
+                       ws.spectrumInfo(), detIds, index);
+
+      if (reporter != nullptr)
+        reporter->report();
+      writer.detector(instrument, compInfo, detIds, index, mappings);
+    }
+  }
+
+  // save NXmonitors
+  for (size_t index = 0; index < detInfo.size(); ++index) {
+    if (detInfo.isMonitor(index)) {
+      // Make spectra detector mappings that can be used
+      SpectraMappings mappings =
+          makeMappings(compInfo, detToIndexMap, ws.indexInfo(),
+                       ws.spectrumInfo(), detIds, index);
+
+      if (reporter != nullptr)
+        reporter->report();
+      writer.monitor(instrument, compInfo, detIds[index], index, mappings);
+    }
+  }
+
+  file.close(); // close file
+}
+
+// saveInstrument
 
 } // namespace NexusGeometrySave
 } // namespace NexusGeometry
diff --git a/Framework/NexusGeometry/src/NexusGeometryUtilities.cpp b/Framework/NexusGeometry/src/NexusGeometryUtilities.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..28b407a823be1bc7dfd30590c19bd13a02bdc187
--- /dev/null
+++ b/Framework/NexusGeometry/src/NexusGeometryUtilities.cpp
@@ -0,0 +1,111 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+//     NScD Oak Ridge National Laboratory, European Spallation Source
+//     & Institut Laue - Langevin
+// SPDX - License - Identifier: GPL - 3.0 +
+
+#include "MantidNexusGeometry/NexusGeometryUtilities.h"
+#include "MantidNexusGeometry/NexusGeometryDefinitions.h"
+
+namespace Mantid {
+namespace NexusGeometry {
+namespace utilities {
+using namespace H5;
+
+/// Find a single dataset inside parent group (returns first match). Optional
+/// wrapped - empty to indicate nothing found.
+boost::optional<H5::DataSet> findDataset(const H5::Group &parentGroup,
+                                         const H5std_string &name) {
+  // Iterate over children, and determine if a group
+  for (hsize_t i = 0; i < parentGroup.getNumObjs(); ++i) {
+    if (parentGroup.getObjTypeByIdx(i) == DATASET_TYPE) {
+      H5std_string childPath = parentGroup.getObjnameByIdx(i);
+      // Open the sub group
+      if (childPath == name) {
+        auto childDataset = parentGroup.openDataSet(childPath);
+        return boost::optional<DataSet>(childDataset);
+      }
+    }
+  }
+  return boost::optional<DataSet>{}; // Empty
+}
+/// Find a single group inside parent (returns first match). class type must
+/// match NX_class. Optional wrapped - empty to indicate nothing found.
+boost::optional<H5::Group> findGroup(const H5::Group &parentGroup,
+                                     const H5std_string &classType) {
+  // Iterate over children, and determine if a group
+  for (hsize_t i = 0; i < parentGroup.getNumObjs(); ++i) {
+    if (parentGroup.getObjTypeByIdx(i) == GROUP_TYPE) {
+      H5std_string childPath = parentGroup.getObjnameByIdx(i);
+      // Open the sub group
+      auto childGroup = parentGroup.openGroup(childPath);
+      // Iterate through attributes to find NX_class
+      for (uint32_t attribute_index = 0;
+           attribute_index < static_cast<uint32_t>(childGroup.getNumAttrs());
+           ++attribute_index) {
+        // Test attribute at current index for NX_class
+        Attribute attribute = childGroup.openAttribute(attribute_index);
+        if (attribute.getName() == NX_CLASS) {
+          // Get attribute data type
+          DataType dataType = attribute.getDataType();
+          // Get the NX_class type
+          H5std_string classT;
+          attribute.read(dataType, classT);
+          // If group of correct type, return the childGroup
+          if (classT == classType) {
+            return boost::optional<Group>(childGroup);
+          }
+        }
+      }
+    }
+  }
+  return boost::optional<Group>{}; // Empty
+}
+
+/// Find all groups at the same level matching same class type. Returns first
+/// item found.
+std::vector<H5::Group> findGroups(const H5::Group &parentGroup,
+                                  const H5std_string &classType) {
+  std::vector<H5::Group> groups;
+  // Iterate over children, and determine if a group
+  for (hsize_t i = 0; i < parentGroup.getNumObjs(); ++i) {
+    if (parentGroup.getObjTypeByIdx(i) == GROUP_TYPE) {
+      H5std_string childPath = parentGroup.getObjnameByIdx(i);
+      // Open the sub group
+      auto childGroup = parentGroup.openGroup(childPath);
+      // Iterate through attributes to find NX_class
+      for (uint32_t attribute_index = 0;
+           attribute_index < static_cast<uint32_t>(childGroup.getNumAttrs());
+           ++attribute_index) {
+        // Test attribute at current index for NX_class
+        Attribute attribute = childGroup.openAttribute(attribute_index);
+        if (attribute.getName() == NX_CLASS) {
+          // Get attribute data type
+          DataType dataType = attribute.getDataType();
+          // Get the NX_class type
+          H5std_string classT;
+          attribute.read(dataType, classT);
+          // If group of correct type, return the childGroup
+          if (classT == classType) {
+            groups.push_back(childGroup);
+          }
+        }
+      }
+    }
+  }
+  return groups; // Empty
+}
+H5::Group findGroupOrThrow(const H5::Group &parentGroup,
+                           const H5std_string &classType) {
+  auto found = findGroup(parentGroup, classType);
+  if (!found) {
+    throw std::runtime_error(std::string("Could not find group class ") +
+                             classType);
+  } else
+    return *found;
+}
+
+} // namespace utilities
+} // namespace NexusGeometry
+} // namespace Mantid
diff --git a/Framework/NexusGeometry/test/NexusGeometryParserTest.h b/Framework/NexusGeometry/test/NexusGeometryParserTest.h
index f408de1751f35ab28442bd577da94409ec09cdc2..63cd92f7966a23debca713019dd36193bb0f3b3b 100644
--- a/Framework/NexusGeometry/test/NexusGeometryParserTest.h
+++ b/Framework/NexusGeometry/test/NexusGeometryParserTest.h
@@ -18,9 +18,9 @@
 #include "MantidGeometry/Surfaces/Cylinder.h"
 #include "MantidKernel/ConfigService.h"
 #include "MantidKernel/EigenConversionHelpers.h"
-#include "MantidKernel/WarningSuppressions.h"
 #include "MantidNexusGeometry/NexusGeometryParser.h"
 
+#include "mockobjects.h"
 #include <H5Cpp.h>
 #include <Poco/Glob.h>
 #include <chrono>
@@ -44,14 +44,6 @@ extractBeamline(const Mantid::Geometry::Instrument &instrument) {
   return {std::move(std::get<0>(beamline)), std::move(std::get<1>(beamline))};
 }
 
-class MockLogger : public NexusGeometry::Logger {
-public:
-  GNU_DIAG_OFF_SUGGEST_OVERRIDE
-  MOCK_METHOD1(warning, void(const std::string &));
-  MOCK_METHOD1(error, void(const std::string &));
-  GNU_DIAG_ON_SUGGEST_OVERRIDE
-};
-
 } // namespace
 class NexusGeometryParserTest : public CxxTest::TestSuite {
 public:
diff --git a/Framework/NexusGeometry/test/NexusGeometrySaveTest.h b/Framework/NexusGeometry/test/NexusGeometrySaveTest.h
index bda9d84112a1aa01f6ea4fdc2d63a3bc46fca17d..46a166dd6e3bab9d2ba0a5bb8313951b27bb0411 100644
--- a/Framework/NexusGeometry/test/NexusGeometrySaveTest.h
+++ b/Framework/NexusGeometry/test/NexusGeometrySaveTest.h
@@ -12,34 +12,21 @@
 #include "MantidGeometry/Instrument/DetectorInfo.h"
 #include "MantidGeometry/Instrument/InstrumentVisitor.h"
 #include "MantidKernel/EigenConversionHelpers.h"
-#include "MantidKernel/ProgressBase.h"
-#include "MantidKernel/WarningSuppressions.h"
 #include "MantidNexusGeometry/NexusGeometryDefinitions.h"
 #include "MantidNexusGeometry/NexusGeometrySave.h"
 #include "MantidTestHelpers/ComponentCreationHelper.h"
 #include "MantidTestHelpers/FileResource.h"
 #include "MantidTestHelpers/NexusFileReader.h"
 
+#include "mockobjects.h"
 #include <cxxtest/TestSuite.h>
 #include <gmock/gmock.h>
 
 using namespace Mantid::NexusGeometry;
 
-//---------------------------------------------------------------
-namespace {
-
-class MockProgressBase : public Mantid::Kernel::ProgressBase {
-public:
-  GNU_DIAG_OFF_SUGGEST_OVERRIDE
-  MOCK_METHOD1(doReport, void(const std::string &));
-  GNU_DIAG_OFF_SUGGEST_OVERRIDE
-};
-
-} // namespace
-
-//---------------------------------------------------------------------
-
 class NexusGeometrySaveTest : public CxxTest::TestSuite {
+private:
+  testing::NiceMock<MockLogger> m_mockLogger;
 
 public:
   // This pair of boilerplate methods prevent the suite being created statically
@@ -77,9 +64,10 @@ used.
         V3D(0, 0, -10), V3D(0, 0, 0), V3D(0, 0, 10));
     auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
 
-    TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(
-                         instr, badDestinationPath, DEFAULT_ROOT_PATH),
-                     std::invalid_argument &);
+    TS_ASSERT_THROWS(
+        NexusGeometrySave::saveInstrument(
+            instr, badDestinationPath, DEFAULT_ROOT_ENTRY_NAME, m_mockLogger),
+        std::invalid_argument &);
   }
 
   void test_progress_reporting() {
@@ -94,8 +82,9 @@ used.
         ComponentCreationHelper::createTestInstrumentRectangular2(2, 2);
     auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
 
-    NexusGeometrySave::saveInstrument(instr, destinationFile, DEFAULT_ROOT_PATH,
-                                      &progressRep);
+    NexusGeometrySave::saveInstrument(instr, destinationFile,
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger,
+                                      false /*strict*/, &progressRep);
     TS_ASSERT(testing::Mock::VerifyAndClearExpectations(&progressRep));
   }
 
@@ -109,8 +98,17 @@ used.
     auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
 
     TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                                       DEFAULT_ROOT_PATH),
+                                                       DEFAULT_ROOT_ENTRY_NAME,
+                                                       m_mockLogger),
                      std::invalid_argument &);
+    // Same error but log rather than throw
+    MockLogger logger;
+    EXPECT_CALL(logger, error(testing::_)).Times(1);
+    TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(
+                         instr, destinationFile, DEFAULT_ROOT_ENTRY_NAME,
+                         logger, false /*append*/),
+                     std::invalid_argument);
+    TS_ASSERT(testing::Mock::VerifyAndClearExpectations(&logger));
   }
 
   void test_instrument_without_sample_throws() {
@@ -131,8 +129,18 @@ used.
     TS_ASSERT(!compInfo.hasSample());      // verify component has no sample
 
     TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                                       DEFAULT_ROOT_PATH),
+                                                       DEFAULT_ROOT_ENTRY_NAME,
+                                                       m_mockLogger),
                      std::invalid_argument &);
+
+    // Same error but log rather than throw
+    MockLogger logger;
+    EXPECT_CALL(logger, error(testing::_)).Times(1);
+    TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(
+                         instr, destinationFile, DEFAULT_ROOT_ENTRY_NAME,
+                         logger, false /*append*/),
+                     std::invalid_argument &);
+    TS_ASSERT(testing::Mock::VerifyAndClearExpectations(&logger));
   }
 
   void test_instrument_without_source_throws() {
@@ -153,8 +161,17 @@ used.
     TS_ASSERT(!compInfo.hasSource());      // verify component has no source
 
     TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                                       DEFAULT_ROOT_PATH),
+                                                       DEFAULT_ROOT_ENTRY_NAME,
+                                                       m_mockLogger),
+                     std::invalid_argument &);
+    // Same error but log rather than throw
+    MockLogger logger;
+    EXPECT_CALL(logger, error(testing::_)).Times(1);
+    TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(
+                         instr, destinationFile, DEFAULT_ROOT_ENTRY_NAME,
+                         logger, false /*append*/),
                      std::invalid_argument &);
+    TS_ASSERT(testing::Mock::VerifyAndClearExpectations(&logger));
   }
 
   void test_sample_not_at_origin_throws() {
@@ -167,8 +184,17 @@ used.
     std::string destinationFile = fileResource.fullPath();
 
     TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                                       DEFAULT_ROOT_PATH),
+                                                       DEFAULT_ROOT_ENTRY_NAME,
+                                                       m_mockLogger),
                      std::invalid_argument &);
+    // Same error but log rather than throw
+    MockLogger logger;
+    EXPECT_CALL(logger, error(testing::_)).Times(1);
+    TS_ASSERT_THROWS(NexusGeometrySave::saveInstrument(
+                         instr, destinationFile, DEFAULT_ROOT_ENTRY_NAME,
+                         logger, false /*append*/),
+                     std::invalid_argument &);
+    TS_ASSERT(testing::Mock::VerifyAndClearExpectations(&logger));
   }
 
   /*
@@ -200,13 +226,13 @@ used.
     auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
 
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // test utility to check output file
     NexusFileReader tester(destinationFile);
 
     // assert the group at the root H5 path is NXentry
-    TS_ASSERT(tester.groupHasNxClass(NX_ENTRY, DEFAULT_ROOT_PATH));
+    TS_ASSERT(tester.groupHasNxClass(NX_ENTRY, DEFAULT_ROOT_ENTRY_NAME));
   }
 
   void test_nxinstrument_group_exists_in_root_group() {
@@ -225,7 +251,7 @@ used.
 
     // call saveinstrument taking test instrument as parameter
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // test utility to check the output file
     NexusFileReader tester(destinationFile);
@@ -236,14 +262,9 @@ used.
     TS_ASSERT(tester.parentNXgroupHasChildNXgroup(NX_ENTRY, NX_INSTRUMENT));
   }
 
-  void test_NXclass_with_name_has_same_group_name_and_is_stored_in_dataset() {
-    // this test checks that when a name for some component in the instrument
-    // cache has been provided, saveInstrument will save the relevant group uder
-    // that name. this test is done for the done for the NXinstrument group. the
-    // name of the instrument will be manually set, then the test utility will
-    // try to open a group with that same same name, if such a group does not
-    // exist, a H5 group error is thrown. no such exception is expected to be
-    // thrown.
+  void test_NXInstrument_name_is_aways_instrument() {
+    // NXInstrument group name is always written as "instrument" for legacy
+    // compatibility reasons
 
     // RAII file resource for test file destination
     FileResource fileResource("check_instrument_name_test_file.nxs");
@@ -260,13 +281,16 @@ used.
 
     // call saveInstrument passing the test instrument as parameter.
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH); // saves instrument
+                                      DEFAULT_ROOT_ENTRY_NAME,
+                                      m_mockLogger); // saves instrument
 
     // test utility to check the output file.
     NexusFileReader testUtility(destinationFile);
 
+    const auto &compInfo = *instr.first;
+
     // full H5 path to the NXinstrument group
-    FullNXPath path = {DEFAULT_ROOT_PATH, "test_instrument_name"};
+    FullNXPath path = {DEFAULT_ROOT_ENTRY_NAME, compInfo.name(compInfo.root())};
 
     // assert no exception thrown on open of instrument group in file with
     // manually set name.
@@ -277,8 +301,8 @@ used.
 
     // assert the dataset containing the instrument name has been correctly
     // stored also.
-    TS_ASSERT(
-        testUtility.dataSetHasStrValue(NAME, "test_instrument_name", path));
+    TS_ASSERT(testUtility.dataSetHasStrValue(
+        NAME, compInfo.name(compInfo.root()), path));
   }
 
   void
@@ -300,7 +324,7 @@ used.
     auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
 
     TS_ASSERT_THROWS_NOTHING(NexusGeometrySave::saveInstrument(
-        instr, destinationFile, DEFAULT_ROOT_PATH));
+        instr, destinationFile, DEFAULT_ROOT_ENTRY_NAME, m_mockLogger));
   }
 
   void test_nxsource_group_exists_and_is_in_nxinstrument_group() {
@@ -319,7 +343,7 @@ used.
 
     // call saveInstrument passing test instrument as parameter
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // test utility to check output file
     NexusFileReader tester(destinationFile);
@@ -344,7 +368,7 @@ used.
     auto &compInfo = (*instr.first);
 
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
     NexusFileReader tester(destinationFile);
 
     TS_ASSERT(compInfo.hasSample());
@@ -398,14 +422,14 @@ Instrument cache.
 
     // call saveInstrument
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // instance of test utility to check saved file
     NexusFileReader tester(destinationFile);
 
     // full path to group to be opened in test utility
     FullNXPath path = {
-        DEFAULT_ROOT_PATH,
+        DEFAULT_ROOT_ENTRY_NAME,
         "test-instrument-with-detector-rotations" /*instrument name*/,
         "detector-stage" /*bank name*/, TRANSFORMATIONS};
 
@@ -459,13 +483,13 @@ Instrument cache.
 
     // call saveInstrument
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // instance of test utility to check saved file
     NexusFileReader tester(destinationFile);
 
     // full path to group to be opened in test utility
-    FullNXPath path = {DEFAULT_ROOT_PATH, "test-instrument-with-monitor",
+    FullNXPath path = {DEFAULT_ROOT_ENTRY_NAME, "test-instrument-with-monitor",
                        "test-monitor", TRANSFORMATIONS};
 
     // get angle magnitude in dataset
@@ -516,13 +540,14 @@ Instrument cache.
 
     // call saveInstrument
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // instance of test utility to check saved file
     NexusFileReader tester(destinationFile);
 
     // full path to group to be opened in test utility
-    FullNXPath path = {DEFAULT_ROOT_PATH, "test-instrument" /*instrument name*/,
+    FullNXPath path = {DEFAULT_ROOT_ENTRY_NAME,
+                       "test-instrument" /*instrument name*/,
                        "source" /*source name*/, TRANSFORMATIONS};
 
     // get magnitude of vector in dataset
@@ -572,13 +597,14 @@ Instrument cache.
 
     // call saveInstrument
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // instance of test utility to check saved file
     NexusFileReader tester(destinationFile);
 
     // full path to group to be opened in test utility
-    FullNXPath path = {DEFAULT_ROOT_PATH, "test-instrument" /*instrument name*/,
+    FullNXPath path = {DEFAULT_ROOT_ENTRY_NAME,
+                       "test-instrument" /*instrument name*/,
                        "source" /*source name*/, TRANSFORMATIONS};
 
     // get angle magnitude in dataset
@@ -630,13 +656,14 @@ Instrument cache.
 
     // call saveInstrument
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // instance of test utility to check saved file
     NexusFileReader tester(destinationFile);
 
     // full path to group to be opened in test utility
-    FullNXPath path = {DEFAULT_ROOT_PATH, "test-instrument" /*instrument name*/,
+    FullNXPath path = {DEFAULT_ROOT_ENTRY_NAME,
+                       "test-instrument" /*instrument name*/,
                        "source" /*source name*/, TRANSFORMATIONS};
 
     // assertations
@@ -673,13 +700,13 @@ Instrument cache.
 
     // full path to access NXtransformations group with test utility
     FullNXPath path = {
-        DEFAULT_ROOT_PATH,
+        DEFAULT_ROOT_ENTRY_NAME,
         "test-instrument-with-detector-rotations" /*instrument name*/,
         "detector-stage" /*bank name*/, TRANSFORMATIONS};
 
     // call saveInstrument passing test instrument as parameter
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // test utility to check output file
     NexusFileReader tester(destinationFile);
@@ -712,12 +739,12 @@ Instrument cache.
     auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
 
     // full path to group to be opened in test utility
-    FullNXPath path = {DEFAULT_ROOT_PATH, "test-instrument-with-monitor",
+    FullNXPath path = {DEFAULT_ROOT_ENTRY_NAME, "test-instrument-with-monitor",
                        "test-monitor", TRANSFORMATIONS};
 
     // call saveInstrument passing test instrument as parameter
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // test utility to check output file
     NexusFileReader tester(destinationFile);
@@ -754,12 +781,12 @@ Instrument cache.
     auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
 
     // full path to group to be opened in test utility
-    FullNXPath path = {DEFAULT_ROOT_PATH, "test-instrument", "source",
+    FullNXPath path = {DEFAULT_ROOT_ENTRY_NAME, "test-instrument", "source",
                        TRANSFORMATIONS};
 
     // call saveinstrument passing test instrument as parameter
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // test utility to check output file
     NexusFileReader tester(destinationFile);
@@ -804,12 +831,12 @@ Instrument cache.
 
     // call save insrument passing the test instrument as parameter
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // instance of test utility to check saved file
     NexusFileReader tester(destinationFile);
     FullNXPath path = {
-        DEFAULT_ROOT_PATH,
+        DEFAULT_ROOT_ENTRY_NAME,
         "test-instrument-with-detector-rotations" /*instrument name*/,
         "detector-stage" /*bank name*/};
 
@@ -882,7 +909,7 @@ Instrument cache.
     auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
 
     FullNXPath transformationsPath = {
-        DEFAULT_ROOT_PATH, "test-instrument" /*instrument name*/,
+        DEFAULT_ROOT_ENTRY_NAME, "test-instrument" /*instrument name*/,
         "source" /*source name*/, TRANSFORMATIONS};
 
     FullNXPath sourcePath = transformationsPath;
@@ -890,7 +917,7 @@ Instrument cache.
 
     // call saveInstrument with test instrument as parameter
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // test utility to check output file
     NexusFileReader tester(destinationFile);
@@ -948,7 +975,7 @@ Instrument cache.
     auto instr = Mantid::Geometry::InstrumentVisitor::makeWrappers(*instrument);
 
     FullNXPath transformationsPath = {
-        DEFAULT_ROOT_PATH, "test-instrument" /*instrument name*/,
+        DEFAULT_ROOT_ENTRY_NAME, "test-instrument" /*instrument name*/,
         "source" /*source name*/, TRANSFORMATIONS};
 
     FullNXPath sourcePath = transformationsPath;
@@ -956,7 +983,7 @@ Instrument cache.
 
     // call saveInstrument with test instrument as parameter
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // test utility for checking file
     NexusFileReader tester(destinationFile);
@@ -1017,7 +1044,7 @@ Instrument cache.
 
     // path to NXtransformations subgoup in NXsource
     FullNXPath transformationsPath = {
-        DEFAULT_ROOT_PATH, "test-instrument" /*instrument name*/,
+        DEFAULT_ROOT_ENTRY_NAME, "test-instrument" /*instrument name*/,
         "source" /*source name*/, TRANSFORMATIONS};
 
     // path to NXsource group
@@ -1026,7 +1053,7 @@ Instrument cache.
 
     // call saveInstrument passing test instrument as parameter
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // test utility for checking output file
     NexusFileReader tester(destinationFile);
@@ -1088,7 +1115,7 @@ Instrument cache.
 
     // path to NXtransformations subgoup in NXsource
     FullNXPath transformationsPath = {
-        DEFAULT_ROOT_PATH, "test-instrument" /*instrument name*/,
+        DEFAULT_ROOT_ENTRY_NAME, "test-instrument" /*instrument name*/,
         "source" /*source name*/, TRANSFORMATIONS};
 
     // path to NXsource group
@@ -1097,7 +1124,7 @@ Instrument cache.
 
     // call saveInstrument passing test instrument as parameter
     NexusGeometrySave::saveInstrument(instr, destinationFile,
-                                      DEFAULT_ROOT_PATH);
+                                      DEFAULT_ROOT_ENTRY_NAME, m_mockLogger);
 
     // test utility to check output file
     NexusFileReader tester(destinationFile);
diff --git a/Framework/NexusGeometry/test/mockobjects.h b/Framework/NexusGeometry/test/mockobjects.h
new file mode 100644
index 0000000000000000000000000000000000000000..02adc52e77289a66f8cb3e3634caea04a48c1ada
--- /dev/null
+++ b/Framework/NexusGeometry/test/mockobjects.h
@@ -0,0 +1,23 @@
+#ifndef NEXUSGEOMETRY_MOCKOBJECTS_H
+#define NEXUSGEOMETRY_MOCKOBJECTS_H
+#include "MantidKernel/ProgressBase.h"
+#include "MantidKernel/WarningSuppressions.h"
+#include "MantidNexusGeometry/AbstractLogger.h"
+#include <gmock/gmock.h>
+
+class MockProgressBase : public Mantid::Kernel::ProgressBase {
+public:
+  GNU_DIAG_OFF_SUGGEST_OVERRIDE
+  MOCK_METHOD1(doReport, void(const std::string &));
+  GNU_DIAG_OFF_SUGGEST_OVERRIDE
+};
+
+class MockLogger : public Mantid::NexusGeometry::AbstractLogger {
+public:
+  GNU_DIAG_OFF_SUGGEST_OVERRIDE
+  MOCK_METHOD1(warning, void(const std::string &));
+  MOCK_METHOD1(error, void(const std::string &));
+  GNU_DIAG_ON_SUGGEST_OVERRIDE
+};
+
+#endif // NEXUSGEOMETRY_MOCKOBJECTS_H
diff --git a/Framework/Properties/Mantid.properties.template b/Framework/Properties/Mantid.properties.template
index 143135818e6d1d2273227ef8be737ec97efdefaf..516b49cdc59bae48cddfca5e135cb38726f48de3 100644
--- a/Framework/Properties/Mantid.properties.template
+++ b/Framework/Properties/Mantid.properties.template
@@ -216,7 +216,7 @@ UploaderWebServer = @MANTIDPUBLISHER@
 # Local system path for the script repository.
 ScriptLocalRepository =
 # Base Url for the remote script repository. Not necessarily accessible, it is used to construct longer URLs
-ScriptRepository = http://download.mantidproject.org/scriptrepository/
+ScriptRepository = https://download.mantidproject.org/scriptrepository/
 # Pattern given to ScriptRepository that is used to hide entries from repository to the users. It is a csv string separated with ';'
 ScriptRepositoryIgnore = *pyc;
 
diff --git a/Framework/PythonInterface/CMakeLists.txt b/Framework/PythonInterface/CMakeLists.txt
index d336e3ecf8b54d84412fe2eccc584062d5cc9d5d..03742fcac8d97cc31630f5f7bbf849e26989755a 100644
--- a/Framework/PythonInterface/CMakeLists.txt
+++ b/Framework/PythonInterface/CMakeLists.txt
@@ -112,7 +112,13 @@ add_subdirectory(mantid)
 include ( PythonPackageTargetFunctions )
 
 # Adds `mantid` as a target in the `MantidFramework/Python` folder
-add_python_package ( PythonInterface EGGLINKNAME mantid )
+if(APPLE)
+  set(_install_lib_dirs "${LIB_DIR};${WORKBENCH_LIB_DIR}")
+else()
+  set(_install_lib_dirs "${WORKBENCH_LIB_DIR}")
+endif()
+add_python_package ( PythonInterface EGGLINKNAME mantid
+                     INSTALL_LIB_DIRS ${_install_lib_dirs} )
 set_property ( TARGET PythonInterface PROPERTY FOLDER "MantidFramework/Python" )
 add_dependencies ( PythonInterface PythonInterfaceCore PythonKernelModule
   PythonGeometryModule PythonAPIModule PythonDataObjectsModule
diff --git a/Framework/PythonInterface/mantid/kernel/src/Exports/Property.cpp b/Framework/PythonInterface/mantid/kernel/src/Exports/Property.cpp
index 0397ab6c77fc5baacb2e53a6afc08cd6b3a505a7..0a92e2ca101316ef37cd657f35936b8412fbf750 100644
--- a/Framework/PythonInterface/mantid/kernel/src/Exports/Property.cpp
+++ b/Framework/PythonInterface/mantid/kernel/src/Exports/Property.cpp
@@ -29,6 +29,29 @@ using Mantid::Kernel::Property;
 using Mantid::PythonInterface::std_vector_exporter;
 using namespace boost::python;
 
+namespace {
+
+//
+// gcc 7 with std=c++17 has an issue attaching the EMPTY_*
+// functions with add_static_property when attempting to cast
+// the function pointer to a "const volatile void *":
+//
+//   arg_to_python.hpp:211:66: error: invalid conversion from 'double (*)()
+//   noexcept'
+//                             to 'const volatile void*' [-fpermissive]
+//
+// The noexcept specification appears to prevent the cast in the
+// boost python layer. These functions provide a pass through without the
+// noexcept specifier.
+
+constexpr inline double emptyDouble() { return Mantid::EMPTY_DBL(); }
+
+constexpr inline int emptyInt() { return Mantid::EMPTY_INT(); }
+
+constexpr inline long emptyLong() { return Mantid::EMPTY_LONG(); }
+
+} // namespace
+
 GET_POINTER_SPECIALIZATION(Property)
 GNU_DIAG_OFF("unused-local-typedef")
 // Ignore -Wconversion warnings coming from boost::python
@@ -116,7 +139,7 @@ void export_Property() {
                                   return_value_policy<return_by_value>()),
                     "Return the object managing this property's settings")
 
-      .add_static_property("EMPTY_DBL", &Mantid::EMPTY_DBL)
-      .add_static_property("EMPTY_INT", &Mantid::EMPTY_INT)
-      .add_static_property("EMPTY_LONG", &Mantid::EMPTY_LONG);
+      .add_static_property("EMPTY_DBL", emptyDouble)
+      .add_static_property("EMPTY_INT", emptyInt)
+      .add_static_property("EMPTY_LONG", emptyLong);
 }
diff --git a/Framework/PythonInterface/plugins/algorithms/CalculateEfficiencyCorrection.py b/Framework/PythonInterface/plugins/algorithms/CalculateEfficiencyCorrection.py
index 0fa64666e9c8a8d2c218378b15934d04c451b76c..621e71935d55b67e682c1476312e5e2557aa081f 100644
--- a/Framework/PythonInterface/plugins/algorithms/CalculateEfficiencyCorrection.py
+++ b/Framework/PythonInterface/plugins/algorithms/CalculateEfficiencyCorrection.py
@@ -36,9 +36,9 @@ class CalculateEfficiencyCorrection(PythonAlgorithm):
                 or vanadium measurements.'
 
     def seeAlso(self):
-        return [ "He3TubeEfficiency", "CalculateSampleTransmission",
-                 "DetectorEfficiencyCor", "DetectorEfficiencyCorUser",
-                 "CalculateEfficiency", "ComputeCalibrationCoefVan" ]
+        return ["He3TubeEfficiency", "CalculateSampleTransmission",
+                "DetectorEfficiencyCor", "DetectorEfficiencyCorUser",
+                "CalculateEfficiency", "ComputeCalibrationCoefVan"]
 
     def PyInit(self):
         self.declareProperty(
@@ -64,9 +64,9 @@ class CalculateEfficiencyCorrection(PythonAlgorithm):
             doc='Sample chemical formula used to determine cross-section term.')
 
         self.declareProperty(
-            name='DensityType', defaultValue = 'Mass Density',
+            name='DensityType', defaultValue='Mass Density',
             validator=StringListValidator(['Mass Density', 'Number Density']),
-            doc = 'Use of Mass density (g/cm^3) or Number density (atoms/Angstrom^3)')
+            doc='Use of Mass density (g/cm^3) or Number density (atoms/Angstrom^3)')
 
         self.declareProperty(
             name='Density',
@@ -102,9 +102,9 @@ class CalculateEfficiencyCorrection(PythonAlgorithm):
             name='XSectionType',
             defaultValue="AttenuationXSection",
             validator=StringListValidator(['AttenuationXSection', 'TotalXSection']),
-            doc = 'Use either the absorption  or total cross section in exponential term. \
+            doc='Use either the absorption  or total cross section in exponential term. \
                    The absorption cross section is for monitor-type corrections and \
-                   the total cross section is for transmission-type corrections' )
+                   the total cross section is for transmission-type corrections')
 
     def validateInputs(self):
         issues = dict()
@@ -216,7 +216,7 @@ class CalculateEfficiencyCorrection(PythonAlgorithm):
         xs_term = ref_absXS * self._efficiency_wavelength / TABULATED_WAVELENGTH
         if self._xsection_type == "TotalXSection":
             xs_term += material.totalScatterXSection()
-        self._area_density = - np.log( 1.0 - self._efficiency) / xs_term
+        self._area_density = - np.log(1.0 - self._efficiency) / xs_term
 
     def _calculate_area_density_from_density(self):
         """Calculates area density (atom/cm^2) using number density and thickness."""
@@ -229,7 +229,7 @@ class CalculateEfficiencyCorrection(PythonAlgorithm):
     def _calculate_alpha_absXS_term(self):
         """Calculates absorption XS alpha term = area_density * absXS / 1.7982"""
         material = self._output_ws.sample().getMaterial()
-        absXS = material.absorbXSection()  / TABULATED_WAVELENGTH
+        absXS = material.absorbXSection() / TABULATED_WAVELENGTH
         self._alpha_absXS = self._area_density * absXS
 
     def _calculate_alpha_scatXS_term(self):
diff --git a/Framework/PythonInterface/plugins/algorithms/FitIncidentSpectrum.py b/Framework/PythonInterface/plugins/algorithms/FitIncidentSpectrum.py
new file mode 100644
index 0000000000000000000000000000000000000000..42115e750febd759436bab73b2d056097369da8f
--- /dev/null
+++ b/Framework/PythonInterface/plugins/algorithms/FitIncidentSpectrum.py
@@ -0,0 +1,182 @@
+# 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
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import absolute_import, division, print_function
+from copy import copy
+import numpy as np
+from scipy import signal, ndimage, interpolate
+from mantid.api import AlgorithmFactory, MatrixWorkspaceProperty, PythonAlgorithm
+from mantid.kernel import Direction, StringListValidator, FloatArrayProperty, RebinParamsValidator
+from mantid.simpleapi import CreateWorkspace, Rebin, SplineSmoothing
+
+
+class FitIncidentSpectrum(PythonAlgorithm):
+    _input_ws = None
+    _output_ws = None
+    _scipy_not_old = hasattr(interpolate.UnivariateSpline, "derivative")
+    # check if scipy version is greater than 0.12.1 i.e it has the derivative function
+
+    def category(self):
+        return 'Diffraction\\Fitting'
+
+    def name(self):
+        return 'FitIncidentSpectrum'
+
+    def summary(self):
+        return 'Calculate a fit for an incident spectrum using different methods. ' \
+               'Outputs a workspace containing the functionalized fit and its first ' \
+               'derivative.'
+
+    def version(self):
+        return 1
+
+    def PyInit(self):
+        self.declareProperty(
+            MatrixWorkspaceProperty('InputWorkspace', '',
+                                    direction=Direction.Input,),
+            doc='Incident spectrum to be fit.')
+
+        self.declareProperty(
+            MatrixWorkspaceProperty('OutputWorkspace', '',
+                                    direction=Direction.Output),
+            doc='Output workspace containing the fit and it\'s first derivative.')
+
+        self.declareProperty(FloatArrayProperty(name="BinningForCalc",
+                                                validator=RebinParamsValidator(AllowEmpty=True),
+                                                direction=Direction.Input),
+                             doc='Bin range for calculation given as an array of floats in the same format as `Rebin`: '
+                                 '[Start],[Increment],[End]. If empty use default binning. The calculated '
+                                 'spectrum will use this binning')
+
+        self.declareProperty(FloatArrayProperty(name="BinningForFit",
+                                                validator=RebinParamsValidator(AllowEmpty=True),
+                                                direction=Direction.Input),
+                             doc='Bin range for fitting given as an array of floats in the same format as `Rebin`: '
+                                 '[Start],[Increment],[End]. If empty use BinningForCalc. The '
+                                 'incident spectrum will be rebined to this range before being fit.')
+
+        self.declareProperty(
+            name='FitSpectrumWith',
+            defaultValue='GaussConvCubicSpline',
+            validator=StringListValidator(['GaussConvCubicSpline', 'CubicSpline', 'CubicSplineViaMantid']),
+            doc='The method for fitting the incident spectrum.')
+
+    def _setup(self):
+        self._input_ws = self.getProperty('InputWorkspace').value
+        self._output_ws = self.getProperty('OutputWorkspace').valueAsStr
+        self._binning_for_fit = self.getProperty('BinningForFit').value
+        self._fit_spectrum_with = self.getProperty('FitSpectrumWith').value
+        self._binning_for_calc = self.getProperty('BinningForCalc').value
+        if not self._binning_for_calc.all():
+            x = self._input_ws.readX(0)
+            self._binning_for_calc = [str(i) for i in [min(x), x[1] - x[0], max(x) + x[1] - x[0]]]
+
+    def PyExec(self):
+        self._setup()
+
+        x = np.arange(self._binning_for_calc[0], self._binning_for_calc[2], self._binning_for_calc[1])
+        incident_index = 0
+        if not self._binning_for_fit.all():
+            rebinned = Rebin(
+                self._input_ws,
+                Params=self._binning_for_fit,
+                PreserveEvents=True,
+                StoreInADS=False)
+            x_fit = np.array(rebinned.readX(incident_index))
+            y_fit = np.array(rebinned.readY(incident_index))
+        else:
+            x_fit = np.array(self._input_ws.readX(incident_index))
+            y_fit = np.array(self._input_ws.readY(incident_index))
+
+        if len(x_fit) != len(y_fit):
+            x_fit = x_fit[:-1]
+
+        if self._fit_spectrum_with == 'CubicSpline':
+            # Fit using cubic spline
+            fit, fit_prime = self.fit_cubic_spline(x_fit, y_fit, x, s=1e7)
+        elif self._fit_spectrum_with == 'CubicSplineViaMantid':
+            # Fit using cubic spline via Mantid
+            fit, fit_prime = self.fit_cubic_spline_via_mantid_spline_smoothing(
+                self._input_ws,
+                params_input=self._binning_for_fit,
+                params_output=self._binning_for_calc,
+                Error=0.0001,
+                MaxNumberOfBreaks=0)
+        elif self._fit_spectrum_with == 'GaussConvCubicSpline':
+            # Fit using Gauss conv cubic spline
+            fit, fit_prime = self.fit_cubic_spline_with_gauss_conv(x_fit, y_fit, x, sigma=0.5)
+
+        # Create output workspace
+        output_workspace = CreateWorkspace(
+            DataX=x,
+            DataY=np.append(fit, fit_prime),
+            UnitX='Wavelength',
+            NSpec=2,
+            Distribution=False,
+            StoreInADS=False)
+        self.setProperty("OutputWorkspace", output_workspace)
+
+    def fit_cubic_spline_with_gauss_conv(self, x_fit, y_fit, x, n_gouss=39, sigma=3):
+        # Fit with Cubic Spline using a Gaussian Convolution to get weights
+        def moving_average(y, n=n_gouss, sig=sigma):
+            b = signal.gaussian(n, sig)
+            average = ndimage.filters.convolve1d(y, b / b.sum())
+            var = ndimage.filters.convolve1d(np.power(y - average, 2), b / b.sum())
+            return average, var
+
+        avg, var = moving_average(y_fit)
+        spline_fit = interpolate.UnivariateSpline(x_fit, y_fit, w=1. / np.sqrt(var))
+        fit = spline_fit(x)
+
+        if self._scipy_not_old:
+            spline_fit_prime = spline_fit.derivative()
+            fit_prime = spline_fit_prime(x)
+        else:
+            index = np.arange(len(x))
+            fit_prime = np.empty(len(x))
+            for pos in index:
+                    dx = (x[1] - x[0])/1000
+                    y1 = spline_fit(x[pos] - dx)
+                    y2 = spline_fit(x[pos] + dx)
+                    fit_prime[pos] = (y2-y1)/dx
+        return fit, fit_prime
+
+    def fit_cubic_spline(self, x_fit, y_fit, x, s=1e15):
+        # Fit with Cubic Spline
+        tck = interpolate.splrep(x_fit, y_fit, s=s)
+        fit = interpolate.splev(x, tck, der=0)
+        fit_prime = interpolate.splev(x, tck, der=1)
+        return fit, fit_prime
+
+    def fit_cubic_spline_via_mantid_spline_smoothing(self, InputWorkspace, params_input, params_output, **kwargs):
+        # Fit with Cubic Spline using the mantid SplineSmoothing algorithm
+        rebinned = Rebin(
+            InputWorkspace=InputWorkspace,
+            Params=params_input,
+            PreserveEvents=True,
+            StoreInADS=False)
+        fit_tuple = SplineSmoothing(
+            InputWorkspace=rebinned,
+            OutputWorkspaceDeriv='fit_prime',
+            DerivOrder=1,
+            StoreInADS=False,
+            **kwargs)
+        fit = Rebin(
+            InputWorkspace=fit_tuple.OutputWorkspace,
+            Params=params_output,
+            PreserveEvents=True,
+            StoreInADS=False)
+        fit_prime = Rebin(
+            InputWorkspace=fit_tuple.OutputWorkspaceDeriv[0],
+            Params=params_output,
+            PreserveEvents=True,
+            StoreInADS=False)
+        fit_array = copy(fit.readY(0))
+        fit_prime_array = copy(fit_prime.readY(0))
+        return fit_array, fit_prime_array
+
+
+AlgorithmFactory.subscribe(FitIncidentSpectrum)
diff --git a/Framework/PythonInterface/setup.py.in b/Framework/PythonInterface/setup.py.in
index 90f00b2032aa24c385a7af804997e69185d37bfa..c63a6f16cddfab4d722d6b0ca24330b337703119 100644
--- a/Framework/PythonInterface/setup.py.in
+++ b/Framework/PythonInterface/setup.py.in
@@ -14,7 +14,7 @@ from setuptools import find_packages, setup
 setup(
     name='mantid',
     version='@VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@',
-    packages=find_packages(exclude=['*.test']),
+    packages=find_packages(exclude=['*.test', 'plugins*']),
     package_data={'': ['*.ui']},
     @SETUPTOOLS_BUILD_COMMANDS_USE@
 )
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt
index e231e11cdba53a695af540e55bc8b6ed1d940624..de141d533bd47196aef4e94bb450343edc54ff6b 100644
--- a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt
@@ -44,6 +44,7 @@ set(TEST_PY_FILES
     ExportSpectraMaskTest.py
     FilterLogByTimeTest.py
     FitGaussianTest.py
+    FitIncidentSpectrumTest.py
     FractionalIndexingTest.py
     GetEiT0atSNSTest.py
     HB2AReduceTest.py
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/CalculateEfficiencyCorrectionTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/CalculateEfficiencyCorrectionTest.py
index 5254b3e668d3e61e904b15695992ea6c6e413743..86693365da9347434a6884a683d650bdfa27c03d 100644
--- a/Framework/PythonInterface/test/python/plugins/algorithms/CalculateEfficiencyCorrectionTest.py
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/CalculateEfficiencyCorrectionTest.py
@@ -19,15 +19,15 @@ class CalculateEfficiencyCorrectionTest(unittest.TestCase):
     _input_wksp = "input_wksp"
     _correction_wksp = "correction_wksp"
     _output_wksp = "output_wksp"
-    _wavelengths="0.2,0.01,4.0"
+    _wavelengths = "0.2,0.01,4.0"
     _alpha = 0.693
     _chemical_formula = "(He3)"
     _number_density = 0.0002336682167635477
     _mass_density = 0.0011702649036052439
     _efficiency1_forAbsXS = 0.712390781371
-    _efficiency2_forAbsXS = { "Efficiency": 0.74110947758, "Wavelength": 1.95}
+    _efficiency2_forAbsXS = {"Efficiency": 0.74110947758, "Wavelength": 1.95}
     _efficiency1_forTotalXS = 0.712793729636
-    _efficiency2_forTotalXS = { "Efficiency": 0.741472190178, "Wavelength": 1.95}
+    _efficiency2_forTotalXS = {"Efficiency": 0.741472190178, "Wavelength": 1.95}
     _thickness = 1.0
 
     def setUp(self):
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/FitIncidentSpectrumTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/FitIncidentSpectrumTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8e7013954dda30f592227e7012a933f5d61e504
--- /dev/null
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/FitIncidentSpectrumTest.py
@@ -0,0 +1,102 @@
+# 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
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+import numpy as np
+from mantid.simpleapi import AnalysisDataService, FitIncidentSpectrum, CalculateEfficiencyCorrection, CloneWorkspace, ConvertToPointData, \
+    CreateSampleWorkspace, DeleteWorkspace, LoadAscii, Multiply, CreateWorkspace, Rebin, Divide
+from testhelpers import run_algorithm
+
+
+class FitIncidentSpectrumTest(unittest.TestCase):
+
+    incident_wksp_name = 'incident_spectrum_wksp'
+    phiMax = 6324
+    phiEpi = 786
+    alpha = 0.099
+    lambda1 = 0.67143
+    lambda2 = 0.06075
+    lambdaT = 1.58
+    binning_default = "0.2,0.01,4.0"
+
+    def setUp(self):
+        # Create the workspace to hold the already corrected incident spectrum
+        self.incident_wksp = CreateWorkspace(OutputWorkspace=self.incident_wksp_name,
+                                             NSpec=1,
+                                             DataX=[0],
+                                             DataY=[0],
+                                             UnitX='Wavelength',
+                                             VerticalAxisUnit='Text',
+                                             VerticalAxisValues='IncidentSpectrum')
+        self.incident_wksp = Rebin(InputWorkspace=self.incident_wksp,
+                                   OutputWorkspace="foobar",
+                                   Params=self.binning_default)
+        self.incident_wksp = ConvertToPointData(InputWorkspace=self.incident_wksp,
+                                                OutputWorkspace="foobar")
+        # Add the incident spectrum to the workspace
+        corrected_spectrum = self.generate_incident_spectrum(self.incident_wksp.readX(0),
+                                                             self.phiMax,
+                                                             self.phiEpi,
+                                                             self.alpha,
+                                                             self.lambda1,
+                                                             self.lambda2,
+                                                             self.lambdaT)
+        self.incident_wksp.setY(0, corrected_spectrum)
+        self.agl_instance = FitIncidentSpectrum
+
+    def generate_incident_spectrum(self, wavelengths, phi_max, phi_epi, alpha, lambda_1, lambda_2, lambda_T):
+        delta_term = 1. / (1. + np.exp((wavelengths - lambda_1) / lambda_2))
+        term1 = phi_max * (lambda_T ** 4. / wavelengths ** 5.) * np.exp(-(lambda_T / wavelengths) ** 2.)
+        term2 = phi_epi * delta_term / (wavelengths ** (1 + 2 * alpha))
+        return term1 + term2
+
+    def test_fit_cubic_spline_with_gauss_conv_produces_fit_with_same_range_as_binning_for_calc(self):
+        binning_for_calc = "0.2,0.1,3.0"
+        binning_for_fit = "0.2,0.1,4.0"
+        alg_test = run_algorithm(
+            "FitIncidentSpectrum",
+            InputWorkspace=self.incident_wksp,
+            OutputWorkspace="fit_wksp",
+            BinningForCalc=binning_for_calc,
+            BinningForFit=binning_for_fit,
+            FitSpectrumWith="GaussConvCubicSpline")
+        self.assertTrue(alg_test.isExecuted())
+        fit_wksp = AnalysisDataService.retrieve("fit_wksp")
+        self.assertEqual(fit_wksp.readX(0).all(), np.arange(0.2, 3, 0.01).all())
+
+    def test_fit_cubic_spline_produces_fit_with_same_range_as_binning_for_calc(self):
+        binning_for_calc = "0.2,0.1,3.0"
+        binning_for_fit = "0.2,0.1,4.0"
+        alg_test = run_algorithm(
+            "FitIncidentSpectrum",
+            InputWorkspace=self.incident_wksp,
+            OutputWorkspace="fit_wksp",
+            BinningForCalc=binning_for_calc,
+            BinningForFit=binning_for_fit,
+            FitSpectrumWith="CubicSpline")
+        self.assertTrue(alg_test.isExecuted())
+        fit_wksp = AnalysisDataService.retrieve("fit_wksp")
+        self.assertEqual(fit_wksp.readX(0).all(), np.arange(0.2, 3, 0.1).all())
+
+    def test_fit_cubic_spline_via_mantid_produces_fit_with_same_range_as_binning_for_calc(self):
+        binning_for_calc = "0.2,0.1,3.0"
+        binning_for_fit = "0.2,0.1,4.0"
+        alg_test = run_algorithm(
+            "FitIncidentSpectrum",
+            InputWorkspace=self.incident_wksp,
+            OutputWorkspace="fit_wksp",
+            BinningForCalc=binning_for_calc,
+            BinningForFit=binning_for_fit,
+            FitSpectrumWith="CubicSplineViaMantid")
+        self.assertTrue(alg_test.isExecuted())
+        fit_wksp = AnalysisDataService.retrieve("fit_wksp")
+        self.assertEqual(fit_wksp.readX(0).all(), np.arange(0.2, 3, 0.1).all())
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/SaveReflectionsTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/SaveReflectionsTest.py
index 403019976a8cba6b9396f805df8ddfb55c9ce7e2..74d18d93757a7a82025527b7babff30a4599be51 100644
--- a/Framework/PythonInterface/test/python/plugins/algorithms/SaveReflectionsTest.py
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/SaveReflectionsTest.py
@@ -15,7 +15,6 @@ from mantid.simpleapi import SaveReflections, DeleteWorkspace, LoadEmptyInstrume
 
 
 class SaveReflectionsTest(unittest.TestCase):
-
     def setUp(self):
         self._workspace = self._create_peaks_workspace()
         self._test_dir = tempfile.mkdtemp()
@@ -34,7 +33,7 @@ class SaveReflectionsTest(unittest.TestCase):
 
         # Add a bunch of random peaks that happen to fall on the
         # detetor bank defined in the IDF
-        center_q = np.array([-5.1302,2.5651,3.71809])
+        center_q = np.array([-5.1302, 2.5651, 3.71809])
         qs = []
         for i in np.arange(0, 1, 0.1):
             for j in np.arange(-0.5, 0, 0.1):
@@ -110,7 +109,7 @@ class SaveReflectionsTest(unittest.TestCase):
         SaveReflections(InputWorkspace=self._workspace, Filename=file_name, Format=output_format)
 
         # Assert
-        self.assertTrue(compare_file(reference_result, file_name))
+        self._assert_file_content_equal(reference_result, file_name)
 
     def test_save_fullprof_format_modulated(self):
         # Arrange
@@ -123,7 +122,7 @@ class SaveReflectionsTest(unittest.TestCase):
         SaveReflections(InputWorkspace=workspace, Filename=file_name, Format=output_format)
 
         # Assert
-        self.assertTrue(compare_file(reference_result, file_name))
+        self._assert_file_content_equal(reference_result, file_name)
 
     def test_save_jana_format(self):
         # Arrange
@@ -135,7 +134,7 @@ class SaveReflectionsTest(unittest.TestCase):
         SaveReflections(InputWorkspace=self._workspace, Filename=file_name, Format=output_format)
 
         # Assert
-        self.assertTrue(compare_file(reference_result, file_name))
+        self._assert_file_content_equal(reference_result, file_name)
 
     def test_save_jana_format_modulated(self):
         # Arrange
@@ -148,7 +147,7 @@ class SaveReflectionsTest(unittest.TestCase):
         SaveReflections(InputWorkspace=workspace, Filename=file_name, Format=output_format)
 
         # Assert
-        self.assertTrue(compare_file(reference_result, file_name))
+        self._assert_file_content_equal(reference_result, file_name)
 
     def test_save_GSAS_format(self):
         # Arrange
@@ -160,7 +159,8 @@ class SaveReflectionsTest(unittest.TestCase):
         SaveReflections(InputWorkspace=self._workspace, Filename=file_name, Format=output_format)
 
         # Assert
-        self.assertTrue(compare_file(reference_result, file_name))
+        #self._assert_file_content_equal(reference_result, file_name))
+        self._assert_file_content_equal(reference_result, file_name)
 
     def test_save_GSAS_format_modulated(self):
         # Arrange
@@ -169,7 +169,11 @@ class SaveReflectionsTest(unittest.TestCase):
         output_format = "GSAS"
 
         # Act
-        self.assertRaises(RuntimeError, SaveReflections, InputWorkspace=workspace, Filename=file_name, Format=output_format)
+        self.assertRaises(RuntimeError,
+                          SaveReflections,
+                          InputWorkspace=workspace,
+                          Filename=file_name,
+                          Format=output_format)
 
     def test_save_SHELX_format(self):
         # Arrange
@@ -181,7 +185,7 @@ class SaveReflectionsTest(unittest.TestCase):
         SaveReflections(InputWorkspace=self._workspace, Filename=file_name, Format=output_format)
 
         # Assert
-        self.assertTrue(compare_file(reference_result, file_name))
+        self._assert_file_content_equal(reference_result, file_name)
 
     def test_save_SHELX_format_modulated(self):
         # Arrange
@@ -190,7 +194,11 @@ class SaveReflectionsTest(unittest.TestCase):
         output_format = "SHELX"
 
         # Act
-        self.assertRaises(RuntimeError, SaveReflections, InputWorkspace=workspace, Filename=file_name, Format=output_format)
+        self.assertRaises(RuntimeError,
+                          SaveReflections,
+                          InputWorkspace=workspace,
+                          Filename=file_name,
+                          Format=output_format)
 
     def test_save_invalid_format(self):
         # Arrange
@@ -198,21 +206,22 @@ class SaveReflectionsTest(unittest.TestCase):
         output_format = "InvalidFormatName"
 
         # Act
-        self.assertRaises(ValueError, SaveReflections, InputWorkspace=self._workspace, Filename=file_name, Format=output_format)
-
-
-def compare_file(reference_result, file_name):
-    with open(reference_result, 'r') as ref_file:
+        self.assertRaises(ValueError,
+                          SaveReflections,
+                          InputWorkspace=self._workspace,
+                          Filename=file_name,
+                          Format=output_format)
+
+    # Private api
+    def _assert_file_content_equal(self, reference_result, file_name):
+        with open(reference_result, 'r') as ref_file:
+            ref_lines = list(map(lambda x: x.strip(), ref_file.readlines()))
         with open(file_name, 'r') as actual_file:
-            ref_lines = ref_file.readlines()
-            actual_lines = actual_file.readlines()
-            ref_lines = map(lambda x: x.strip(), ref_lines)
-            actual_lines = map(lambda x: x.strip(), actual_lines)
-            for ref_line, actual_line in zip(ref_lines, actual_lines):
-                if ref_line != actual_line:
-                    return False
-
-    return True
+            actual_lines = list(map(lambda x: x.strip(), actual_file.readlines()))
+
+        self.assertEqual(len(ref_lines), len(actual_lines))
+        for ref_line, actual_line in zip(ref_lines, actual_lines):
+            self.assertEqual(ref_line, actual_line)
 
 
 if __name__ == '__main__':
diff --git a/Framework/ScriptRepository/src/ScriptRepositoryImpl.cpp b/Framework/ScriptRepository/src/ScriptRepositoryImpl.cpp
index e3389c08183972f62399c4bdc3f379424a60d6b9..b21bd4f70d15bcfe45e396b14269bf686a9c29ac 100644
--- a/Framework/ScriptRepository/src/ScriptRepositoryImpl.cpp
+++ b/Framework/ScriptRepository/src/ScriptRepositoryImpl.cpp
@@ -161,7 +161,7 @@ DECLARE_SCRIPTREPOSITORY(ScriptRepositoryImpl)
  ScriptrepositoryImpl sharing();
  // apply given values
  ScriptrepositoryImpl sharing("/tmp/gitrep",
- "http://repository.mantidproject.com");
+ "https://repository.mantidproject.com");
  @endcode
  */
 ScriptRepositoryImpl::ScriptRepositoryImpl(const std::string &local_rep,
@@ -983,7 +983,7 @@ void ScriptRepositoryImpl::updateRepositoryJson(const std::string &path,
  *changing the word
  * publish to remove. For example:
  *
- * http://upload.mantidproject.org/scriptrepository/payload/remove
+ * https://upload.mantidproject.org/scriptrepository/payload/remove
  *
  * The server will them create a git commit deleting the file. And will reply
  *with a json string
diff --git a/Framework/ScriptRepository/test/ScriptRepositoryTestImpl.h b/Framework/ScriptRepository/test/ScriptRepositoryTestImpl.h
index 98df977aa201a22f41fa2442ffd61354ebab8bd0..9f462eb5a39f643f8b05dba696ff390544a2485e 100644
--- a/Framework/ScriptRepository/test/ScriptRepositoryTestImpl.h
+++ b/Framework/ScriptRepository/test/ScriptRepositoryTestImpl.h
@@ -7,43 +7,25 @@
 #ifndef SCRIPTREPOSITORYIMPLTEST_H_
 #define SCRIPTREPOSITORYIMPLTEST_H_
 
+#include "MantidKernel/ConfigService.h"
 #include "MantidScriptRepository/ScriptRepositoryImpl.h"
+#include <Poco/DateTimeFormatter.h>
 #include <Poco/File.h>
-#include <Poco/Path.h>
-#include <cxxtest/TestSuite.h>
-// Visual Studion compains with the inclusion of Poco/FileStream
-// disabling this warning.
-#if defined(_WIN32) || defined(_WIN64)
-#pragma warning(push)
-#pragma warning(disable : 4250)
 #include <Poco/FileStream.h>
-#pragma warning(pop)
-#else
-#include <Poco/FileStream.h>
-#endif
-#include "MantidKernel/ConfigService.h"
-#include <Poco/DateTimeFormatter.h>
+#include <Poco/Path.h>
 #include <Poco/TemporaryFile.h>
-#include <algorithm>
 #include <boost/algorithm/string.hpp>
+#include <cxxtest/TestSuite.h>
+
+#include <algorithm>
+
+using Mantid::API::ScriptRepoException;
+using Mantid::API::ScriptRepositoryImpl;
 using Mantid::Kernel::ConfigService;
 using Mantid::Kernel::ConfigServiceImpl;
 using Mantid::Types::Core::DateAndTime;
-using namespace std;
-using Mantid::API::ScriptRepoException;
-using Mantid::API::ScriptRepositoryImpl;
-
-const bool TEST_MANUALLY = false;
-
-/** To run this tests (LINUX):
-
-    make ScriptRepositoryTest
-
-    ctest -R ScriptRepositoryTest [-verbose]
 
- **/
-
-const std::string REPOSITORYJSON =
+constexpr auto REPOSITORYJSON =
     "{\n"
     "\"TofConv\":\n"
     "{\n"
@@ -76,10 +58,9 @@ const std::string REPOSITORYJSON =
     "}\n"
     "}\n";
 
-const std::string TOFCONV_README = "This is the content of TOFCONV_README";
-const std::string TOFCONV_CONVERTER = "print 'hello world'";
-
-const std::string webserverurl = "http://localhost";
+constexpr auto TOFCONV_README = "This is the content of TOFCONV_README";
+constexpr auto TOFCONV_CONVERTER = "print 'hello world'";
+constexpr auto WEBSERVER_URL = "https://localhost";
 
 /** The ScriptRepositoryTest aims to ensure and protect the logic and
 the interfaces described for ScriptRepository without requiring
@@ -140,7 +121,7 @@ public:
     // request to ping the site
     if (local_file_path.empty())
       return;
-    if (url_file.find("http://") == std::string::npos) {
+    if (url_file.find("https://") == std::string::npos) {
       throw ScriptRepoException("Invalid url to download");
     }
     Poco::FileStream _out(local_file_path);
@@ -243,7 +224,7 @@ public:
     backup_local_repository_path = config.getString("ScriptLocalRepository");
     local_rep = std::string(Poco::Path::current()).append("mytemprepository/");
     TS_ASSERT_THROWS_NOTHING(repo = std::make_unique<ScriptRepositoryImplLocal>(
-                                 local_rep, webserverurl));
+                                 local_rep, WEBSERVER_URL));
   }
 
   // ensure that the local files are free from the test created.
@@ -265,7 +246,7 @@ public:
    ******************/
   void test_doDownloadFile() {
     // ensure it can ping the remote url
-    TS_ASSERT_THROWS_NOTHING(repo->doDownloadFile(webserverurl, ""));
+    TS_ASSERT_THROWS_NOTHING(repo->doDownloadFile(WEBSERVER_URL, ""));
 
     // simulate the installation.
     Poco::File dir(local_rep);
@@ -276,7 +257,7 @@ public:
       std::string local_j_file =
           std::string(local_rep).append("/.repository.json");
       TS_ASSERT_THROWS_NOTHING(repo->doDownloadFile(
-          std::string(webserverurl).append("/repository.json"), local_j_file));
+          std::string(WEBSERVER_URL).append("/repository.json"), local_j_file));
     }
     {
       // ensure it can download TofConv/README.txt
@@ -285,7 +266,7 @@ public:
       Poco::File dir(std::string(local_rep).append("/TofConv"));
       dir.createDirectories();
       TS_ASSERT_THROWS_NOTHING(repo->doDownloadFile(
-          std::string(webserverurl).append("/TofConv/README.txt"),
+          std::string(WEBSERVER_URL).append("/TofConv/README.txt"),
           local_j_file));
       Poco::File f(local_j_file);
       TS_ASSERT(f.exists());
@@ -463,7 +444,7 @@ public:
    *************************************/
   void test_update() {
     TS_ASSERT_THROWS_NOTHING(repo->install(local_rep));
-    std::vector<string> list_of_files;
+    std::vector<std::string> list_of_files;
     TS_ASSERT_THROWS_NOTHING(list_of_files = repo->listFiles());
     TS_ASSERT(list_of_files.size() == 5);
 
@@ -494,7 +475,7 @@ public:
 
   void test_auto_update() {
     TS_ASSERT_THROWS_NOTHING(repo->install(local_rep));
-    std::vector<string> list_of_files;
+    std::vector<std::string> list_of_files;
     TS_ASSERT_THROWS_NOTHING(list_of_files = repo->listFiles());
     TS_ASSERT(list_of_files.size() == 5);
     std::string file_name = "TofConv/README.txt";
@@ -531,7 +512,7 @@ public:
 
   void test_auto_update_cascade() {
     TS_ASSERT_THROWS_NOTHING(repo->install(local_rep));
-    std::vector<string> list_of_files;
+    std::vector<std::string> list_of_files;
     TS_ASSERT_THROWS_NOTHING(list_of_files = repo->listFiles());
     TS_ASSERT(list_of_files.size() == 5);
     std::string folder_name = "TofConv";
@@ -581,7 +562,7 @@ public:
 
   void test_auto_update_cascade_remove_all_internal_files() {
     TS_ASSERT_THROWS_NOTHING(repo->install(local_rep));
-    std::vector<string> list_of_files;
+    std::vector<std::string> list_of_files;
     TS_ASSERT_THROWS_NOTHING(list_of_files = repo->listFiles());
     TS_ASSERT(list_of_files.size() == 5);
     std::string folder_name = "TofConv";
@@ -668,54 +649,30 @@ public:
     TS_ASSERT(repo->fileStatus(file_name) == Mantid::API::BOTH_UNCHANGED);
     TS_ASSERT(repo->fileStatus(dir_name) == Mantid::API::BOTH_UNCHANGED);
 
-    /**
-       We have to change the local file in order to verify if
-       it changes its local status.
-
-       unfortunatelly, the only way to see a local change, is giving some time,
-       at least
-       one seccond.
-
-       so, we will propose two versions, but for production, we will use the
-       fastest one.
-    */
-
-    if (TEST_MANUALLY) {
-#if defined(WIN32) || defined(WIN64)
-      Sleep(1000000);
-#else
-      sleep(1);
-#endif
-      Poco::FileStream _ap(
-          std::string(local_rep).append("/").append(file_name));
-      _ap << "Local change";
-      _ap.close();
-    } else {
-      // we will simulate the change of the file, by, changin the local.json
-      // file
-
-      Poco::FileStream ss(std::string(local_rep).append("/local.json"));
-      ss << "{\n"
-         << "\"TofConv/README.txt\":\n"
-         << "{\n"
-         << "\"downloaded_date\": \"2013-Mar-07 14:30:09\",\n"
-         << "\"downloaded_pubdate\": \"2012-Feb-13 10:02:50\"\n"
-         << "}\n"
-         << "}";
-      ss.close();
-      std::string localjson = string(local_rep).append("/.local.json");
-      Poco::File f(std::string(local_rep).append("/local.json"));
+    // we will simulate the change of the file, by, changin the local.json
+    // file
+
+    Poco::FileStream ss(std::string(local_rep).append("/local.json"));
+    ss << "{\n"
+       << "\"TofConv/README.txt\":\n"
+       << "{\n"
+       << "\"downloaded_date\": \"2013-Mar-07 14:30:09\",\n"
+       << "\"downloaded_pubdate\": \"2012-Feb-13 10:02:50\"\n"
+       << "}\n"
+       << "}";
+    ss.close();
+    std::string localjson = std::string(local_rep).append("/.local.json");
+    Poco::File f(std::string(local_rep).append("/local.json"));
 
 #if defined(_WIN32) || defined(_WIN64)
-      // set the .repository.json and .local.json hidden
-      SetFileAttributes(localjson.c_str(), FILE_ATTRIBUTE_NORMAL);
+    // set the .repository.json and .local.json hidden
+    SetFileAttributes(localjson.c_str(), FILE_ATTRIBUTE_NORMAL);
 #endif
-      f.moveTo(localjson);
+    f.moveTo(localjson);
 #if defined(_WIN32) || defined(_WIN64)
-      // set the .repository.json and .local.json hidden
-      SetFileAttributes(localjson.c_str(), FILE_ATTRIBUTE_HIDDEN);
+    // set the .repository.json and .local.json hidden
+    SetFileAttributes(localjson.c_str(), FILE_ATTRIBUTE_HIDDEN);
 #endif
-    }
 
     TS_ASSERT_THROWS_NOTHING(repo->listFiles());
 
@@ -798,54 +755,30 @@ public:
     // do download
     TS_ASSERT_THROWS_NOTHING(repo->download(file_name));
 
-    /**
-       We have to change the local file in order to verify if
-       it changes its local status.
-
-       unfortunatelly, the only way to see a local change, is giving some time,
-       at least
-       one seccond.
-
-       so, we will propose two versions, but for production, we will use the
-       fastest one.
-    */
-
-    if (TEST_MANUALLY) {
-#if defined(WIN32) || defined(WIN64)
-      Sleep(1000000);
-#else
-      sleep(1);
-#endif
-      Poco::FileStream _ap(
-          std::string(local_rep).append("/").append(file_name));
-      _ap << "Local change";
-      _ap.close();
-    } else {
-      // we will simulate the change of the file, by, changin the local.json
-      // file
-
-      Poco::FileStream ss(std::string(local_rep).append("/local.json"));
-      ss << "{\n"
-         << "\"TofConv/README.txt\":\n"
-         << "{\n"
-         << "\"downloaded_date\": \"2013-Mar-07 14:30:09\",\n"
-         << "\"downloaded_pubdate\": \"2012-Feb-13 10:02:50\"\n"
-         << "}\n"
-         << "}";
-      ss.close();
-      std::string localjson = string(local_rep).append("/.local.json");
-      Poco::File f(std::string(local_rep).append("/local.json"));
+    // we will simulate the change of the file, by, changin the local.json
+    // file
+
+    Poco::FileStream ss(std::string(local_rep).append("/local.json"));
+    ss << "{\n"
+       << "\"TofConv/README.txt\":\n"
+       << "{\n"
+       << "\"downloaded_date\": \"2013-Mar-07 14:30:09\",\n"
+       << "\"downloaded_pubdate\": \"2012-Feb-13 10:02:50\"\n"
+       << "}\n"
+       << "}";
+    ss.close();
+    std::string localjson = std::string(local_rep).append("/.local.json");
+    Poco::File f(std::string(local_rep).append("/local.json"));
 
 #if defined(_WIN32) || defined(_WIN64)
-      // set the .repository.json and .local.json hidden
-      SetFileAttributes(localjson.c_str(), FILE_ATTRIBUTE_NORMAL);
+    // set the .repository.json and .local.json hidden
+    SetFileAttributes(localjson.c_str(), FILE_ATTRIBUTE_NORMAL);
 #endif
-      f.moveTo(localjson);
+    f.moveTo(localjson);
 #if defined(_WIN32) || defined(_WIN64)
-      // set the .repository.json and .local.json hidden
-      SetFileAttributes(localjson.c_str(), FILE_ATTRIBUTE_HIDDEN);
+    // set the .repository.json and .local.json hidden
+    SetFileAttributes(localjson.c_str(), FILE_ATTRIBUTE_HIDDEN);
 #endif
-    }
 
     // now, we will simulate a new version of the file
     std::string original_time = "2012-Feb-13 10:02:50";
@@ -895,7 +828,7 @@ public:
     boost::replace_all(curr_python_direc, "\\", "/");
     boost::replace_all(direc, "\\", "/");
 
-    TS_ASSERT(curr_python_direc.find(direc) != string::npos);
+    TS_ASSERT(curr_python_direc.find(direc) != std::string::npos);
 
     config.setString("pythonscripts.directories", backup_python_directories);
     config.saveConfig(config.getUserFilename());
diff --git a/Framework/TestHelpers/inc/MantidTestHelpers/FileResource.h b/Framework/TestHelpers/inc/MantidTestHelpers/FileResource.h
index 905e1ace249fd98f7f9b062615d87a17e7a9aef8..0fa8b39909748b9b8211d15847140bf1d2c64f20 100644
--- a/Framework/TestHelpers/inc/MantidTestHelpers/FileResource.h
+++ b/Framework/TestHelpers/inc/MantidTestHelpers/FileResource.h
@@ -16,16 +16,19 @@
 #define MANTID_NEXUSGEOMETRY_FILERESOURCE_H_
 
 #include <boost/filesystem.hpp>
+#include <iostream>
 #include <string>
 
 class FileResource {
 
 public:
-  FileResource(const std::string &fileName);
+  FileResource(const std::string &fileName, bool debugMode = false);
+  void setDebugMode(bool mode);
   std::string fullPath() const;
   ~FileResource();
 
 private:
+  bool m_debugMode;
   boost::filesystem::path m_full_path; // full path to file
   // prevent heap allocation for ScopedFileHandle
 protected:
diff --git a/Framework/TestHelpers/inc/MantidTestHelpers/NexusFileReader.h b/Framework/TestHelpers/inc/MantidTestHelpers/NexusFileReader.h
index 08e198e05f197b23ffc16a0616ad8239581bb7f6..0aeeb8c4ef916892286299556e69d19eb4240e35 100644
--- a/Framework/TestHelpers/inc/MantidTestHelpers/NexusFileReader.h
+++ b/Framework/TestHelpers/inc/MantidTestHelpers/NexusFileReader.h
@@ -74,6 +74,8 @@ void validateStorageType(const H5::DataSet &data) {
 // for unit tests in Nexus Geometry.
 class NexusFileReader {
 
+  bool m_open = false;
+
 public:
   NexusFileReader(const std::string &fullPath) {
     boost::filesystem::path tmp = fullPath;
@@ -82,6 +84,7 @@ public:
       throw std::invalid_argument("no such file.\n");
     } else {
       m_file.openFile(fullPath, H5F_ACC_RDONLY);
+      m_open = true;
     }
   }
 
@@ -124,7 +127,7 @@ public:
   bool parentNXgroupHasChildNXgroup(const std::string &parentNX_CLASS_TYPE,
                                     const std::string &childNX_CLASS_TYPE) {
 
-    H5::Group rootGroup = m_file.openGroup(DEFAULT_ROOT_PATH);
+    H5::Group rootGroup = m_file.openGroup(DEFAULT_ROOT_ENTRY_NAME);
 
     // if specified parent NX class type is NX entry, check the top level of
     // file structure only. (dont take extra step to look for parent group)
@@ -366,6 +369,15 @@ public:
     return attributeValue == attrVal;
   }
 
+  void close() {
+    if (m_open) {
+      m_file.close();
+    }
+    m_open = false;
+  }
+
+  ~NexusFileReader() { close(); }
+
 private:
   H5::H5File m_file;
 
diff --git a/Framework/TestHelpers/src/FileResource.cpp b/Framework/TestHelpers/src/FileResource.cpp
index a8854b2641ce3cf2a4073575bb2b951263936f7c..feddc230d1c8a6077cf2fdbfe09c67b0ff6681bb 100644
--- a/Framework/TestHelpers/src/FileResource.cpp
+++ b/Framework/TestHelpers/src/FileResource.cpp
@@ -9,7 +9,8 @@
 #include <boost/filesystem.hpp>
 #include <string>
 
-FileResource::FileResource(const std::string &fileName) {
+FileResource::FileResource(const std::string &fileName, bool debugMode)
+    : m_debugMode(debugMode) {
 
   const auto temp_dir = boost::filesystem::temp_directory_path();
   auto temp_full_path = temp_dir;
@@ -28,13 +29,19 @@ FileResource::FileResource(const std::string &fileName) {
   }
 }
 
+void FileResource::setDebugMode(bool mode) { m_debugMode = mode; }
 std::string FileResource::fullPath() const {
   return m_full_path.generic_string();
 }
 
 FileResource::~FileResource() {
+
   // file is removed at end of file handle's lifetime
   if (boost::filesystem::is_regular_file(m_full_path)) {
-    boost::filesystem::remove(m_full_path);
+    if (!m_debugMode)
+      boost::filesystem::remove(m_full_path);
+    else
+      std::cout << "Debug file at: " << m_full_path << " not removed. "
+                << std::endl;
   }
 }
diff --git a/MantidPlot/make_package.rb.in b/MantidPlot/make_package.rb.in
index d0c8294ab1a28bc8eb9e44c75413ee579836faba..61bd6e4d35fe7c33cc5cdc30bdfa98d7a0ea1131 100755
--- a/MantidPlot/make_package.rb.in
+++ b/MantidPlot/make_package.rb.in
@@ -210,13 +210,18 @@ if( "@MAKE_VATES@" == "ON" )
   `install_name_tool -id @rpath/libNonOrthogonalSource.dylib plugins/paraview/qt4/libNonOrthogonalSource.dylib`
 end
 
-# use install_tool_name to add an @rpath for the libraries
+# use install_tool_name to add an @rpath for the python dynamic libraries
 Dir["Contents/MacOS/*.so"].each do |library|
-  dependencies = `otool -L #{library}`
   print library, "\n"
   `install_name_tool -add_rpath @loader_path/../MacOS #{library} > /dev/null 2>&1`
 end
 
+# use install_tool_name to add an @rpath for the ParaView python dynamic libraries
+Dir["Contents/Libraries/*.so"].each do |library|
+  print library, "\n"
+  `install_name_tool -add_rpath @loader_path/../Libraries #{library} > /dev/null 2>&1`
+end
+
 #use install_name_tool to change dependencies from /usr/local to libraries in the package.
 search_patterns = ["**/*.dylib","**/*.so","**/MantidPlot"]
 search_patterns.each do |pattern|
@@ -385,11 +390,13 @@ if !File.exist?(mpltoolkit_init)
   `touch #{mpltoolkit_init}`
 end
 
-files = ["gnureadline.so","cycler.py","readline.py","pyparsing.py","mistune.py","decorator.py","kiwisolver.so","subprocess32.py","six.py"]
+files = ["cycler.py","readline.py","pyparsing.py","mistune.py","decorator.py","kiwisolver.so","subprocess32.py","six.py"]
 files.each do |file|
   copyFile("#{pip_site_packages}/#{file}")
 end
 
+# gnureadline is only present on older versions of readline
+copyOptionalFile("#{pip_site_packages}/gnureadline.so")
 # mistune.so isn't present in v0.7
 copyOptionalFile("#{pip_site_packages}/mistune.so")
 
diff --git a/Testing/SystemTests/scripts/InstallerTests.py b/Testing/SystemTests/scripts/InstallerTests.py
index f7bb48f1e381770e45cc63219375e4cea3614f93..e21b4583aa6e50efee3c8fa3ca4085e2c932a5cf 100644
--- a/Testing/SystemTests/scripts/InstallerTests.py
+++ b/Testing/SystemTests/scripts/InstallerTests.py
@@ -10,6 +10,7 @@ from __future__ import (absolute_import, division, print_function)
 import argparse
 import os
 import subprocess
+import sys
 
 from mantidinstaller import (createScriptLog, log, stop, failure, scriptfailure, get_installer, run)
 
@@ -125,9 +126,12 @@ try:
     # and this produces the error: argument -a/--exec-args: expected one argument from runSystemTests.
     # The workaround places a space in the --exec-args parameter that is then stripped off inside
     # runSystemTests.
-    run_test_cmd = '{} {} {}/runSystemTests.py --loglevel={} --executable="{}" --exec-args=" {}"'.format(
+    executor_args = installer.python_args
+    if sys.platform == 'win32':
+        executor_args = ' ' + executor_args
+    run_test_cmd = '{} {} {}/runSystemTests.py --loglevel={} --executable="{}" --exec-args="{}"'.format(
         installer.python_cmd, installer.python_args, THIS_MODULE_DIR,
-        options.log_level, installer.python_cmd, installer.python_args)
+        options.log_level, installer.python_cmd, executor_args)
     run_test_cmd += " -j%i --quiet --output-on-failure" % options.ncores
     if options.test_regex is not None:
         run_test_cmd += " -R " + options.test_regex
diff --git a/Testing/SystemTests/tests/analysis/HRPDPowderDiffraction.py b/Testing/SystemTests/tests/analysis/HRPDPowderDiffraction.py
deleted file mode 100644
index d5b5d77a20becf6cbd3631f8d1be78d34a9b4355..0000000000000000000000000000000000000000
--- a/Testing/SystemTests/tests/analysis/HRPDPowderDiffraction.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# 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
-# SPDX - License - Identifier: GPL - 3.0 +
-#pylint: disable=no-init
-import systemtesting
-from mantid.simpleapi import *
-
-# Simply tests that our LoadRaw and LoadISISNexus algorithms produce the same workspace
-
-
-class HRPDPowderDiffraction(systemtesting.MantidSystemTest):
-
-    def requiredFiles(self):
-        return ["HRP39191.RAW", "hrpd_new_072_01_corr.cal", "HRP39187.RAW", 'HRPDPowderDiffraction.nxs']
-
-    def runTest(self):
-        #Load the Vanadium
-        LoadRaw(Filename="HRP39191.RAW",OutputWorkspace="Vanadium")
-        #mask out the vanadium peaks
-        MaskBins(InputWorkspace="Vanadium",OutputWorkspace="Vanadium",XMin="19970",XMax="20140")
-        MaskBins(InputWorkspace="Vanadium",OutputWorkspace="Vanadium",XMin="39970",XMax="40140")
-        MaskBins(InputWorkspace="Vanadium",OutputWorkspace="Vanadium",XMin="59970",XMax="60140")
-        MaskBins(InputWorkspace="Vanadium",OutputWorkspace="Vanadium",XMin="79970",XMax="80140")
-        MaskBins(InputWorkspace="Vanadium",OutputWorkspace="Vanadium",XMin="99970",XMax="100140")
-        #align vanadium detectors
-        AlignDetectors(InputWorkspace="Vanadium",OutputWorkspace="Vanadium",CalibrationFile="hrpd_new_072_01_corr.cal")
-        #normalise by current
-        NormaliseByCurrent(InputWorkspace="Vanadium",OutputWorkspace="Vanadium")
-        #correct for solid angle
-        SolidAngle(InputWorkspace="Vanadium",OutputWorkspace="Corr")
-        Divide(LHSWorkspace="Vanadium",RHSWorkspace="Corr",OutputWorkspace="Vanadium")
-        #Multiply the solid angle by the integrated vanadium flux between 1.4 and 3 Angstrom
-        ConvertUnits(InputWorkspace="Vanadium",OutputWorkspace="flux",Target="Wavelength")
-        Integration(InputWorkspace="flux",OutputWorkspace="flux",RangeLower="1.4",RangeUpper="3")
-        Multiply(LHSWorkspace="Corr",RHSWorkspace="flux",OutputWorkspace="Corr")
-        #adjust the correction down by a factor of 1000
-        CreateSingleValuedWorkspace(OutputWorkspace="Sc",DataValue="1000")
-        Divide(LHSWorkspace="Corr",RHSWorkspace="Sc",OutputWorkspace="Corr")
-        #Load the Vanadium - a second time
-        LoadRaw(Filename="HRP39191.RAW",OutputWorkspace="Vanadium")
-        #mask out the vanadium peaks
-        MaskBins(InputWorkspace="Vanadium",OutputWorkspace="Vanadium",XMin="19970",XMax="20140")
-        MaskBins(InputWorkspace="Vanadium",OutputWorkspace="Vanadium",XMin="39970",XMax="40140")
-        MaskBins(InputWorkspace="Vanadium",OutputWorkspace="Vanadium",XMin="59970",XMax="60140")
-        MaskBins(InputWorkspace="Vanadium",OutputWorkspace="Vanadium",XMin="79970",XMax="80140")
-        MaskBins(InputWorkspace="Vanadium",OutputWorkspace="Vanadium",XMin="99970",XMax="100140")
-        #align vanadium detectors
-        AlignDetectors(InputWorkspace="Vanadium",OutputWorkspace="Vanadium",CalibrationFile="hrpd_new_072_01_corr.cal")
-        #normalise by current
-        NormaliseByCurrent(InputWorkspace="Vanadium",OutputWorkspace="Vanadium")
-        #correct by accumulated correction - solid angle/(1000*flux(1.4 - 3 Angstrom))
-        Divide(LHSWorkspace="Vanadium",RHSWorkspace="Corr",OutputWorkspace="Vanadium")
-        #Load the vanadium empty
-        LoadRaw(Filename="HRP39187.RAW",OutputWorkspace="VEmpty")
-        #mask out the vanadium peaks
-        MaskBins(InputWorkspace="VEmpty",OutputWorkspace="VEmpty",XMin="19970",XMax="20140")
-        MaskBins(InputWorkspace="VEmpty",OutputWorkspace="VEmpty",XMin="39970",XMax="40140")
-        MaskBins(InputWorkspace="VEmpty",OutputWorkspace="VEmpty",XMin="59970",XMax="60140")
-        MaskBins(InputWorkspace="VEmpty",OutputWorkspace="VEmpty",XMin="79970",XMax="80140")
-        MaskBins(InputWorkspace="VEmpty",OutputWorkspace="VEmpty",XMin="99970",XMax="100140")
-        #align vanadium empty detectors
-        AlignDetectors(InputWorkspace="VEmpty",OutputWorkspace="VEmpty",CalibrationFile="hrpd_new_072_01_corr.cal")
-        #correct by accumulated correction - solid angle/(1000*flux(1.4 - 3 Angstrom))
-        Divide(LHSWorkspace="VEmpty",RHSWorkspace="Corr",OutputWorkspace="VEmpty")
-        #normalise by current
-        NormaliseByCurrent(InputWorkspace="VEmpty",OutputWorkspace="VEmpty")
-        #Subtract Vanadium empty from the Vanadium
-        Minus(LHSWorkspace="Vanadium",RHSWorkspace="VEmpty",OutputWorkspace="Vanadium")
-        #Convert to wavelength
-        ConvertUnits(InputWorkspace="Vanadium",OutputWorkspace="Vanadium",Target="Wavelength")
-        #Correct for cylinderAbsorption
-        CylinderAbsorption(InputWorkspace="Vanadium",OutputWorkspace="Transmission",CylinderSampleHeight="2",
-                           CylinderSampleRadius="0.4",AttenuationXSection="5.1",ScatteringXSection="5.08",
-                           SampleNumberDensity="0.072",NumberOfSlices="10",NumberOfAnnuli="10",NumberOfWavelengthPoints="100")
-        Divide(LHSWorkspace="Vanadium",RHSWorkspace="Transmission",OutputWorkspace="Vanadium")
-        #convert to dspacing and focuss
-        ConvertUnits(InputWorkspace="Vanadium",OutputWorkspace="Vanadium",Target="dSpacing")
-        DiffractionFocussing(InputWorkspace="Vanadium",OutputWorkspace="Vanadium",GroupingFileName="hrpd_new_072_01_corr.cal")
-
-    def validate(self):
-        # Fitting parameters not saved to ParameterMap
-        self.disableChecking.append("Instrument")
-        return 'Vanadium','HRPDPowderDiffraction.nxs'
diff --git a/Testing/SystemTests/tests/analysis/ISIS_PowderGemTest.py b/Testing/SystemTests/tests/analysis/ISIS_PowderGemTest.py
index d00a395414fd176fb161076bb73eab4a51c3c687..124c3e687451c32722a2a8e7ffdbb3bfaf32ba4f 100644
--- a/Testing/SystemTests/tests/analysis/ISIS_PowderGemTest.py
+++ b/Testing/SystemTests/tests/analysis/ISIS_PowderGemTest.py
@@ -20,6 +20,7 @@ DIRS = config['datasearch.directories'].split(';')
 # Setup various path details
 
 inst_name = "GEM"
+user_name = "Test"
 # Relative to system data folder
 working_folder_name = "ISIS_Powder"
 
@@ -30,7 +31,8 @@ output_folder_name = "output"
 # Relative to input folder
 calibration_folder_name = os.path.join("calibration", inst_name.lower())
 calibration_map_rel_path = os.path.join("yaml_files", "gem_system_test_mapping.yaml")
-spline_rel_path = os.path.join("17_1", "VanSplined_83608_offsets_2011_cycle111b.cal.nxs")
+cycle = "17_1"
+spline_rel_path = os.path.join(cycle, "VanSplined_83608_offsets_2011_cycle111b.cal.nxs")
 
 # Generate paths for the tests
 # This implies DIRS[0] is the system test data folder
@@ -96,6 +98,23 @@ class FocusTestNoAbsCorr(FocusTestMixin, systemtesting.MantidSystemTest):
         self.doTest(absorb_corrections=False)
 
     def validate(self):
+        # check output files as expected
+        def generate_error_message(expected_file, output_dir):
+            return "Unable to find {} in {}.\nContents={}".format(expected_file, output_dir,
+                                                                  os.listdir(output_dir))
+
+        def assert_output_file_exists(directory, filename):
+            self.assertTrue(os.path.isfile(os.path.join(directory, filename)),
+                            msg=generate_error_message(filename, directory))
+
+        user_output = os.path.join(output_dir, cycle, user_name)
+        assert_output_file_exists(user_output, 'GEM83605.nxs')
+        assert_output_file_exists(user_output, 'GEM83605.gsas')
+        output_dat_dir = os.path.join(user_output, 'dat_files')
+        for bankno in range(1, 7):
+            assert_output_file_exists(output_dat_dir, 'GEM83605-b_{}-TOF.dat'.format(bankno))
+            assert_output_file_exists(output_dat_dir, 'GEM83605-b_{}-d.dat'.format(bankno))
+
         return self.focus_results.name(), "ISIS_Powder-GEM83605_FocusSempty.nxs"
 
 
@@ -198,8 +217,6 @@ def setup_mantid_paths():
 
 
 def setup_inst_object(mode):
-    user_name = "Test"
-
     inst_obj = Gem(user_name=user_name, calibration_mapping_file=calibration_map_path,
                    calibration_directory=calibration_dir, output_directory=output_dir, mode=mode)
     return inst_obj
diff --git a/Testing/SystemTests/tests/analysis/ISIS_PowderGeneric.py b/Testing/SystemTests/tests/analysis/ISIS_PowderGeneric.py
deleted file mode 100644
index d9ffef0fb39ab84eeb012250279a46727970163c..0000000000000000000000000000000000000000
--- a/Testing/SystemTests/tests/analysis/ISIS_PowderGeneric.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# 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
-# SPDX - License - Identifier: GPL - 3.0 +
-from __future__ import (absolute_import, division, print_function)
-import systemtesting
-from mantid.simpleapi import *
-
-
-class PEARLPowderDiffraction(systemtesting.MantidSystemTest):
-
-    sample = "PEARL00073987.raw"
-    calfile = "pearl_offset_11_4.cal"
-    groupfile = "pearl_group_11_2_TT88.cal"
-    reffile = "PEARLPowderDiffraction.nxs"
-
-    def requiredFiles(self):
-        return [self.sample, self.calfile, self.groupfile, self.reffile]
-
-    def runTest(self):
-        LoadRaw(Filename=self.sample, OutputWorkspace='work',LoadLogFiles='0')
-        ConvertUnits(InputWorkspace='work',OutputWorkspace='work',Target='Wavelength')
-
-        LoadRaw(Filename=self.sample, OutputWorkspace='monitor73987',LoadLogFiles='0',SpectrumMax='1')
-        ConvertUnits(InputWorkspace='monitor73987',OutputWorkspace='monitor73987',Target='Wavelength')
-        CropWorkspace(InputWorkspace='monitor73987',OutputWorkspace='monitor73987',
-                      XMin=0.03,XMax=6.0)
-
-        MaskBins(InputWorkspace='monitor73987',OutputWorkspace='monitor73987',XMin=3.45,XMax=3.7)
-        MaskBins(InputWorkspace='monitor73987',OutputWorkspace='monitor73987',XMin=2.96,XMax=3.2)
-        MaskBins(InputWorkspace='monitor73987',OutputWorkspace='monitor73987',XMin=2.1,XMax=2.26)
-        MaskBins(InputWorkspace='monitor73987',OutputWorkspace='monitor73987',XMin=1.73,XMax=1.98)
-
-        SplineBackground(InputWorkspace='monitor73987',OutputWorkspace='monitor73987',NCoeff=20)
-        NormaliseToMonitor(InputWorkspace='work',OutputWorkspace='work',MonitorWorkspace='monitor73987',
-                           IntegrationRangeMin=0.6,IntegrationRangeMax=5.0)
-        ConvertUnits(InputWorkspace='work',OutputWorkspace='work',Target='TOF')
-
-        rb_params = [1500,-0.0006,19900]
-        Rebin(InputWorkspace='work',OutputWorkspace='work',Params=rb_params)
-        AlignDetectors(InputWorkspace='work',OutputWorkspace='work', CalibrationFile=self.calfile)
-        DiffractionFocussing(InputWorkspace='work',OutputWorkspace='focus',
-                             GroupingFileName=self.groupfile)
-
-        ConvertUnits(InputWorkspace='focus',OutputWorkspace='focus',Target='TOF')
-        Rebin(InputWorkspace='focus',OutputWorkspace='focus',Params=rb_params)
-        CropWorkspace(InputWorkspace='focus',OutputWorkspace='focus',XMin=0.1)
-
-    def validate(self):
-        return 'focus','PEARLPowderDiffraction.nxs'
diff --git a/Testing/SystemTests/tests/analysis/ISIS_PowderHRPDTest.py b/Testing/SystemTests/tests/analysis/ISIS_PowderHRPDTest.py
index 95deedc85c597830c5b97f28d548a2ba43287cac..694a7651f589996cb5a2502d7c739f4c291d2637 100644
--- a/Testing/SystemTests/tests/analysis/ISIS_PowderHRPDTest.py
+++ b/Testing/SystemTests/tests/analysis/ISIS_PowderHRPDTest.py
@@ -61,7 +61,8 @@ class CreateVanadiumTest(systemtesting.MantidSystemTest):
 
     def validate(self):
         self.tolerance = 0.05  # Required for difference in spline data between operating systems
-        return self.calibration_results.name(), "ISIS_Powder-HRPD-VanSplined_66031_hrpd_new_072_01_corr.cal.nxs"
+        return self.calibration_results.name(
+        ), "ISIS_Powder-HRPD-VanSplined_66031_hrpd_new_072_01_corr.cal.nxs"
 
     def cleanup(self):
         try:
@@ -86,10 +87,27 @@ class FocusTest(systemtesting.MantidSystemTest):
         self.focus_results = run_focus()
 
     def validate(self):
+        # check output files as expected
+        def generate_error_message(expected_file, output_dir):
+            return "Unable to find {} in {}.\nContents={}".format(expected_file, output_dir,
+                                                                  os.listdir(output_dir))
+
+        def assert_output_file_exists(directory, filename):
+            self.assertTrue(os.path.isfile(os.path.join(directory, filename)),
+                            msg=generate_error_message(filename, directory))
+
+        user_output = os.path.join(output_dir, cycle_number, user_name)
+        assert_output_file_exists(user_output, 'hrpd66063.nxs')
+        assert_output_file_exists(user_output, 'hrpd66063.gss')
+        output_dat_dir = os.path.join(user_output, 'dat_files')
+        for bankno in range(1, 4):
+            assert_output_file_exists(output_dat_dir, 'hrpd66063_b{}_D.dat'.format(bankno))
+            assert_output_file_exists(output_dat_dir, 'hrpd66063_b{}_TOF.dat'.format(bankno))
+
         if platform.system() == "Darwin":  # OSX requires higher tolerance for splines
             self.tolerance = 0.4
         else:
-            self.tolerance = 0.05
+            self.tolerance = 0.16
         return self.focus_results.name(), "HRPD66063_focused.nxs"
 
     def cleanup(self):
@@ -103,15 +121,19 @@ class FocusTest(systemtesting.MantidSystemTest):
 
 def _gen_required_files():
     required_run_numbers = gen_required_run_numbers()
-    input_files = [os.path.join(input_dir, (inst_name + number + ".raw")) for number in required_run_numbers]
+    input_files = [
+        os.path.join(input_dir, (inst_name + number + ".raw")) for number in required_run_numbers
+    ]
     input_files.append(calibration_map_path)
     return input_files
 
 
 def gen_required_run_numbers():
-    return ["66028",  # Sample empty
-            "66031",  # Vanadium
-            "66063"]  # Run to focus
+    return [
+        "66028",  # Sample empty
+        "66031",  # Vanadium
+        "66063"
+    ]  # Run to focus
 
 
 def run_vanadium_calibration():
@@ -124,7 +146,8 @@ def run_vanadium_calibration():
 
     # Check the spline looks good and was saved
     if not os.path.exists(spline_path):
-        raise RuntimeError("Could not find output spline at the following path: {}".format(spline_path))
+        raise RuntimeError(
+            "Could not find output spline at the following path: {}".format(spline_path))
     splined_ws = mantid.Load(Filename=spline_path)
 
     return splined_ws
@@ -146,14 +169,20 @@ def run_focus():
     sample.set_material(chemical_formula="Si")
     inst_object.set_sample_details(sample=sample)
 
-    return inst_object.focus(run_number=run_number, window="10-110", sample_empty=sample_empty,
-                             sample_empty_scale=sample_empty_scale, vanadium_normalisation=True,
-                             do_absorb_corrections=True, multiple_scattering=False)
+    return inst_object.focus(run_number=run_number,
+                             window="10-110",
+                             sample_empty=sample_empty,
+                             sample_empty_scale=sample_empty_scale,
+                             vanadium_normalisation=True,
+                             do_absorb_corrections=True,
+                             multiple_scattering=False)
 
 
 def setup_inst_object():
-    inst_obj = HRPD(user_name=user_name, calibration_mapping_file=calibration_map_path,
-                    calibration_directory=calibration_dir, output_directory=output_dir)
+    inst_obj = HRPD(user_name=user_name,
+                    calibration_mapping_file=calibration_map_path,
+                    calibration_directory=calibration_dir,
+                    output_directory=output_dir)
     return inst_obj
 
 
diff --git a/Testing/SystemTests/tests/analysis/ISIS_PowderPearlTest.py b/Testing/SystemTests/tests/analysis/ISIS_PowderPearlTest.py
index 6bf3df4814ef84210cf0636b9b4d03d152a0b7ca..fe1c51574c038cd4f76c2edc23e46d38b0a9bcec 100644
--- a/Testing/SystemTests/tests/analysis/ISIS_PowderPearlTest.py
+++ b/Testing/SystemTests/tests/analysis/ISIS_PowderPearlTest.py
@@ -20,6 +20,7 @@ DIRS = config['datasearch.directories'].split(';')
 # Setup various path details
 
 inst_name = "PEARL"
+user_name = "Test"
 # Relative to system data folder
 working_folder_name = "ISIS_Powder"
 
@@ -30,7 +31,8 @@ output_folder_name = "output"
 # Relative to input folder
 calibration_folder_name = os.path.join("calibration", inst_name.lower())
 calibration_map_rel_path = os.path.join("yaml_files", "pearl_system_test_mapping.yaml")
-spline_rel_path = os.path.join("17_1", "VanSplined_98472_tt70_pearl_offset_16_4.cal.nxs")
+cycle = "17_1"
+spline_rel_path = os.path.join(cycle, "VanSplined_98472_tt70_pearl_offset_16_4.cal.nxs")
 
 # Generate paths for the tests
 # This implies DIRS[0] is the system test data folder
@@ -124,6 +126,21 @@ class FocusTest(systemtesting.MantidSystemTest):
         self.assertEqual(inst_object._inst_settings.tt_mode, "tt88")
 
     def validate(self):
+        # check output files as expected
+        def generate_error_message(expected_file, output_dir):
+            return "Unable to find {} in {}\nContents={}".format(expected_file, output_dir,
+                                                                 os.listdir(output_dir))
+
+        def assert_output_file_exists(directory, filename):
+            self.assertTrue(os.path.isfile(os.path.join(directory, filename)),
+                            msg=generate_error_message(filename, directory))
+
+        user_output = os.path.join(output_dir, cycle, user_name)
+        assert_output_file_exists(user_output, 'PRL98507_tt70.nxs')
+        assert_output_file_exists(user_output, 'PRL98507_tt70.gsas')
+        assert_output_file_exists(user_output, 'PRL98507_tt70_tof_xye-0.dat')
+        assert_output_file_exists(user_output, 'PRL98507_tt70_d_xye-0.dat')
+
         self.tolerance = 5e-9  # Required for difference in spline data between operating systems
         return "PEARL98507_tt70-Results-D-Grp", "ISIS_Powder-PEARL00098507_tt70Atten.nxs"
 
@@ -157,6 +174,25 @@ class FocusLongThenShortTest(systemtesting.MantidSystemTest):
         self.assertFalse(inst_object._inst_settings.long_mode)
 
     def validate(self):
+        # check output files as expected
+        def generate_error_message(expected_file, output_dir):
+            return "Unable to find {} in {}.\nContents={}".format(expected_file, output_dir,
+                                                                  os.listdir(output_dir))
+
+        def assert_output_file_exists(directory, filename):
+            self.assertTrue(os.path.isfile(os.path.join(directory, filename)),
+                            msg=generate_error_message(filename, directory))
+
+        user_output = os.path.join(output_dir, cycle, user_name)
+        assert_output_file_exists(user_output, 'PRL98507_tt70.nxs')
+        assert_output_file_exists(user_output, 'PRL98507_tt70_long.nxs')
+        assert_output_file_exists(user_output, 'PRL98507_tt70.gsas')
+        assert_output_file_exists(user_output, 'PRL98507_tt70_long.gsas')
+        assert_output_file_exists(user_output, 'PRL98507_tt70_tof_xye-0.dat')
+        assert_output_file_exists(user_output, 'PRL98507_tt70_d_xye-0.dat')
+        assert_output_file_exists(user_output, 'PRL98507_tt70_long_tof_xye-0.dat')
+        assert_output_file_exists(user_output, 'PRL98507_tt70_long_d_xye-0.dat')
+
         self.tolerance = 5e-9  # Required for difference in spline data between operating systems
         return "PEARL98507_tt70-Results-D-Grp", "ISIS_Powder-PEARL00098507_tt70Atten.nxs"
 
@@ -272,8 +308,6 @@ def setup_mantid_paths():
 
 
 def setup_inst_object(tt_mode, focus_mode):
-    user_name = "Test"
-
     inst_obj = Pearl(user_name=user_name, calibration_mapping_file=calibration_map_path, long_mode=False,
                      calibration_directory=calibration_dir, output_directory=output_dir, tt_mode=tt_mode,
                      focus_mode=focus_mode)
diff --git a/Testing/SystemTests/tests/analysis/ISIS_PowderPolarisTest.py b/Testing/SystemTests/tests/analysis/ISIS_PowderPolarisTest.py
index d35f939a503d93db42731fe5a8c9933371d97a30..6c8fbb38720949adf8eff9060d63dfc6802b45ba 100644
--- a/Testing/SystemTests/tests/analysis/ISIS_PowderPolarisTest.py
+++ b/Testing/SystemTests/tests/analysis/ISIS_PowderPolarisTest.py
@@ -90,6 +90,23 @@ class FocusTest(systemtesting.MantidSystemTest):
         self.focus_results = run_focus()
 
     def validate(self):
+        # check output files as expected
+        def generate_error_message(expected_file, output_dir):
+            return "Unable to find {} in {}.\nContents={}".format(expected_file, output_dir,
+                                                                  os.listdir(output_dir))
+
+        def assert_output_file_exists(directory, filename):
+            self.assertTrue(os.path.isfile(os.path.join(directory, filename)),
+                            msg=generate_error_message(filename, directory))
+
+        user_output = os.path.join(output_dir, "17_1", "Test")
+        assert_output_file_exists(user_output, 'POLARIS98533.nxs')
+        assert_output_file_exists(user_output, 'POLARIS98533.gsas')
+        output_dat_dir = os.path.join(user_output, 'dat_files')
+        for bankno in range(1, 6):
+            assert_output_file_exists(output_dat_dir, 'POL98533-b_{}-TOF.dat'.format(bankno))
+            assert_output_file_exists(output_dat_dir, 'POL98533-b_{}-d.dat'.format(bankno))
+
         for ws in self.focus_results:
             self.assertEqual(ws.sample().getMaterial().name(), 'Si')
         self.tolerance = 1e-7
diff --git a/Testing/SystemTests/tests/analysis/reference/HRPD66063_focused.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/HRPD66063_focused.nxs.md5
index 050a768a3f9b78deb7a4c15cc5fba991e8340d07..63e50212573994a80cb4619fc4e7f634e3a83bd1 100644
--- a/Testing/SystemTests/tests/analysis/reference/HRPD66063_focused.nxs.md5
+++ b/Testing/SystemTests/tests/analysis/reference/HRPD66063_focused.nxs.md5
@@ -1 +1 @@
-fac289306a7a7d5807c4454af82eea72
+bad2167c5e22f7f6f971a5a4908f387c
diff --git a/Testing/SystemTests/tests/analysis/reference/HRPDPowderDiffraction.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/HRPDPowderDiffraction.nxs.md5
deleted file mode 100644
index 08b6996a1f08dd715fcb73986162290d7b5ea2a3..0000000000000000000000000000000000000000
--- a/Testing/SystemTests/tests/analysis/reference/HRPDPowderDiffraction.nxs.md5
+++ /dev/null
@@ -1 +0,0 @@
-ce1622243acd564e16ca69ab64b2929d
\ No newline at end of file
diff --git a/Testing/SystemTests/tests/analysis/reference/ILLIN16B_BATS.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/ILLIN16B_BATS.nxs.md5
index 5f9102ceef923ae58e80e7c83f92bd027f071f9b..718356cf226621f1d0840e96ac3f5f9e8d1d6666 100644
--- a/Testing/SystemTests/tests/analysis/reference/ILLIN16B_BATS.nxs.md5
+++ b/Testing/SystemTests/tests/analysis/reference/ILLIN16B_BATS.nxs.md5
@@ -1 +1 @@
-39146f691eee5299b8bfd42f07e8cd36
+37a012a21d704bfcee5939cdb4079f70
diff --git a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098472_splined.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098472_splined.nxs.md5
index 558a0594ee5876e22d38a9e3626e2a56d94351f4..e1aa8ebeb44aaac722a8689dd7e70eadf21e9866 100644
--- a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098472_splined.nxs.md5
+++ b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098472_splined.nxs.md5
@@ -1 +1 @@
-6ccffe477944b2140ea9613c16c164d7
+a0adaa334c342cbc836a135616e168d4
diff --git a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5
index 9af4de456994f9aed774961da21f26970b8b8d99..4a31d93fadb8f07f6b4f083065e33e1cd184143f 100644
--- a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5
+++ b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5
@@ -1 +1 @@
-030de817ea2616f5293ccde51cbb977b
+242555ded7a3baeb291727c784370b96
diff --git a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5 b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5
index 9e49c7bf63c35520bf8b49069e2a7de04467f859..b25c15b20cabf7b87fab50b32351955ee9e1b0a2 100644
--- a/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5
+++ b/Testing/SystemTests/tests/analysis/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5
@@ -1 +1 @@
-4632e968794625ae790cd5abff74da93
+a8786906c6a30ddb73c9a2633b414e3f
diff --git a/buildconfig/CMake/PythonPackageTargetFunctions.cmake b/buildconfig/CMake/PythonPackageTargetFunctions.cmake
index 4cb5c4f647118388af86df583694b39cdd413e09..bfd96bd3ea15c56d46bdf52492c1a767319e0271 100644
--- a/buildconfig/CMake/PythonPackageTargetFunctions.cmake
+++ b/buildconfig/CMake/PythonPackageTargetFunctions.cmake
@@ -1,5 +1,6 @@
 # Defines functions to help deal with python packages
 
+# ~~~
 # Function to create links to python packages in the source tree
 # Optional keyword arguments:
 #   - EXECUTABLE: If this option provided then it is assumed the package contains a
@@ -7,16 +8,23 @@
 #                 directory
 #   - EGGLINKNAME: Pass in a new name for the egg link, e.g. EGGLINKNAME mylink,
 #                  creates a new egg link called mylink
+#   - INSTALL_LIB_DIRS: A list of install directories.
+#   - INSTALL_BIN_DIR: Destination for an executable to be installed
 #   - EXCLUDE_ON_INSTALL: Specifies a regex of files to exclude from the install
 #   -                     command
-function ( add_python_package pkg_name )
+# ~~~
+function(
+  add_python_package
+  pkg_name
+)
   # Create a setup.py file if necessary
-  set ( _setup_py ${CMAKE_CURRENT_SOURCE_DIR}/setup.py )
-  set ( _setup_py_build_root ${CMAKE_CURRENT_BINARY_DIR} )
+  set(_setup_py ${CMAKE_CURRENT_SOURCE_DIR}/setup.py)
+  set(_setup_py_build_root ${CMAKE_CURRENT_BINARY_DIR})
 
-  if ( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in" )
-    set ( SETUPTOOLS_BUILD_COMMANDS_DEF
-"def patch_setuptools_command(cmd_cls_name):
+  if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in")
+    set(
+      SETUPTOOLS_BUILD_COMMANDS_DEF
+      "def patch_setuptools_command(cmd_cls_name):
     import importlib
     cmd_module = importlib.import_module('setuptools.command.' + cmd_cls_name)
     setuptools_command_cls = getattr(cmd_module, cmd_cls_name)
@@ -34,106 +42,150 @@ function ( add_python_package pkg_name )
 CustomBuildPy = patch_setuptools_command('build_py')
 CustomInstall = patch_setuptools_command('install')
 CustomInstallLib = patch_setuptools_command('install_lib')
-" )
-    set ( SETUPTOOLS_BUILD_COMMANDS_USE "cmdclass={'build_py': CustomBuildPy, 'install': CustomInstall, 'install-lib': CustomInstallLib }" )
-    configure_file ( ${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${_setup_py} @ONLY )
-  endif ()
+"
+    )
+    set(
+      SETUPTOOLS_BUILD_COMMANDS_USE
+      "cmdclass={'build_py': CustomBuildPy, 'install': CustomInstall, 'install-lib': CustomInstallLib }"
+    )
+    configure_file(
+      ${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in
+      ${_setup_py}
+      @ONLY
+    )
+  endif()
 
-  set ( _egg_link_dir ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR} )
+  set(_egg_link_dir ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR})
 
   # Create variables for additional arguments
-  cmake_parse_arguments(_parsed_arg
-                        "EXECUTABLE"
-                        "EGGLINKNAME;EXCLUDE_FROM_INSTALL"
-                        "" ${ARGN})
+  cmake_parse_arguments(
+    _parsed_arg
+    "EXECUTABLE"
+    "EGGLINKNAME;EXCLUDE_FROM_INSTALL;INSTALL_BIN_DIR"
+    "INSTALL_LIB_DIRS"
+    ${ARGN}
+  )
 
   # If a custom egg-link name was specified use that for the link
-  if (_parsed_arg_EGGLINKNAME)
-    set ( _egg_link ${_egg_link_dir}/${_parsed_arg_EGGLINKNAME}.egg-link )
+  if(_parsed_arg_EGGLINKNAME)
+    set(_egg_link ${_egg_link_dir}/${_parsed_arg_EGGLINKNAME}.egg-link)
   else()
     # if no egg-link name is specified, then use the target name
-    set ( _egg_link ${_egg_link_dir}/${pkg_name}.egg-link )
+    set(_egg_link ${_egg_link_dir}/${pkg_name}.egg-link)
   endif()
 
-  if ( _parsed_arg_EXECUTABLE )
-    if ( WIN32 )
-      # add .exe in the executable name for Windows, otherwise it can't find it during the install step
-      set ( _executable_name ${pkg_name}.exe )
-      set ( _startup_script_full_name ${pkg_name}-script.pyw )
-      set ( _startup_script ${_egg_link_dir}/${_startup_script_full_name} )
-    else ()
-      set ( _startup_script_full_name )
-      set ( _startup_script )
-      set ( _executable_name ${pkg_name} )
-    endif ()
-    set ( _startup_exe ${_egg_link_dir}/${_executable_name} )
-  endif ()
+  if(_parsed_arg_EXECUTABLE)
+    if(WIN32)
+      # add .exe in the executable name for Windows, otherwise it can't find it
+      # during the install step
+      set(_executable_name ${pkg_name}.exe)
+      set(_startup_script_full_name ${pkg_name}-script.pyw)
+      set(_startup_script ${_egg_link_dir}/${_startup_script_full_name})
+    else()
+      set(_startup_script_full_name)
+      set(_startup_script)
+      set(_executable_name ${pkg_name})
+    endif()
+    set(_startup_exe ${_egg_link_dir}/${_executable_name})
+  endif()
 
-  # create the developer setup which just creates a pth file rather than copying things over
-  set ( _outputs ${_egg_link} ${_startup_script} ${_startup_exe} )
-  add_custom_command ( OUTPUT ${_outputs}
-    COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${_egg_link_dir}
-      ${PYTHON_EXECUTABLE} ${_setup_py} develop
-      --install-dir ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}
-      --script-dir ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}
+  # create the developer setup which just creates a pth file rather than copying
+  # things over
+  set(
+    _outputs
+    ${_egg_link}
+    ${_startup_script}
+    ${_startup_exe}
+  )
+  add_custom_command(
+    OUTPUT ${_outputs}
+    COMMAND
+      ${CMAKE_COMMAND}
+      -E
+      env
+      PYTHONPATH=${_egg_link_dir}
+      ${PYTHON_EXECUTABLE}
+      ${_setup_py}
+      develop
+      --install-dir
+      ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}
+      --script-dir
+      ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}
     WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
     DEPENDS ${_setup_py}
   )
-  add_custom_target ( ${pkg_name} ALL
+  add_custom_target(
+    ${pkg_name}
+    ALL
     DEPENDS ${_outputs}
   )
 
-  if ( ${ENABLE_WORKBENCH} )
-    # setuptools by default wants to build into a directory called 'build' relative the to the working directory. We have overridden
-    # commands in setup.py.in to force the build directory to take place out of source. The install directory is specified here and then
-    # --install-scripts=bin --install-lib=lib removes any of the platform/distribution specific install directories so we can have a flat
-    # structure
-    install(CODE "execute_process(COMMAND ${PYTHON_EXECUTABLE} ${_setup_py} install -O1 --single-version-externally-managed --root=${_setup_py_build_root}/install --install-scripts=bin --install-lib=lib WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})")
+  # setuptools by default wants to build into a directory called 'build'
+  # relative the to the working directory. We have overridden commands in
+  # setup.py.in to force the build directory to take place out of source. The
+  # install directory is specified here and then --install-scripts=bin
+  # --install-lib=lib removes any of the platform/distribution specific install
+  # directories so we can have a flat structure
+  install(
+    CODE
+      "execute_process(COMMAND ${PYTHON_EXECUTABLE} ${_setup_py} install -O1 --single-version-externally-managed --root=${_setup_py_build_root}/install --install-scripts=bin --install-lib=lib WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})"
+  )
 
-    # Specify the installation directory based on OS
-    if ( WIN32 OR APPLE)
-      # The / after lib tells cmake to copy over the _CONTENTS_ of the lib directory
-      # placing the installed files inside the DESTINATION folder. This copies the
-      # installed Python package inside the bin directory of Mantid's installation
-      set ( _package_source_directory ${_setup_py_build_root}/install/lib/ )
-      set ( _package_install_destination ${WORKBENCH_BIN_DIR} )
-    else ()
-      # NOTE the lack of slash at the end - this means the _whole_ lib directory will be moved
-      set ( _package_source_directory ${_setup_py_build_root}/install/lib )
-      set ( _package_install_destination . )
-    endif ()
+  # Registers the "installed" components with CMake so it will carry them over
+  if(_parsed_arg_EXCLUDE_FROM_INSTALL)
+    foreach(
+      _dest
+      ${_parsed_arg_INSTALL_LIB_DIRS}
+    )
+      install(
+        DIRECTORY ${_setup_py_build_root}/install/lib/
+        DESTINATION ${_dest}
+        PATTERN
+          "test"
+          EXCLUDE
+        REGEX
+          "${_parsed_arg_EXCLUDE_FROM_INSTALL}"
+          EXCLUDE
+      )
+    endforeach()
+  else()
+    foreach(
+      _dest
+      ${_parsed_arg_INSTALL_LIB_DIRS}
+    )
+      install(
+        DIRECTORY ${_setup_py_build_root}/install/lib/
+        DESTINATION ${_dest}
+        PATTERN
+          "test"
+          EXCLUDE
+      )
+    endforeach()
+  endif()
 
-    # Registers the "installed" components with CMake so it will carry them over
-    if ( _parsed_arg_EXCLUDE_FROM_INSTALL )
-      install(DIRECTORY ${_package_source_directory}
-              DESTINATION ${_package_install_destination}
-              PATTERN "test" EXCLUDE
-              REGEX "${_parsed_arg_EXCLUDE_FROM_INSTALL}" EXCLUDE)
-    else ()
-      install(DIRECTORY ${_package_source_directory}
-              DESTINATION ${_package_install_destination}
-              PATTERN "test" EXCLUDE)
-    endif()
+  if(APPLE AND "${pkg_name}" STREQUAL "mantidqt")
+    # Horrible hack to get mantidqt into the MantidPlot.app bundle too. Remove
+    # this when MantidPlot is removed!! Registers the "installed" components
+    # with CMake so it will carry them over
+    install(
+      DIRECTORY ${_setup_py_build_root}/install/lib/
+      DESTINATION ${BIN_DIR}
+      PATTERN
+        "test"
+        EXCLUDE
+    )
+  endif()
 
-    if (APPLE AND "${pkg_name}" STREQUAL "mantidqt")
-      # Horrible hack to get mantidqt into the MantidPlot.app bundle too.
-      # Remove this when MantidPlot is removed!!
-      set ( _package_install_destination ${BIN_DIR} )
-      # Registers the "installed" components with CMake so it will carry them over
-      install(DIRECTORY ${_package_source_directory}
-              DESTINATION ${_package_install_destination}
-              PATTERN "test" EXCLUDE )
+  # install the generated executable - only tested with "workbench"
+  if(_parsed_arg_EXECUTABLE)
+    # On UNIX systems install the workbench executable directly. The Windows
+    # case is handled with a custom startup script installed in WindowsNSIS
+    if(NOT WIN32)
+      install(
+        PROGRAMS ${_setup_py_build_root}/install/bin/${pkg_name}
+        DESTINATION ${_parsed_arg_INSTALL_BIN_DIR}
+        RENAME workbench-script
+      )
     endif()
-
-    # install the generated executable - only tested with "workbench"
-    if ( _parsed_arg_EXECUTABLE )
-        # On UNIX systems install the workbench executable directly.
-        # The Windows case is handled with a custom startup script installed in WindowsNSIS
-        if ( NOT WIN32 )
-            install(PROGRAMS ${_setup_py_build_root}/install/bin/${pkg_name}
-                DESTINATION ${WORKBENCH_BIN_DIR}
-                RENAME workbench-script)
-        endif()
   endif()
-endif()
-endfunction ()
+endfunction()
diff --git a/buildconfig/CMake/SipQtTargetFunctions.cmake b/buildconfig/CMake/SipQtTargetFunctions.cmake
index 54cdd3bb6478b39f47d12bcb02d9cd6c8bae376b..e0fae82900f73235c5bb0c693d3513298dcb82c3 100644
--- a/buildconfig/CMake/SipQtTargetFunctions.cmake
+++ b/buildconfig/CMake/SipQtTargetFunctions.cmake
@@ -66,11 +66,12 @@ function ( mtd_add_sip_module )
   set ( _module_spec ${CMAKE_CURRENT_BINARY_DIR}/${PARSED_MODULE_NAME}.sip )
   configure_file ( ${_sipmodule_template_path} ${_module_spec} )
   set ( _pyqt_sip_dir ${PYQT${PARSED_PYQT_VERSION}_SIP_DIR} )
+  set ( _sip_wrapper ${CMAKE_SOURCE_DIR}/tools/sip/sipwrapper.py)
   list ( APPEND _sip_include_flags "-I${_pyqt_sip_dir}" )
   set ( _pyqt_sip_flags ${PYQT${PARSED_PYQT_VERSION}_SIP_FLAGS} )
   set ( _sip_generated_cpp ${CMAKE_CURRENT_BINARY_DIR}/sip${PARSED_MODULE_NAME}part0.cpp )
   add_custom_command ( OUTPUT ${_sip_generated_cpp}
-    COMMAND ${SIP_EXECUTABLE}
+    COMMAND ${PYTHON_EXECUTABLE} ${_sip_wrapper} ${SIP_EXECUTABLE}
       ${_sip_include_flags} ${_pyqt_sip_flags}
       -c ${CMAKE_CURRENT_BINARY_DIR} -j1 -w -e ${_module_spec}
     DEPENDS ${_module_spec} ${_sip_include_deps} ${SIP_INCLUDE_DIR}/sip.h
diff --git a/buildconfig/dev-packages/rpm/mantid-developer/mantid-developer.spec b/buildconfig/dev-packages/rpm/mantid-developer/mantid-developer.spec
index 06aed0a8464dbcad6fdddf60499d934c945e7685..1d38f40976f3c4b91ab0cf0415f511eca51a3baa 100644
--- a/buildconfig/dev-packages/rpm/mantid-developer/mantid-developer.spec
+++ b/buildconfig/dev-packages/rpm/mantid-developer/mantid-developer.spec
@@ -110,7 +110,7 @@ Requires: python3-mock
 Requires: boost-python3-devel
 %endif
 
-%if 0%{el7}
+%if 0%{?el7}
 Requires: boost-python36-devel
 Requires: python36-devel
 Requires: python36-h5py
diff --git a/docs/source/algorithms/Fit-v1.rst b/docs/source/algorithms/Fit-v1.rst
index 41e829c42b0d90c029af803f783915bfe65b4a62..1690a28f751f3bd594d674ffc544ae317f3871c7 100644
--- a/docs/source/algorithms/Fit-v1.rst
+++ b/docs/source/algorithms/Fit-v1.rst
@@ -9,7 +9,7 @@
 Description
 -----------
 
-Additional properties for a 1D function and a MatrixWorkspace
+Additional properties for a 1D function
 #############################################################
 
 If Function defines a one-dimensional function and InputWorkspace is a
@@ -26,16 +26,35 @@ additional properties:
 | EndX             | Input       | double    | End of the spectrum     | An X value in the last bin to be included in the fit                |
 +------------------+-------------+-----------+-------------------------+---------------------------------------------------------------------+
 
+If Function defines a one-dimensional function and InputWorkspace is a
+:ref:`Table Workspace <Table Workspaces>` the algorithm will have these
+additional properties:
+
++------------------+-------------+-----------+-------------------------+---------------------------------------------------------+
+| Name             | Direction   | Type      | Default                 | Description                                             |
++==================+=============+===========+=========================+=========================================================+
+| StartX           | Input       | double    | Start of the spectrum   | An X value in the first bin to be included in the fit   |
++------------------+-------------+-----------+-------------------------+---------------------------------------------------------+
+| EndX             | Input       | double    | End of the spectrum     | An X value in the last bin to be included in the fit    |
++------------------+-------------+-----------+-------------------------+---------------------------------------------------------+
+| XColumn          | Input       | string    |                         | The name of the X column.                               |
++------------------+-------------+-----------+-------------------------+---------------------------------------------------------+
+| YColumn          | Input       | string    |                         | The name of the Y column.                               |
++------------------+-------------+-----------+-------------------------+---------------------------------------------------------+
+| ErrColumn        | Input       | string    |                         | The name of the error column.                           |
++------------------+-------------+-----------+-------------------------+---------------------------------------------------------+
+
 Overview
 ########
 
 This is a generic algorithm for fitting data in a Workspace with a
 function. The workspace must have the type supported by the algorithm.
-Currently supported types are: :ref:`MatrixWorkspace <MatrixWorkspace>` for
-fitting with a IFunction1D and :ref:`MDWorkspace <MDWorkspace>` for fitting with
-IFunctionMD. After Function and InputWorkspace properties are set the algorithm
-may decide that it needs more information from the caller to locate the fitting
-data. For example, if a spectrum in a MatrixWorkspace is to be fit with a 1D
+Currently supported types are: :ref:`MatrixWorkspace <MatrixWorkspace>` and
+:ref:`Table Workspace <Table Workspaces>` for fitting with a IFunction1D and
+:ref:`MDWorkspace <MDWorkspace>` for fitting with IFunctionMD.
+After Function and InputWorkspace properties are set the algorithm may decide
+that it needs more information from the caller to locate the fitting data.
+For example, if a spectrum in a MatrixWorkspace is to be fit with a 1D
 function it will need to know at least the index of that spectrum. To request
 this information Fit dynamically creates relevant properties which the caller
 can set. Note that the dynamic properties depend both on the workspace
@@ -426,6 +445,52 @@ Output:
    Number of spectra in fitWorkspace is: 3
    The 20th y-value of the calculated pattern: 0.2361
 
+**Example - Fit a Gaussian to a Table Workspace:**
+
+.. testcode:: exTableFit
+
+    import math
+
+    #Create a table workspace with a gaussian curve and a flat background of 0.5
+    tableWS = CreateEmptyTableWorkspace()
+    tableWS.addColumn(type="double",name="X data")
+    tableWS.addColumn(type="double",name="Y data")
+    tableWS.addColumn(type="double",name="Errors")
+
+    for i in range(0,99):
+        xValue = i * 0.1
+        yValue = 10 * math.exp(-0.5 * (xValue - 5.0)**2 / 0.7**2 ) + 0.5
+        eValue = 0.5
+        tableWS.addRow ( {'X data': xValue, 'Y data': yValue, 'Errors': eValue} )
+
+    # Do the fitting
+    myFunc = 'name=Gaussian, PeakCentre=4, Height=8, Sigma=1'
+    fit_output = Fit(InputWorkspace=tableWS, StartX = 1, EndX=20, Output='fit', Function=myFunc, \
+                     XColumn = 'X data', YColumn = 'Y data', ErrColumn = 'Errors')
+    paramTable = fit_output.OutputParameters
+    fitWorkspace = fit_output.OutputWorkspace
+
+    print("The fit was: {}".format(fit_output.OutputStatus))
+    print("chi-squared of fit is: {:.2f}".format(fit_output.OutputChi2overDoF))
+    print("Fitted Height value is: {:.2f}".format(paramTable.column(1)[0]))
+    print("Fitted centre value is: {:.2f}".format(paramTable.column(1)[1]))
+    print("Fitted sigma value is: {:.2f}".format(paramTable.column(1)[2]))
+    # fitWorkspace contains the data, the calculated and the difference patterns
+    print("Number of spectra in fitWorkspace is: {}".format(fitWorkspace.getNumberHistograms()))
+    print("The 20th y-value of the calculated pattern: {:.4f}".format(fitWorkspace.readY(1)[19]))
+
+Output:
+
+.. testoutput:: exTableFit
+
+    The fit was: success
+    chi-squared of fit is: 0.59
+    Fitted Height value is: 10.33
+    Fitted centre value is: 5.00
+    Fitted sigma value is: 0.75
+    Number of spectra in fitWorkspace is: 3
+    The 20th y-value of the calculated pattern: 0.2125
+
 **Example - Fit to two data sets simultaneously:**
 
 .. testcode:: simFit
diff --git a/docs/source/algorithms/FitIncidentSpectrum-v1.rst b/docs/source/algorithms/FitIncidentSpectrum-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..3239be46aa8a989338a19c20bf464582374bfc24
--- /dev/null
+++ b/docs/source/algorithms/FitIncidentSpectrum-v1.rst
@@ -0,0 +1,117 @@
+.. algorithm::
+
+.. summary::
+
+.. relatedalgorithms::
+
+.. properties::
+
+Description
+-----------
+
+This algorithm fits and functionalizes an incident spectrum and finds its first derivative.
+FitIncidentSpectrum is able to fit an incident spectrum using:
+
+*  GaussConvCubicSpline: A fit with Cubic Spline using a Gaussian Convolution to get weights. In builds running older
+   versions of SciPy the first derivative is can be less accurate.
+*  CubicSpline: A fit using a cubic cline.
+*  CubicSplineViaMantid: A fit with cubic spline using the mantid SplineSmoothing algorithm.
+
+Usage
+-----
+
+**Example: fit an incident spectrum using GaussConvCubicSpline** [1]_
+
+.. testcode:: ExFitIncidentSpectrum
+
+    import numpy as np
+    import matplotlib.pyplot as plt
+    from mantid.simpleapi import \
+        AnalysisDataService, \
+        CalculateEfficiencyCorrection, \
+        ConvertToPointData, \
+        CreateWorkspace, \
+        Divide, \
+        FitIncidentSpectrum, \
+        Rebin
+
+    # Create the workspace to hold the already corrected incident spectrum
+    incident_wksp_name = 'incident_spectrum_wksp'
+    binning_incident = "%s,%s,%s" % (0.2, 0.01, 4.0)
+    binning_for_calc = "%s,%s,%s" % (0.2, 0.2, 4.0)
+    binning_for_fit = "%s,%s,%s" % (0.2, 0.01, 4.0)
+    incident_wksp = CreateWorkspace(
+        OutputWorkspace=incident_wksp_name,
+        NSpec=1,
+        DataX=[0],
+        DataY=[0],
+        UnitX='Wavelength',
+        VerticalAxisUnit='Text',
+        VerticalAxisValues='IncidentSpectrum')
+    incident_wksp = Rebin(InputWorkspace=incident_wksp, Params=binning_incident)
+    incident_wksp = ConvertToPointData(InputWorkspace=incident_wksp)
+
+
+    # Spectrum function given in Milder et al. Eq (5)
+    def incidentSpectrum(wavelengths, phiMax, phiEpi, alpha, lambda1, lambda2,
+                         lamdaT):
+        deltaTerm = 1. / (1. + np.exp((wavelengths - lambda1) / lambda2))
+        term1 = phiMax * (
+            lambdaT**4. / wavelengths**5.) * np.exp(-(lambdaT / wavelengths)**2.)
+        term2 = phiEpi * deltaTerm / (wavelengths**(1 + 2 * alpha))
+        return term1 + term2
+
+
+    # Variables for polyethlyene moderator at 300K
+    phiMax = 6324
+    phiEpi = 786
+    alpha = 0.099
+    lambda1 = 0.67143
+    lambda2 = 0.06075
+    lambdaT = 1.58
+
+    # Add the incident spectrum to the workspace
+    corrected_spectrum = incidentSpectrum(
+        incident_wksp.readX(0), phiMax, phiEpi, alpha, lambda1, lambda2, lambdaT)
+    incident_wksp.setY(0, corrected_spectrum)
+
+    # Calculate the efficiency correction for Alpha=0.693
+    # and back calculate measured spectrum
+    eff_wksp = CalculateEfficiencyCorrection(
+        InputWorkspace=incident_wksp, Alpha=0.693)
+    measured_wksp = Divide(LHSWorkspace=incident_wksp, RHSWorkspace=eff_wksp)
+
+    # Fit incident spectrum
+    prefix = "incident_spectrum_fit_with_"
+
+    fit_gauss_conv_spline = prefix + "_gauss_conv_spline"
+    FitIncidentSpectrum(
+        InputWorkspace=incident_wksp,
+        OutputWorkspace=fit_gauss_conv_spline,
+        BinningForCalc=binning_for_calc,
+        BinningForFit=binning_for_fit,
+        FitSpectrumWith="GaussConvCubicSpline")
+
+    # Retrieve workspaces
+    wksp_fit_gauss_conv_spline = AnalysisDataService.retrieve(
+        fit_gauss_conv_spline)
+
+    print(wksp_fit_gauss_conv_spline.readY(0))
+
+Output:
+
+.. testoutput:: ExFitIncidentSpectrum
+
+    [ 5328.83700775  2330.08408285  1600.78200105  2543.59379589  3249.78956903
+      2797.87138465  2050.3366076   1417.4868309    965.23854845   659.79544224
+       456.54322031   320.88688262   229.29830975   166.5536716    122.89703604
+        92.0419568     69.89199835    53.75902111    41.84355559]
+
+References
+------------
+
+.. [1] D. F. R. Mildner, B. C. Boland, R. N. Sinclair, C. G. Windsor, L. J. Bunce, and J. H. Clarke (1977) *A Cooled Polyethylene Moderator on a Pulsed Neutron Source*, Nuclear Instruments and Methods 152 437-446 `doi: 10.1016/0029-554X(78)90043-5 <https://doi.org/10.1016/0029-554X(78)90043-5>`__
+
+.. categories::
+
+.. sourcelink::
\ No newline at end of file
diff --git a/docs/source/algorithms/LoadNexusProcessed-v2.rst b/docs/source/algorithms/LoadNexusProcessed-v2.rst
new file mode 100644
index 0000000000000000000000000000000000000000..4dc51a24d88d32259cb2e1bcccb649f127d7fb06
--- /dev/null
+++ b/docs/source/algorithms/LoadNexusProcessed-v2.rst
@@ -0,0 +1,103 @@
+
+.. algorithm::
+
+.. summary::
+
+.. relatedalgorithms::
+
+.. properties::
+
+Description
+-----------
+
+The algorithm LoadNexusProcessed will read a Nexus data file created
+by :ref:`algm-SaveNexusProcessed` or `algm-SaveNexusESS` and place the data into the named workspace. The file name can be an absolute or relative path and
+should have the extension .nxs, .nx5 or .xml. 
+
+.. warning:: 
+	Using XML format can be extremely slow for large data sets and generate very
+	large files.
+
+The optional parameters can be used to control which spectra are
+loaded into the workspace. If SpectrumMin and SpectrumMax are given,
+then only that range to data will be loaded. A specific list of
+spectra to load can also be given (SpectrumList). Filtering of spectra
+is supported when loading into workspaces of type :ref:`Workspace2Ds
+<Workspace2D>` and also :ref:`EventWorkspaces <EventWorkspace>`.
+
+
+A Mantid Nexus file may contain several workspace entries each labelled
+with an integer starting at 1. By default the highest number workspace
+is read, earlier ones can be accessed by setting the EntryNumber.
+
+If the saved data has a reference to an XML file defining instrument
+geometry this will be read.
+
+This is version 2 of the algorithm. For old behaviour :ref:`algm-SaveNexusProcessed-v1`, which does not handle :ref:`algm-SaveNexusESS` outputs. This is the only difference between the two versions.
+
+Time series data
+################
+
+The log data in the Nexus file (NX\_LOG sections) is loaded as
+TimeSeriesProperty data within the workspace. Time is stored as seconds
+from the Unix epoch. Only floating point logs are stored and loaded at
+present.
+
+Child algorithms used
+#####################
+
+The Child Algorithms used by LoadMuonNexus are:
+
+-  LoadInstrument - this algorithm looks for an XML description of the
+   instrument and if found reads it.
+
+Usage
+-----
+
+**Example**
+
+.. testcode:: LoadNexusProcessedex
+
+    import os
+
+    #Create an absolute path by joining the proposed filename to a directory
+    #os.path.expanduser("~") used in this case returns the home directory of the current user
+    savePath = os.path.expanduser("~")
+    wsPath = os.path.join(savePath, "LoadNexusProcessedTest.nxs")
+
+    ws = CreateSampleWorkspace(WorkspaceType="Histogram", NumBanks=2, BankPixelWidth=1, BinWidth=10, Xmax=50)
+    SaveNexusProcessed(ws, wsPath)
+
+    wsOutput = LoadNexusProcessed(wsPath)
+
+    print(CompareWorkspaces(ws,wsOutput, CheckInstrument=False)[0])
+
+    os.remove(wsPath)
+
+Output:
+
+.. testoutput:: LoadNexusProcessedex
+
+   True
+
+.. testcode:: SaveNexusESSExample
+
+    from mantid.simpleapi import *
+    import os
+    import tempfile
+    simple_run = CreateSampleWorkspace(NumBanks=2, BankPixelWidth=10)
+    destination = os.path.join(tempfile.gettempdir(), "sample_processed.nxs")
+    SaveNexusESS(Filename=destination, InputWorkspace=simple_run)
+    ws = LoadNexusProcessed(Filename=destination)
+    print("Workspace includes {} detectors".format(ws.detectorInfo().size(), 2 * 10 * 10))
+    os.remove(destination)
+
+Output:
+
+.. testoutput:: SaveNexusESSExample
+
+  Workspace includes 200 detectors
+
+.. categories::
+
+.. sourcelink::
diff --git a/docs/source/algorithms/SaveHKL-v1.rst b/docs/source/algorithms/SaveHKL-v1.rst
index c293e2eee2c6b547001263ec8879604591efb8b2..da97ef84535f1acb2ffbc5e02f3d7c44bd494f33 100644
--- a/docs/source/algorithms/SaveHKL-v1.rst
+++ b/docs/source/algorithms/SaveHKL-v1.rst
@@ -10,7 +10,7 @@ Description
 -----------
 
 SaveHKL outputs the peaks with corrections applied in a format
-that works successfully in GSAS and SHELX. Peaks that have not been 
+that works successfully in GSAS and SHELX. Peaks that have not been
 integrated and also peaks that were not indexed are removed.
 
 hklFile.write('%4d%4d%4d%8.2f%8.2f%4d%8.4f%7.4f%7d%7d%7.4f%4d%9.5f%9.4f\\n'%
@@ -87,7 +87,7 @@ Output:
     #load a peaks workspace from file
     peaks = LoadIsawPeaks(Filename=r'Peaks5637.integrate')
     print("Number of peaks in table {}".format(peaks.rowCount()))
-    
+
     path = os.path.join(os.path.expanduser("~"), "MyPeaks.hkl")
     SaveHKL(peaks, path, MinWavelength=0.5, MaxWavelength=2,MinDSpacing=0.2, SortBy='Bank')
 
@@ -114,6 +114,43 @@ Output:
 
     removeFiles(["MyPeaks.hkl"])
 
+**Example - SaveHKL with shape from SetSample**
+
+.. testcode:: ExSaveHKLSetSample
+
+    import os
+    path = os.path.join(os.path.expanduser("~"), "MyPeaks.hkl")
+
+    # load a peaks workspace from file
+    peaks = LoadIsawPeaks(Filename=r'SXD23767.peaks')
+    SetSample(peaks,
+              Geometry={'Shape': 'Cylinder', 'Height': 4.0,
+                        'Radius': 0.8,
+                        'Center': [0.,0.,0.]},
+              Material={'ChemicalFormula': 'V', 'SampleNumberDensity': 0.1})
+    SaveHKL(peaks, path)
+    print(os.path.isfile(path))
+
+Output:
+
+.. testoutput:: ExSaveHKLSetSample
+
+    True
+
+.. testcleanup:: ExSaveHKLSimple
+
+    import os
+    def removeFiles(files):
+      for ws in files:
+        try:
+          path = os.path.join(os.path.expanduser("~"), ws)
+          os.remove(path)
+        except:
+          pass
+
+    removeFiles(["MyPeaks.hkl"])
+
+
 
 
 .. categories::
diff --git a/docs/source/algorithms/SaveNexusESS-v1.rst b/docs/source/algorithms/SaveNexusESS-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..022de450b4d882582f556c6aeac1be87553eb8bd
--- /dev/null
+++ b/docs/source/algorithms/SaveNexusESS-v1.rst
@@ -0,0 +1,48 @@
+
+.. algorithm::
+
+.. summary::
+
+.. relatedalgorithms::
+
+.. properties::
+
+Description
+-----------
+
+Saves a processed nexus file similar to :ref:`algm-SaveNexusProcessed`, but provides nexus geometry which is an accurate snapshot of the calibrated/transformed instrument geometry in-memory. One current major difference between the two algorithms is that SaveNexusESS does not support the generation of a single processed file based on a :ref:`GroupWorkspace <WorkspaceGroup>` input.
+
+This algorithm may be deprecated in future in favour of a master :ref:`algm-SaveNexusProcessed` algorithm.
+
+This algorithm currently provides not shape information for component geometry.
+
+Usage
+-----
+..  Try not to use files in your examples,
+    but if you cannot avoid it then the (small) files must be added to
+    autotestdata\UsageData and the following tag unindented
+    .. include:: ../usagedata-note.txt
+
+**Example - SaveNexusESS**
+
+.. testcode:: SaveNexusESSExample
+
+    from mantid.simpleapi import *
+    import os
+    import tempfile
+    simple_run = CreateSampleWorkspace(NumBanks=2, BankPixelWidth=10)
+    destination = os.path.join(tempfile.gettempdir(), "sample_processed.nxs")
+    SaveNexusESS(Filename=destination, InputWorkspace=simple_run)
+    print("Created: {}".format(os.path.isfile(destination)))
+    os.remove(destination)
+
+Output:
+
+.. testoutput:: SaveNexusESSExample
+
+  Created: True
+
+.. categories::
+
+.. sourcelink::
+
diff --git a/docs/source/algorithms/StartLiveData-v1.rst b/docs/source/algorithms/StartLiveData-v1.rst
index 8cb0d00fcc9d6a95c82974aed1311a57dba331b4..9cea6f976d8742dd0da9352ee9afcddf43ae5f8e 100644
--- a/docs/source/algorithms/StartLiveData-v1.rst
+++ b/docs/source/algorithms/StartLiveData-v1.rst
@@ -46,6 +46,20 @@ PeriodList and SpectraList are properties of the ISISHistoDataListener. They
 are available as arguments in this call because Instrument is set to
 'ISIS_Histogram', which uses that listener.
 
+KafkaEventListener
+******************
+
+``BufferThreshold`` defines the number of events (default 1000000) to hold in the intermediate buffer before it is flushed to the streamed EventWorkspace.
+This parameter must be tuned to the data you are streaming.
+Setting this parameter too high for your event rate will cause behaviour that make live streaming appear to have stalled.
+Setting this too low may cause performance issues with high count rates.
+
+There is no general rule for deriving this parameter that will give the best performance.
+To ensure that live streaming remains responsive this parameter should be set to ``UpdateEvery`` times the approximate event rate.
+Due to the nature of the intermediate buffer it is not possible to avoid having to know the approximate event rate before streaming.
+
+25000000 has shown to work well for simulated LOKI data at 10e7 events per second.
+
 Live Plots
 ##########
 
diff --git a/docs/source/concepts/PropertiesFile.rst b/docs/source/concepts/PropertiesFile.rst
index 08a5581c86341758c739a16459296f1036158c45..4229c7894dd6da10908c28737d442e8fc8b19fa5 100644
--- a/docs/source/concepts/PropertiesFile.rst
+++ b/docs/source/concepts/PropertiesFile.rst
@@ -210,12 +210,12 @@ ScriptRepository Properties
 +============================+===============================================+======================================================================+
 | ``ScriptLocalRepository``  |Directory where ScriptRepository is Installed. | ``C:\\MantidInstall\\MyScriptRepository``                            |
 +----------------------------+-----------------------------------------------+----------------------------------------------------------------------+
-| ``ScriptRepository``       |Base URL for the remote script repository.     | ``http://download.mantidproject.org/scriptrepository/``              |
+| ``ScriptRepository``       |Base URL for the remote script repository.     | ``https://download.mantidproject.org/scriptrepository/``             |
 +----------------------------+-----------------------------------------------+----------------------------------------------------------------------+
 | ``ScriptRepositoryIgnore`` |CSV patterns for paths that should not be      | ``*pyc;``                                                            |
 |                            |listed at ScriptRepository.                    |                                                                      |
 +----------------------------+-----------------------------------------------+----------------------------------------------------------------------+
-| ``UploaderWebServer``      |URL for uploading scripts.                     | ``http://upload.mantidproject.org/scriptrepository/payload/publish`` |
+| ``UploaderWebServer``      |URL for uploading scripts.                     | ``https://upload.mantidproject.org/scriptrepository/payload/publish``|
 +----------------------------+-----------------------------------------------+----------------------------------------------------------------------+
 
 
diff --git a/docs/source/release/v4.2.0/diffraction.rst b/docs/source/release/v4.2.0/diffraction.rst
index e4e1ede34ecf2064eed44e449c2036431e4d5024..4475f6eda2f2292fef0eda361629f9be2c643993 100644
--- a/docs/source/release/v4.2.0/diffraction.rst
+++ b/docs/source/release/v4.2.0/diffraction.rst
@@ -16,6 +16,8 @@ Improvements
 ############
 
 - The HRPD scripts now mask out the Bragg peaks from the Vanadium.
+- The file-naming scheme for ISIS powder is now controlled by a string template
+- The file-naming of output on HRPD as been updated to closely match old script outputs
 - Geometry definition for LLB 5C1
 
 Bug Fixes
@@ -30,6 +32,11 @@ Engineering Diffraction
 Single Crystal Diffraction
 --------------------------
 
+Improvements
+############
+
+- :ref:`SaveHKL <algm-SaveHKL>` now saves the tbar and transmission values for shapes and materials provided by :ref:`SetSample <algm-SetSample>`.
+
 Imaging
 -------
 
diff --git a/docs/source/release/v4.2.0/direct_geometry.rst b/docs/source/release/v4.2.0/direct_geometry.rst
index 452280ba58080dd0ffdfc3770cddf51064536fe7..6e2aea72a5282fc8c834bf44ed2290cbfafdf2cc 100644
--- a/docs/source/release/v4.2.0/direct_geometry.rst
+++ b/docs/source/release/v4.2.0/direct_geometry.rst
@@ -9,4 +9,6 @@ Direct Geometry Changes
     putting new features at the top of the section, followed by
     improvements, followed by bug fixes.
 
-:ref:`Release 4.2.0 <v4.2.0>`
\ No newline at end of file
+* Added the ``CHESS`` and ``ZEEMANS`` instruments  in the ``Facilities.xml`` to SNS for the second target station
+
+:ref:`Release 4.2.0 <v4.2.0>`
diff --git a/docs/source/release/v4.2.0/framework.rst b/docs/source/release/v4.2.0/framework.rst
index e66ee54a72bf171966dd0aa9f8d22cd55fb7de9b..dd0a72879211e45749e032e908a00c6beb442a70 100644
--- a/docs/source/release/v4.2.0/framework.rst
+++ b/docs/source/release/v4.2.0/framework.rst
@@ -15,8 +15,11 @@ Concepts
 Algorithms
 ----------
 * :ref:`MaskAngle <algm-MaskAngle>` has an additional option of ``Angle='InPlane'``
+* :ref:`FitIncidentSpectrum <algm-FitIncidentSpectrum>` will fit a curve to an incident spectrum returning the curve and it's first derivative.
 * Whitespace is now ignored anywhere in the string when setting the Filename parameter in :ref:`Load <algm-Load>`.
 * Added options to :ref:`SaveMD <algm-SaveMD>` to allow selection of what will be saved. For MDHistoWorkspace only.
+* New algorithm :ref:`SaveNexusESS <algm-SaveNexusESS>` to save data and nexus geometry to a single processed file.
+* Version upgrade :ref:`LoadNexusProcessed <algm-LoadNexusProcessed>` to allow loading of both existing Mantid format Processed Nexus files and those produced via :ref:`SaveNexusESS <algm-SaveNexusESS>`.
 
 Data Objects
 ------------
diff --git a/docs/source/release/v4.2.0/mantidworkbench.rst b/docs/source/release/v4.2.0/mantidworkbench.rst
index 0f21f184f2b6f973195f33546422a5540478d2a0..178e1394ccf4d76179b6e45ed28f0253eea8605c 100644
--- a/docs/source/release/v4.2.0/mantidworkbench.rst
+++ b/docs/source/release/v4.2.0/mantidworkbench.rst
@@ -12,6 +12,7 @@ User Interface
 - The zoom icon in the SliceViewer and plot toolbars have been replaced with clearer icons.
 - Plots now allow the insertion of draggable horizontal and vertical markers.
 - Marker label, color and line style can be edited on a per-marker basis.
+- The button to remove a curve in the figure options is now the same size as the drop-down list of curves.
 
 New
 ###
@@ -28,6 +29,8 @@ Improvements
 - The dialog for selecting spectra to plot now has the spectrum number input field selected by default.
 - There are now icons alongside the colormap names in the plot options dialog.
 
+- Hex codes can now be inputted directly into the color selectors in figure options.
+
 Bugfixes
 ########
 - Pressing the tab key while in the axis quick editor now selects each input field in the correct order.
diff --git a/docs/source/techniques/ISISPowder-GEM-v1.rst b/docs/source/techniques/ISISPowder-GEM-v1.rst
index 230c9deefab34e4476bbea7ed404f27ec0f95722..b53adf8d08f5c81e056c78d7bfd952a74aeb74a6 100644
--- a/docs/source/techniques/ISISPowder-GEM-v1.rst
+++ b/docs/source/techniques/ISISPowder-GEM-v1.rst
@@ -900,6 +900,36 @@ on GEM this is set to the following (this file is distributed with Mantid):
 
   gsas_calib_filename: "GEM_PF1_PROFILE.IPF"
 
+.. _nxs_filename_gem_isis-powder-diffraction-ref:
+
+nxs_filename
+^^^^^^^^^^^^
+A template for the filename of the generated NeXus file.
+
+.. _gss_filename_gem_isis-powder-diffraction-ref:
+
+gss_filename
+^^^^^^^^^^^^
+A template for the filename of the generated GSAS file.
+
+.. _dat_files_directory_gem_isis-powder-diffraction-ref:
+
+dat_files_directory
+^^^^^^^^^^^^^^^^^^^
+The subdirectory of the output directory where the .dat files are saved
+
+.. _tof_xye_filename_gem_isis-powder-diffraction-ref:
+
+tof_xye_filename
+^^^^^^^^^^^^^^^^
+A template for the filename of the generated TOF XYE file.
+
+.. _dspacing_xye_filename_gem_isis-powder-diffraction-ref:
+
+dspacing_xye_filename
+^^^^^^^^^^^^^^^^^^^^^
+A template for the filename of the generated dSpacing XYE file.
+
 .. _maud_grouping_scheme_gem_isis-powder-diffraction-ref:
 
 maud_grouping_scheme
diff --git a/docs/source/techniques/ISISPowder-HRPD-v1.rst b/docs/source/techniques/ISISPowder-HRPD-v1.rst
index ec12d0e4f4f21313917d2c6cf88c90187d256ab2..0429344d295ad2e1340b0321937a5f02e2c8b2ee 100644
--- a/docs/source/techniques/ISISPowder-HRPD-v1.rst
+++ b/docs/source/techniques/ISISPowder-HRPD-v1.rst
@@ -613,6 +613,36 @@ On HRPD this is set to the following:
 		 
   grouping_file_name = "hrpd_new_072_01_corr.cal"
 
+.. _nxs_filename_hrpd_isis-powder-diffraction-ref:
+
+nxs_filename
+^^^^^^^^^^^^
+A template for the filename of the generated NeXus file.
+
+.. _gss_filename_hrpd_isis-powder-diffraction-ref:
+
+gss_filename
+^^^^^^^^^^^^
+A template for the filename of the generated GSAS file.
+
+.. _dat_files_directory_hrpd_isis-powder-diffraction-ref:
+
+dat_files_directory
+^^^^^^^^^^^^^^^^^^^
+The subdirectory of the output directory where the .dat files are saved
+
+.. _tof_xye_filename_hrpd_isis-powder-diffraction-ref:
+
+tof_xye_filename
+^^^^^^^^^^^^^^^^
+A template for the filename of the generated TOF XYE file.
+
+.. _dspacing_xye_filename_hrpd_isis-powder-diffraction-ref:
+
+dspacing_xye_filename
+^^^^^^^^^^^^^^^^^^^^^
+A template for the filename of the generated dSpacing XYE file.
+
 .. _mode_hrpd_isis-powder-diffraction-ref:
 
 mode
diff --git a/docs/source/techniques/ISISPowder-Pearl-v1.rst b/docs/source/techniques/ISISPowder-Pearl-v1.rst
index 204277243c34bb87590194af9a0e5fcf82056397..e83101603a5b803a574fd1f974c0a1c6ad078153 100644
--- a/docs/source/techniques/ISISPowder-Pearl-v1.rst
+++ b/docs/source/techniques/ISISPowder-Pearl-v1.rst
@@ -1096,6 +1096,37 @@ On PEARL this is set to the following:
 
   vanadium_absorb_filename: "pearl_absorp_sphere_10mm_newinst2_long.nxs"
 
+.. _nxs_filename_pearl_isis-powder-diffraction-ref:
+
+nxs_filename
+^^^^^^^^^^^^
+A template for the filename of the generated NeXus file.
+
+.. _gss_filename_pearl_isis-powder-diffraction-ref:
+
+gss_filename
+^^^^^^^^^^^^
+A template for the filename of the generated GSAS file.
+
+.. _dat_files_directory_pearl_isis-powder-diffraction-ref:
+
+dat_files_directory
+^^^^^^^^^^^^^^^^^^^
+The subdirectory of the output directory where the .dat files are saved
+
+.. _tof_xye_filename_pearl_isis-powder-diffraction-ref:
+
+tof_xye_filename
+^^^^^^^^^^^^^^^^
+A template for the filename of the generated TOF XYE file.
+
+.. _dspacing_xye_filename_pearl_isis-powder-diffraction-ref:
+
+dspacing_xye_filename
+^^^^^^^^^^^^^^^^^^^^^
+A template for the filename of the generated dSpacing XYE file.
+
+
 .. _vanadium_tof_cropping_pearl_isis-powder-diffraction-ref:
 
 vanadium_tof_cropping
diff --git a/docs/source/techniques/ISISPowder-Polaris-v1.rst b/docs/source/techniques/ISISPowder-Polaris-v1.rst
index bb77370d6daa5b38a07a1bd50fbb9fd128fe9016..9152d3320bf855dcb19d26a115e484c1054181c9 100644
--- a/docs/source/techniques/ISISPowder-Polaris-v1.rst
+++ b/docs/source/techniques/ISISPowder-Polaris-v1.rst
@@ -650,6 +650,36 @@ On POLARIS this is set to the following:
 
   vanadium_peaks_masking_file: "VanaPeaks.dat"
 
+.. _nxs_filename_polaris_isis-powder-diffraction-ref:
+
+nxs_filename
+^^^^^^^^^^^^
+A template for the filename of the generated NeXus file.
+
+.. _gss_filename_polaris_isis-powder-diffraction-ref:
+
+gss_filename
+^^^^^^^^^^^^
+A template for the filename of the generated GSAS file.
+
+.. _dat_files_directory_polaris_isis-powder-diffraction-ref:
+
+dat_files_directory
+^^^^^^^^^^^^^^^^^^^
+The subdirectory of the output directory where the .dat files are saved
+
+.. _tof_xye_filename_polaris_isis-powder-diffraction-ref:
+
+tof_xye_filename
+^^^^^^^^^^^^^^^^
+A template for the filename of the generated TOF XYE file.
+
+.. _dspacing_xye_filename_polaris_isis-powder-diffraction-ref:
+
+dspacing_xye_filename
+^^^^^^^^^^^^^^^^^^^^^
+A template for the filename of the generated dSpacing XYE file.
+
 .. _sample_empty_scale_polaris_isis-powder-diffraction-ref:
 
 sample_empty_scale
diff --git a/instrument/CG2_Definition.xml b/instrument/CG2_Definition.xml
index 50721841f4484fc177526ed808740007a325863a..5b89e708ca222fc575a6b225642dc2c326d0171d 100644
--- a/instrument/CG2_Definition.xml
+++ b/instrument/CG2_Definition.xml
@@ -433,7 +433,7 @@
   <component type="double-flat-panel" idlist="pixel_ids" name="detector1">
     <location>
       <parameter name="x">
-        <logfile id="detector-translation" eq="0.001*value"/>
+        <logfile id="detector-translation" eq="-0.001*value"/>
       </parameter>
       <parameter name="z">
         <logfile id="sdd" eq="0.001*value"/>
diff --git a/instrument/Facilities.xml b/instrument/Facilities.xml
index 26cc1843cc305e63c79a85da58e9362415ef6a79..7ba5baee6fe35b8ccdf5f0400cbfda798f7f7d9e 100644
--- a/instrument/Facilities.xml
+++ b/instrument/Facilities.xml
@@ -419,19 +419,19 @@
       <technique>technique</technique>
    </instrument>
 
-   <instrument name="GPSANS">
+   <instrument name="GPSANS" shortname="CG2">
       <technique>Small Angle Scattering</technique>
    </instrument>
 
-   <instrument name="HIRESSANS">
+   <instrument name="CG2" shortname="CG2">
       <technique>Small Angle Scattering</technique>
    </instrument>
 
-   <instrument name="CG2">
+   <instrument name="BIOSANS"  shortname="CG3">
       <technique>Small Angle Scattering</technique>
    </instrument>
 
-   <instrument name="BIOSANS">
+   <instrument name="HIRESSANS">
       <technique>Small Angle Scattering</technique>
    </instrument>
 
@@ -595,6 +595,18 @@
       <technique>Small Wide Angle Scattering</technique>
    </instrument>
 
+   <instrument name="ZEEMANS" beamline="998">
+    <technique>Neutron Spectroscopy</technique>
+    <technique>TOF Indirect Geometry Spectroscopy</technique>
+    <technique>Neutron Diffraction</technique>
+   </instrument>
+
+   <instrument name="CHESS" beamline="999">
+    <technique>Neutron Spectroscopy</technique>
+    <technique>TOF Direct Geometry Spectroscopy</technique>
+    <technique>Neutron Diffraction</technique>
+   </instrument>
+
 </facility>
 
 <facility name="NCNR" FileExtensions=".dat,.xml">
@@ -876,7 +888,7 @@
     <technique>Neutron Spectroscopy</technique>
     <technique>Inelastic Neutron Scattering</technique>
   </instrument>
-  
+
 </facility>
 
 <!-- HZB -->
diff --git a/instrument/IN16B_Parameters.xml b/instrument/IN16B_Parameters.xml
index 3b7632c2507d962031cc30f02e5be4174f72c4e0..a17469199c8d746d97cef6bb8b1521e926092fbd 100644
--- a/instrument/IN16B_Parameters.xml
+++ b/instrument/IN16B_Parameters.xml
@@ -26,16 +26,16 @@
     <value val="run_number, start_time, end_time, SamS_Rot.offset_value, SamS_Rot.target_value, SamS_Rot.value, sample.setpoint_field, sample.setpoint_pressure, sample.setpoint_temperature" />
 </parameter>
 <parameter name="sample_logs_warn" type="string">
-    <value val="monitor.master_pickup, selector.rotation_speed, reactor_power, BG.phase, BG.setpoint_phase, BG.setpoint_rotation_gear, BG.rotation_speed, BG.reference_speed, PST.phase, PST.setpoint_phase, PST.setpoint_rotation_gear, PST.rotation_speed, PST.reference_speed, CH1.phase, CH1.setpoint_phase, CH1.setpoint_rotation_gear, CH1.rotation_speed, CH1.reference_speed, CH2.phase, CH2.setpoint_phase, CH2.setpoint_rotation_gear, CH2.rotation_speed, CH2.reference_speed, CH3.phase, CH3.setpoint_phase, CH3.setpoint_rotation_gear, CH3.rotation_speed, CH3.reference_speed, CH4.phase, CH4.setpoint_phase, CH4.setpoint_rotation_gear, CH4.rotation_speed, CH4.reference_speed" />
+    <value val="monitor.master_pickup, selector.rotation_speed, reactor_power, BG.phase, BG.setpoint_phase, BG.setpoint_rotation_gear, BG.rotation_speed, BG.reference_speed, PST.phase, PST.setpoint_phase, PST.setpoint_rotation_gear, PST.rotation_speed, PST.reference_speed, CH1.phase, CH1.setpoint_phase, CH1.setpoint_rotation_gear, CH1.rotation_speed, CH1.reference_speed, CH2.phase, CH2.setpoint_phase, CH2.setpoint_rotation_gear, CH2.rotation_speed, CH2.reference_speed, CH3.phase, CH3.setpoint_phase, CH3.setpoint_rotation_gear, CH3.rotation_speed, CH3.reference_speed, CH4.phase, CH4.setpoint_phase, CH4.setpoint_rotation_gear, CH4.rotation_speed, CH4.reference_speed, PSD.time_of_flight_0, PSD.time_of_flight_1, PSD.time_of_flight_2, PSD.wanted_t1_delay, PSD.wanted_t2_tofvalidation, monitor.time_of_flight_0, monitor.time_of_flight_1, monitor.time_of_flight_2, monitor.wanted_t1_delay, monitor.wanted_t2_tofvalidation, SingleD.time_of_flight_0, SingleD.time_of_flight_1, SingleD.time_of_flight_2, SingleD.wanted_t1_delay, SingleD.wanted_t2_tofvalidation" />
 </parameter>
 <parameter name="sample_logs_warn_tolerances" type="string">
-    <value val="0.5, 50, 1, 0.1, 0.1, 0.1, 5, 5, 0.1, 0.1, 0.1, 5, 5, 0.1, 0.1, 0.1, 5, 5, 0.1, 0.1, 0.1, 5, 5, 0.1, 0.1, 0.1, 5, 5, 0.1, 0.1, 0.1, 5, 5" />
+    <value val="0.5, 50, 1, 0.1, 0.1, 0.1, 5, 5, 0.1, 0.1, 0.1, 5, 5, 0.1, 0.1, 0.1, 5, 5, 0.1, 0.1, 0.1, 5, 5, 0.1, 0.1, 0.1, 5, 5, 0.1, 0.1, 0.1, 5, 5, 0.001, 1, 1, 1, 1, 0.001, 1, 1, 1, 1, 0.001, 1, 1, 1, 1" />
 </parameter>
 <parameter name="sample_logs_fail" type="string">
-    <value val="PSD.PSD angle 1, Doppler.mirror_sense, acquisition_mode, Doppler.maximum_delta_energy, wavelength, PSD.time_of_flight_0, PSD.time_of_flight_1, PSD.time_of_flight_2, PSD.wanted_t1_delay, PSD.wanted_t2_tofvalidation, monitor.time_of_flight_0, monitor.time_of_flight_1, monitor.time_of_flight_2, monitor.wanted_t1_delay, monitor.wanted_t2_tofvalidation, SingleD.time_of_flight_0, SingleD.time_of_flight_1, SingleD.time_of_flight_2, SingleD.wanted_t1_delay, SingleD.wanted_t2_tofvalidation" />
+    <value val="PSD.PSD angle 1, Doppler.mirror_sense, acquisition_mode, Doppler.maximum_delta_energy, wavelength" />
 </parameter>
 <parameter name="sample_logs_fail_tolerances" type="string">
-    <value val="1, 0, 0, 0.001, 0.001, 0.001, 1, 1, 1, 1, 0.001, 1, 1, 1, 1, 0.001, 1, 1, 1, 1" />
+    <value val="1, 0, 0, 0.001, 0.001" />
 </parameter>
 
 <!-- Reduction workflow parameters under this line -->
diff --git a/qt/applications/workbench/CMakeLists.txt b/qt/applications/workbench/CMakeLists.txt
index bee3b9299ff6d234a5b0c446afb9d6b86a8594e6..06d9cc289c37d07e54e47819fe4cebf305c2a2fa 100644
--- a/qt/applications/workbench/CMakeLists.txt
+++ b/qt/applications/workbench/CMakeLists.txt
@@ -8,6 +8,8 @@ endif()
 add_python_package(workbench
   EXECUTABLE
   EXCLUDE_FROM_INSTALL ${_exclude_on_install}
+  INSTALL_LIB_DIRS "${WORKBENCH_LIB_DIR}"
+  INSTALL_BIN_DIR "${WORKBENCH_BIN_DIR}"
 )
 
 set(_images_qrc_file ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
@@ -32,7 +34,7 @@ if (WIN32)
 
     # Write resources.qrc.BUILD_TYPE
     file (WRITE ${_paths_qrc_file} ${_qrc_file_contents})
-  
+
     # Add command to generate resources.py file
     add_custom_command(OUTPUT ${_output_res_py}
                       COMMAND ${PYRCC5_CMD} -o ${_output_res_py} ${_images_qrc_file} ${_paths_qrc_file}
diff --git a/qt/applications/workbench/make_package.rb.in b/qt/applications/workbench/make_package.rb.in
index a535fc6061344a937bc92d475cacd3829dfacaec..e2ccb6f3e121cf41fabe6394e18ce60e7496b243 100755
--- a/qt/applications/workbench/make_package.rb.in
+++ b/qt/applications/workbench/make_package.rb.in
@@ -348,11 +348,13 @@ if !File.exist?(mpltoolkit_init)
   `touch #{mpltoolkit_init}`
 end
 
-files = ["gnureadline.so","cycler.py","readline.py","pyparsing.py","mistune.py","decorator.py","kiwisolver.so","subprocess32.py","six.py"]
+files = ["cycler.py","readline.py","pyparsing.py","mistune.py","decorator.py","kiwisolver.so","subprocess32.py","six.py"]
 files.each do |file|
   copyFile("#{pip_site_packages}/#{file}")
 end
 
+# gnureadline is only present on older versions of readline
+copyOptionalFile("#{pip_site_packages}/gnureadline.so")
 # mistune.so isn't present in v0.7
 copyOptionalFile("#{pip_site_packages}/mistune.so")
 
diff --git a/qt/icons/CMakeLists.txt b/qt/icons/CMakeLists.txt
index 9bdf901d4be5227e1012268f6ea4adbaacb64886..79078c905fc4c55bbff5024b2f181cd1178299dd 100644
--- a/qt/icons/CMakeLists.txt
+++ b/qt/icons/CMakeLists.txt
@@ -47,7 +47,6 @@ if(ENABLE_WORKBENCH)
                         ${Boost_LIBRARIES}
                         ${JSONCPP_LIBRARIES}
                       INSTALL_DIR
-                        ${LIB_DIR}
                         ${WORKBENCH_LIB_DIR}
                       OSX_INSTALL_RPATH
                         @loader_path/../MacOS
diff --git a/qt/paraview_ext/VatesAlgorithms/CMakeLists.txt b/qt/paraview_ext/VatesAlgorithms/CMakeLists.txt
index 6878f0077e29279bdec632d1eb5ab1940c5891ee..2dfdc6ca73dea6d64e96c3944760de45c4e74800 100644
--- a/qt/paraview_ext/VatesAlgorithms/CMakeLists.txt
+++ b/qt/paraview_ext/VatesAlgorithms/CMakeLists.txt
@@ -51,7 +51,7 @@ if(OSX_VERSION VERSION_GREATER 10.8)
   set_target_properties(
     VatesAlgorithms
     PROPERTIES INSTALL_RPATH
-               "@loader_path/../Contents/MacOS;@loader_path/../Libraries")
+               "@loader_path/../Contents/MacOS;@loader_path/../Contents/Libraries")
 elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
   set_target_properties(
     VatesAlgorithms
diff --git a/qt/python/CMakeLists.txt b/qt/python/CMakeLists.txt
index 58029954fea914ec301930cd76ec0bef74e55ed7..8aabf85e81974b1f882a997ad0fc40b20edcc4cb 100644
--- a/qt/python/CMakeLists.txt
+++ b/qt/python/CMakeLists.txt
@@ -9,7 +9,7 @@ if(ENABLE_MANTIDPLOT)
 endif()
 
 # mantidqt
-if(ENABLE_WORKBENCH)
+if(ENABLE_WORKBENCH OR ENABLE_WORKBENCH)
   # The default value is just an empty string - this prevents a Python syntax
   # error when not on Windows Configure utils.qt.plugins file for build. It is
   # placed in the source directory and added to the .gitignore for simplicity.
@@ -21,7 +21,13 @@ if(ENABLE_WORKBENCH)
   # plugins
   configure_file(mantidqt/utils/qt/plugins.py.in
                  ${CMAKE_CURRENT_SOURCE_DIR}/mantidqt/utils/qt/plugins.py)
-  add_python_package(mantidqt)
+  if(APPLE)
+    set(_install_lib_dirs "${LIB_DIR};${WORKBENCH_LIB_DIR}")
+  else()
+    set(_install_lib_dirs "${WORKBENCH_LIB_DIR}")
+  endif()
+  add_python_package(mantidqt
+    INSTALL_LIB_DIRS ${_install_lib_dirs})
 
   # Configure resources data in place for ease of development. The output file
   # is added to the toplevel gitignore
@@ -40,23 +46,25 @@ if(ENABLE_WORKBENCH)
   # Setup dependency chain
   add_dependencies(mantidqt
                    mantidqt_resources
-                   mantidqt_commonqt5
-                   mantidqt_instrumentviewqt5
-                   mantidqt_iconsqt5
                    PythonInterface)
 
   if(ENABLE_MANTIDPLOT)
     add_dependencies(mantidqt mantidqt_commonqt4 mantidqt_iconsqt4)
   endif()
-
-  if(MSVC)
-    # Debug builds need libraries that are linked with MSVC debug runtime
-    add_custom_command(
-      TARGET mantidqt POST_BUILD
-      COMMAND if 1==$<CONFIG:Debug> ${CMAKE_COMMAND} -E copy_directory
-              ${PYTHON_DIR}/msvc-site-packages/debug/PyQt5
-              ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}/PyQt5
-      COMMENT "Copying debug PyQt5 to bin/Debug")
+  if(ENABLE_WORKBENCH)
+    add_dependencies(mantidqt
+                     mantidqt_commonqt5
+                     mantidqt_instrumentviewqt5
+                     mantidqt_iconsqt5)
+    if(MSVC)
+      # Debug builds need libraries that are linked with MSVC debug runtime
+      add_custom_command(
+        TARGET mantidqt POST_BUILD
+        COMMAND if 1==$<CONFIG:Debug> ${CMAKE_COMMAND} -E copy_directory
+                ${PYTHON_DIR}/msvc-site-packages/debug/PyQt5
+                ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}/PyQt5
+        COMMENT "Copying debug PyQt5 to bin/Debug")
+    endif()
   endif()
 
   # Testing
@@ -102,53 +110,57 @@ if(ENABLE_WORKBENCH)
     mantidqt/widgets/embedded_find_replace_dialog/test/test_embedded_find_replace_dialog_presenter.py
     )
 
-  # ctest target for widgets that only get tested in qt5, because they are only
-  # used in the workbench
-  set(
-    PYTHON_WIDGET_QT5_ONLY_TESTS
-    mantidqt/widgets/algorithmselector/test/observer_test.py
-    mantidqt/widgets/algorithmselector/test/test_algorithmselector.py
-    mantidqt/widgets/codeeditor/test/test_interpreter_view.py
-    mantidqt/widgets/codeeditor/test/test_multifileinterpreter.py
-    mantidqt/widgets/codeeditor/test/test_multifileinterpreter_view.py
-    mantidqt/widgets/codeeditor/tab_widget/test/test_codeeditor_tab_presenter.py
-    mantidqt/widgets/codeeditor/tab_widget/test/test_codeeditor_tab_view.py
-    mantidqt/widgets/instrumentview/test/test_instrumentview_io.py
-    mantidqt/widgets/instrumentview/test/test_instrumentview_view.py
-    mantidqt/widgets/plotconfigdialog/axestabwidget/test/test_axestabwidgetpresenter.py
-    mantidqt/widgets/plotconfigdialog/curvestabwidget/test/test_curveproperties.py
-    mantidqt/widgets/plotconfigdialog/curvestabwidget/test/test_curvestabwidgetpresenter.py
-    mantidqt/widgets/plotconfigdialog/test/test_apply_all_properties.py
-    mantidqt/widgets/plotconfigdialog/imagestabwidget/test/test_imagestabwidgetpresenter.py
-    mantidqt/widgets/plotconfigdialog/test/test_plotconfigdialogpresenter.py
-    mantidqt/widgets/samplelogs/test/test_samplelogs_view.py
-    mantidqt/widgets/test/test_jupyterconsole.py
-    mantidqt/widgets/workspacedisplay/test/test_data_copier.py
-    mantidqt/widgets/workspacedisplay/test/test_user_notifier.py
-    mantidqt/widgets/workspacedisplay/matrix/test/test_matrixworkspacedisplay_model.py
-    mantidqt/widgets/workspacedisplay/matrix/test/test_matrixworkspacedisplay_presenter.py
-    mantidqt/widgets/workspacedisplay/matrix/test/test_matrixworkspacedisplay_table_view_model.py
-    mantidqt/widgets/workspacedisplay/matrix/test/test_matrixworkspacedisplay_view.py
-    mantidqt/widgets/workspacedisplay/matrix/test/test_matrixworkspacedisplay_io.py
-    mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_error_column.py
-    mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_marked_columns.py
-    mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_workbench_table_widget_item.py
-    mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_model.py
-    mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_presenter.py
-    mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_view.py
-    mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_io.py
-    mantidqt/widgets/workspacewidget/test/test_workspacetreewidget.py
-    mantidqt/icons/test/test_icons.py
-)
+  if(ENABLE_WORKBENCH)
+    # ctest target for widgets that only get tested in qt5, because they are only
+    # used in the workbench
+    set(
+      PYTHON_WIDGET_QT5_ONLY_TESTS
+      mantidqt/widgets/algorithmselector/test/observer_test.py
+      mantidqt/widgets/algorithmselector/test/test_algorithmselector.py
+      mantidqt/widgets/codeeditor/test/test_interpreter_view.py
+      mantidqt/widgets/codeeditor/test/test_multifileinterpreter.py
+      mantidqt/widgets/codeeditor/test/test_multifileinterpreter_view.py
+      mantidqt/widgets/codeeditor/tab_widget/test/test_codeeditor_tab_presenter.py
+      mantidqt/widgets/codeeditor/tab_widget/test/test_codeeditor_tab_view.py
+      mantidqt/widgets/instrumentview/test/test_instrumentview_io.py
+      mantidqt/widgets/instrumentview/test/test_instrumentview_view.py
+      mantidqt/widgets/plotconfigdialog/axestabwidget/test/test_axestabwidgetpresenter.py
+      mantidqt/widgets/plotconfigdialog/curvestabwidget/test/test_curveproperties.py
+      mantidqt/widgets/plotconfigdialog/curvestabwidget/test/test_curvestabwidgetpresenter.py
+      mantidqt/widgets/plotconfigdialog/test/test_apply_all_properties.py
+      mantidqt/widgets/plotconfigdialog/imagestabwidget/test/test_imagestabwidgetpresenter.py
+      mantidqt/widgets/plotconfigdialog/test/test_plotconfigdialogpresenter.py
+      mantidqt/widgets/samplelogs/test/test_samplelogs_view.py
+      mantidqt/widgets/test/test_jupyterconsole.py
+      mantidqt/widgets/workspacedisplay/test/test_data_copier.py
+      mantidqt/widgets/workspacedisplay/test/test_user_notifier.py
+      mantidqt/widgets/workspacedisplay/matrix/test/test_matrixworkspacedisplay_model.py
+      mantidqt/widgets/workspacedisplay/matrix/test/test_matrixworkspacedisplay_presenter.py
+      mantidqt/widgets/workspacedisplay/matrix/test/test_matrixworkspacedisplay_table_view_model.py
+      mantidqt/widgets/workspacedisplay/matrix/test/test_matrixworkspacedisplay_view.py
+      mantidqt/widgets/workspacedisplay/matrix/test/test_matrixworkspacedisplay_io.py
+      mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_error_column.py
+      mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_marked_columns.py
+      mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_workbench_table_widget_item.py
+      mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_model.py
+      mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_presenter.py
+      mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_view.py
+      mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_io.py
+      mantidqt/widgets/workspacewidget/test/test_workspacetreewidget.py
+      mantidqt/icons/test/test_icons.py
+  )
 
-  # Tests
-  set(PYUNITTEST_QT_API pyqt5)
-  pyunittest_add_test(${CMAKE_CURRENT_SOURCE_DIR}
-                      mantidqt_qt5
-                      ${PYTHON_TEST_FILES}
-                      ${PYTHON_WIDGET_QT5_ONLY_TESTS})
-  set(PYUNITTEST_QT_API pyqt)
-  pyunittest_add_test(${CMAKE_CURRENT_SOURCE_DIR} mantidqt_qt4
-                      ${PYTHON_TEST_FILES})
-  unset(PYUNITTEST_QT_API)
+    # Tests
+    set(PYUNITTEST_QT_API pyqt5)
+    pyunittest_add_test(${CMAKE_CURRENT_SOURCE_DIR}
+                        mantidqt_qt5
+                        ${PYTHON_TEST_FILES}
+                        ${PYTHON_WIDGET_QT5_ONLY_TESTS})
+  endif()
+  if(ENABLE_MANTIDPLOT)
+    set(PYUNITTEST_QT_API pyqt)
+    pyunittest_add_test(${CMAKE_CURRENT_SOURCE_DIR} mantidqt_qt4
+                        ${PYTHON_TEST_FILES})
+    unset(PYUNITTEST_QT_API)
+  endif()
 endif()
diff --git a/qt/python/mantidqt/project/project.py b/qt/python/mantidqt/project/project.py
index 8785af708e350d7b8ac8c427a5e7944dd8bb629d..e50b60d138dc9ac8fd213792f57d529b1de41df9 100644
--- a/qt/python/mantidqt/project/project.py
+++ b/qt/python/mantidqt/project/project.py
@@ -10,8 +10,7 @@ from __future__ import (absolute_import, division, print_function, unicode_liter
 
 import os
 
-from qtpy.QtWidgets import QFileDialog, QMessageBox
-from qtpy.QtWidgets import QApplication
+from qtpy.QtWidgets import QApplication, QFileDialog, QMessageBox
 
 from mantid.api import AnalysisDataService, AnalysisDataServiceObserver
 from mantid.kernel import ConfigService
@@ -52,19 +51,18 @@ class Project(AnalysisDataServiceObserver):
     def load_settings_from_config(self, config):
         self.prompt_save_on_close = config.get('project', 'prompt_save_on_close')
 
-    def __get_saved(self):
+    @property
+    def saved(self):
         return self.__saved
 
-    def __get_is_saving(self):
+    @property
+    def is_saving(self):
         return self.__is_saving
 
-    def __get_is_loading(self):
+    @property
+    def is_loading(self):
         return self.__is_loading
 
-    saved = property(__get_saved)
-    is_saving = property(__get_is_saving)
-    is_loading = property(__get_is_loading)
-
     def save(self):
         """
         The function that is called if the save button is clicked on the mainwindow
@@ -119,27 +117,29 @@ class Project(AnalysisDataServiceObserver):
 
     def _save(self):
         self.__is_saving = True
-        workspaces_to_save = AnalysisDataService.getObjectNames()
-        # Calculate the size of the workspaces in the project.
-        project_size = self._get_project_size(workspaces_to_save)
-        warning_size = int(ConfigService.getString("projectSaving.warningSize"))
-        # If a project is > the value in the properties file, question the user if they want to continue.
-        result = None
-        if project_size > warning_size:
-            result = self._offer_large_size_confirmation()
-        if result is None or result != QMessageBox.Cancel:
-            plots_to_save = self.plot_gfm.figs
-            interfaces_to_save = self.interface_populating_function()
-            project_saver = ProjectSaver(self.project_file_ext)
-            project_saver.save_project(file_name=self.last_project_location, workspace_to_save=workspaces_to_save,
-                                       plots_to_save=plots_to_save, interfaces_to_save=interfaces_to_save)
-            self.__saved = True
-        self.__is_saving = False
+        try:
+            workspaces_to_save = AnalysisDataService.getObjectNames()
+            # Calculate the size of the workspaces in the project.
+            project_size = self._get_project_size(workspaces_to_save)
+            warning_size = int(ConfigService.getString("projectSaving.warningSize"))
+            # If a project is > the value in the properties file, question the user if they want to continue.
+            result = None
+            if project_size > warning_size:
+                result = self._offer_large_size_confirmation()
+            if result is None or result != QMessageBox.Cancel:
+                plots_to_save = self.plot_gfm.figs
+                interfaces_to_save = self.interface_populating_function()
+                project_saver = ProjectSaver(self.project_file_ext)
+                project_saver.save_project(file_name=self.last_project_location, workspace_to_save=workspaces_to_save,
+                                           plots_to_save=plots_to_save, interfaces_to_save=interfaces_to_save)
+                self.__saved = True
+        finally:
+            self.__is_saving = False
 
     @staticmethod
     def inform_user_not_possible():
         return QMessageBox().information(None, "That action is not possible!",
-                                         "You cannot at present exit workbench whilst it is saving or loading a "
+                                         "You cannot exit workbench whilst it is saving or loading a "
                                          "project")
 
     @staticmethod
@@ -155,22 +155,24 @@ class Project(AnalysisDataServiceObserver):
         :return: None; if the user cancelled.
         """
         self.__is_loading = True
-        file_name = self._load_file_dialog()
-        if file_name is None:
-            # Cancel close dialogs
-            return
+        try:
+            file_name = self._load_file_dialog()
+            if file_name is None:
+                # Cancel close dialogs
+                return
 
-        # Sanity check
-        _, file_ext = os.path.splitext(file_name)
+            # Sanity check
+            _, file_ext = os.path.splitext(file_name)
 
-        if file_ext != ".mtdproj":
-            QMessageBox.warning(None, "Wrong file type!", "Please select a valid project file", QMessageBox.Ok)
+            if file_ext != ".mtdproj":
+                QMessageBox.warning(None, "Wrong file type!", "Please select a valid project file", QMessageBox.Ok)
 
-        self._load(file_name)
+            self._load(file_name)
 
-        self.last_project_location = file_name
-        self.__saved = True
-        self.__is_loading = False
+            self.last_project_location = file_name
+            self.__saved = True
+        finally:
+            self.__is_loading = False
 
     def _load(self, file_name):
         project_loader = ProjectLoader(self.project_file_ext)
diff --git a/qt/python/mantidqt/project/test/test_project.py b/qt/python/mantidqt/project/test/test_project.py
index d395e4e847f57947d2c6400741a84545f2430ea8..81ff5ecbbd9e90181d3ef7ebf2ee817e2e796c6f 100644
--- a/qt/python/mantidqt/project/test/test_project.py
+++ b/qt/python/mantidqt/project/test/test_project.py
@@ -31,6 +31,10 @@ def fake_window_finding_function():
     return []
 
 
+def _raise(exception):
+    raise exception
+
+
 @start_qapplication
 class ProjectTest(unittest.TestCase):
     def setUp(self):
@@ -189,6 +193,31 @@ class ProjectTest(unittest.TestCase):
         self.project._save()
         self.assertEqual(self.project._offer_large_size_confirmation.call_count, 0)
 
+    def test_is_loading_is_False_after_error_thrown_during_load(self):
+        with mock.patch.object(self.project, '_load_file_dialog', lambda: _raise(IOError)):
+            try:
+                self.project.load()
+            except IOError:
+                pass
+        self.assertFalse(self.project.is_loading)
+
+    def test_is_loading_is_False_after_None_returned_from_load_dialog(self):
+        # None is returned from the load dialog when a user clicks Cancel
+        with mock.patch.object(self.project, '_load_file_dialog', lambda: None):
+            try:
+                self.project.load()
+            except IOError:
+                pass
+        self.assertFalse(self.project.is_loading)
+
+    def test_is_saving_is_False_if_error_thrown_during_save(self):
+        with mock.patch.object(self.project, '_get_project_size', lambda x: _raise(IOError)):
+            try:
+                self.project._save()
+            except IOError:
+                pass
+        self.assertFalse(self.project.is_saving)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/qt/python/mantidqt/widgets/plotconfigdialog/colorselector.py b/qt/python/mantidqt/widgets/plotconfigdialog/colorselector.py
index 1ab8f63bc8e0b743da72e53b673a929e1257dd54..28cd82e7ed12864867d3ea55e8b11ca229c2772c 100644
--- a/qt/python/mantidqt/widgets/plotconfigdialog/colorselector.py
+++ b/qt/python/mantidqt/widgets/plotconfigdialog/colorselector.py
@@ -9,7 +9,8 @@
 from __future__ import (absolute_import, unicode_literals)
 
 from matplotlib import colors, rcParams
-from qtpy.QtGui import QColor, QPalette
+from qtpy.QtCore import QRegExp
+from qtpy.QtGui import QColor, QPalette, QRegExpValidator
 from qtpy.QtWidgets import (QWidget, QLineEdit, QPushButton, QHBoxLayout,
                             QColorDialog)
 
@@ -42,7 +43,13 @@ class ColorSelector(QWidget):
         self.h_layout.setContentsMargins(0, 0, 0, 0)
 
         self.line_edit.setText(self.initial_color.name())
-        self.line_edit.setReadOnly(True)
+        self.prev_color = self.initial_color.name()
+
+        # Color input only allows valid hex codes.
+        re = QRegExp('^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$')
+        validator = ColorValidator(re, self.line_edit, self)
+        self.line_edit.setValidator(validator)
+
         self.button.setAutoFillBackground(True)
         self.button.setFlat(True)
         self.update_color_button()
@@ -50,6 +57,7 @@ class ColorSelector(QWidget):
         # Signals
         self.button.clicked.connect(self.launch_qcolor_dialog)
         self.line_edit.textChanged.connect(self.update_color_button)
+        self.line_edit.editingFinished.connect(self.convert_three_digit_hex_to_six)
 
     def get_color(self):
         return self.line_edit.text()
@@ -60,9 +68,15 @@ class ColorSelector(QWidget):
         color_dialog.colorSelected.connect(
             lambda: self.set_line_edit(color_dialog.selectedColor().name())
         )
+        color_dialog.accepted.connect(
+            lambda: self.set_prev_color(color_dialog.selectedColor().name())
+        )
         color_dialog.setModal(True)
         color_dialog.show()
 
+    def set_prev_color(self, color):
+        self.prev_color = color
+
     def set_color(self, color_hex):
         self.line_edit.setText(color_hex)
 
@@ -75,3 +89,26 @@ class ColorSelector(QWidget):
         palette.setColor(QPalette.Button, qcolor)
         self.button.setPalette(palette)
         self.button.update()
+
+    def convert_three_digit_hex_to_six(self):
+        color = self.get_color()
+
+        # If a 3-digit hex code is inputted, it is converted to 6 digits
+        # by duplicating each digit.
+        if len(color) == 4:
+            new = '#{}'.format(''.join(2 * c for c in color.lstrip('#')))
+            self.set_color(new)
+            color = new
+
+        self.prev_color = color
+
+
+class ColorValidator(QRegExpValidator):
+    def __init__(self, regexp, widget, color_selector):
+        QRegExpValidator.__init__(self, regexp, widget)
+        self.color_selector = color_selector
+
+    def fixup(self, text):
+        # If an invalid color is inputted, the field reverts back
+        # to the last valid color entered.
+        self.color_selector.set_color(self.color_selector.prev_color)
diff --git a/qt/python/mantidqt/widgets/plotconfigdialog/curvestabwidget/curves_tab.ui b/qt/python/mantidqt/widgets/plotconfigdialog/curvestabwidget/curves_tab.ui
index b69f46e7cc556981ed495cbf3cf74b1dc522f81f..65a7575955aee3eb15aa7408a1a5d64f36f9012d 100644
--- a/qt/python/mantidqt/widgets/plotconfigdialog/curvestabwidget/curves_tab.ui
+++ b/qt/python/mantidqt/widgets/plotconfigdialog/curvestabwidget/curves_tab.ui
@@ -95,7 +95,7 @@
        <property name="maximumSize">
         <size>
          <width>1500000</width>
-         <height>16777215</height>
+         <height>22</height>
         </size>
        </property>
        <property name="text">
diff --git a/qt/python/mantidqt/widgets/workspacedisplay/table/model.py b/qt/python/mantidqt/widgets/workspacedisplay/table/model.py
index 42f67bb0fb7b2bcc127dd6ef66c04691a9c53b55..afa6527db398bccbdc84aee92d48875d1f1c0808 100644
--- a/qt/python/mantidqt/widgets/workspacedisplay/table/model.py
+++ b/qt/python/mantidqt/widgets/workspacedisplay/table/model.py
@@ -146,3 +146,6 @@ class TableWorkspaceDisplayModel:
         else:
             SortTableWorkspace(InputWorkspace=self.ws, OutputWorkspace=self.ws, Columns=column_name,
                                Ascending=sort_ascending)
+
+    def set_column_type(self, col, type):
+        self.ws.setPlotType(col, type)
diff --git a/qt/python/mantidqt/widgets/workspacedisplay/table/presenter.py b/qt/python/mantidqt/widgets/workspacedisplay/table/presenter.py
index 2e40db369e3012e510f1808cff1862f6caab14b2..996bcc936ccbba2db46255748aee7731b18efb15 100644
--- a/qt/python/mantidqt/widgets/workspacedisplay/table/presenter.py
+++ b/qt/python/mantidqt/widgets/workspacedisplay/table/presenter.py
@@ -217,7 +217,7 @@ class TableWorkspaceDisplay(ObservingPresenter, DataCopier):
         for column_index in range(self.view.columnCount()):
             self.view.showColumn(column_index)
 
-    def _action_set_as(self, add_to_list_func):
+    def _action_set_as(self, add_to_list_func, type):
         try:
             selected_columns = self._get_selected_columns()
         except ValueError:
@@ -225,14 +225,15 @@ class TableWorkspaceDisplay(ObservingPresenter, DataCopier):
 
         for col in selected_columns:
             add_to_list_func(col)
+            self.model.set_column_type(col, type)
 
         self.update_column_headers()
 
     def action_set_as_x(self):
-        self._action_set_as(self.model.marked_columns.add_x)
+        self._action_set_as(self.model.marked_columns.add_x, 1)
 
     def action_set_as_y(self):
-        self._action_set_as(self.model.marked_columns.add_y)
+        self._action_set_as(self.model.marked_columns.add_y , 2)
 
     def action_set_as_y_err(self, related_y_column, label_index):
         """
@@ -253,10 +254,11 @@ class TableWorkspaceDisplay(ObservingPresenter, DataCopier):
             return
 
         self.model.marked_columns.add_y_err(err_column)
+        self.model.set_column_type(selected_column, 5)
         self.update_column_headers()
 
     def action_set_as_none(self):
-        self._action_set_as(self.model.marked_columns.remove)
+        self._action_set_as(self.model.marked_columns.remove, 0)
 
     def action_sort(self, sort_ascending):
         """
diff --git a/qt/python/mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_presenter.py b/qt/python/mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_presenter.py
index 9fe18964e9683979a1acb20d80a9cd598a2f8762..e7e93cf9301fd8b8b8625462922ae414581dea98 100644
--- a/qt/python/mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_presenter.py
+++ b/qt/python/mantidqt/widgets/workspacedisplay/table/test/test_tableworkspacedisplay_presenter.py
@@ -52,6 +52,7 @@ def with_mock_presenter(add_selection_model=False, add_plot=False):
     def real_decorator(func, *args, **kwargs):
         def wrapper(self, *args):
             ws = MockWorkspace()
+            ws.setPlotType = Mock()
             view = Mock(spec=TableWorkspaceDisplayView)
             container = Mock(spec=StatusBarView)
             container.status_bar = Mock(spec=QStatusBar)
@@ -250,7 +251,7 @@ class TableWorkspaceDisplayPresenterTest(unittest.TestCase):
     @with_mock_presenter(add_selection_model=True)
     def test_action_set_as(self, ws, view, twd):
         mock_func = Mock()
-        twd._action_set_as(mock_func)
+        twd._action_set_as(mock_func, 1)
 
         self.assertEqual(3, mock_func.call_count)
 
diff --git a/qt/scientific_interfaces/ISISReflectometry/Common/Clipboard.cpp b/qt/scientific_interfaces/ISISReflectometry/Common/Clipboard.cpp
index 74ad3f8ba46ec43d12bc503e230d170ad7bac055..063f0510660629ba759ebb475a4c5581e539f779 100644
--- a/qt/scientific_interfaces/ISISReflectometry/Common/Clipboard.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/Common/Clipboard.cpp
@@ -127,9 +127,9 @@ std::vector<boost::optional<Row>> Clipboard::createRowsForSubtree(
                    });
     auto validationResult = validateRow(cells);
     if (validationResult.isValid())
-      result.push_back(validationResult.assertValid());
+      result.emplace_back(validationResult.assertValid());
     else
-      result.push_back(boost::none);
+      result.emplace_back(boost::none);
   }
 
   return result;
diff --git a/qt/scientific_interfaces/ISISReflectometry/GUI/MainWindow/QtMainWindowView.cpp b/qt/scientific_interfaces/ISISReflectometry/GUI/MainWindow/QtMainWindowView.cpp
index 9db5f09b845415986a94b20bd617010d0f941a62..ae99ce4ac6464cb262f979a74e6a3bcf27978272 100644
--- a/qt/scientific_interfaces/ISISReflectometry/GUI/MainWindow/QtMainWindowView.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/GUI/MainWindow/QtMainWindowView.cpp
@@ -79,9 +79,8 @@ void QtMainWindowView::initLayout() {
       {{"INTER", "SURF", "CRISP", "POLREF", "OFFSPEC"}});
 
   auto thetaTolerance = 0.01;
-  auto pythonRunner = this;
 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
-  Plotter plotter(pythonRunner);
+  Plotter plotter(this);
 #else
   Plotter plotter;
 #endif
@@ -90,9 +89,9 @@ void QtMainWindowView::initLayout() {
 
   auto defaultInstrumentIndex = getDefaultInstrumentIndex(instruments);
   auto messageHandler = this;
-  auto makeRunsPresenter = RunsPresenterFactory(
-      std::move(makeRunsTablePresenter), thetaTolerance, instruments,
-      defaultInstrumentIndex, messageHandler, pythonRunner);
+  auto makeRunsPresenter =
+      RunsPresenterFactory(std::move(makeRunsTablePresenter), thetaTolerance,
+                           instruments, defaultInstrumentIndex, messageHandler);
 
   auto makeEventPresenter = EventPresenterFactory();
   auto makeSaveSettingsPresenter = SavePresenterFactory();
diff --git a/qt/scientific_interfaces/ISISReflectometry/GUI/Runs/RunsPresenterFactory.h b/qt/scientific_interfaces/ISISReflectometry/GUI/Runs/RunsPresenterFactory.h
index b22b1077b9d7aeb1f283ec9f4cc20157a8125d28..23a73f39ef08ee3e957c06ed81a905e4aced22f7 100644
--- a/qt/scientific_interfaces/ISISReflectometry/GUI/Runs/RunsPresenterFactory.h
+++ b/qt/scientific_interfaces/ISISReflectometry/GUI/Runs/RunsPresenterFactory.h
@@ -23,13 +23,12 @@ public:
   RunsPresenterFactory( // cppcheck-suppress passedByValue
       RunsTablePresenterFactory runsTablePresenterFactory,
       double thetaTolerance, std::vector<std::string> instruments,
-      int defaultInstrumentIndex, IMessageHandler *messageHandler,
-      IPythonRunner *pythonRunner)
+      int defaultInstrumentIndex, IMessageHandler *messageHandler)
       : m_runsTablePresenterFactory(std::move(runsTablePresenterFactory)),
         m_thetaTolerance(std::move(thetaTolerance)),
         m_instruments(std::move(instruments)),
         m_defaultInstrumentIndex(std::move(defaultInstrumentIndex)),
-        m_messageHandler(messageHandler), m_pythonRunner(pythonRunner) {}
+        m_messageHandler(messageHandler) {}
 
   std::unique_ptr<IRunsPresenter> make(IRunsView *view) {
     return std::make_unique<RunsPresenter>(
@@ -43,7 +42,6 @@ private:
   std::vector<std::string> m_instruments;
   int m_defaultInstrumentIndex;
   IMessageHandler *m_messageHandler;
-  IPythonRunner *m_pythonRunner;
 };
 } // namespace ISISReflectometry
 } // namespace CustomInterfaces
diff --git a/qt/scientific_interfaces/ISISReflectometry/Reduction/Group.cpp b/qt/scientific_interfaces/ISISReflectometry/Reduction/Group.cpp
index 15e260f84af3baa422ca127acf8271e57d99af2f..1e6273de06f7194c8e4a11445f23303e0f809412 100644
--- a/qt/scientific_interfaces/ISISReflectometry/Reduction/Group.cpp
+++ b/qt/scientific_interfaces/ISISReflectometry/Reduction/Group.cpp
@@ -64,10 +64,9 @@ bool Group::requiresPostprocessing(bool reprocessFailed) const {
     return false;
 
   // If all rows are valid and complete then we're ready to postprocess
-  return std::all_of(m_rows.cbegin(), m_rows.cend(),
-                     [&reprocessFailed](boost::optional<Row> const &row) {
-                       return row && row->success();
-                     });
+  return std::all_of(
+      m_rows.cbegin(), m_rows.cend(),
+      [](boost::optional<Row> const &row) { return row && row->success(); });
 }
 
 std::string Group::postprocessedWorkspaceName() const {
diff --git a/qt/scientific_interfaces/ISISReflectometry/Reduction/Item.h b/qt/scientific_interfaces/ISISReflectometry/Reduction/Item.h
index f02ffca58d985199ce05ddad7430a91af255cc53..3cbb2d03951c5333e69f094e6f8eaaa34e9a687f 100644
--- a/qt/scientific_interfaces/ISISReflectometry/Reduction/Item.h
+++ b/qt/scientific_interfaces/ISISReflectometry/Reduction/Item.h
@@ -26,6 +26,7 @@ public:
   using ItemCountFunction = int (Item::*)() const;
 
   Item();
+  virtual ~Item() = default;
 
   virtual bool isGroup() const = 0;
   State state() const;
diff --git a/qt/widgets/common/CMakeLists.txt b/qt/widgets/common/CMakeLists.txt
index 0e74772ec2174d43f416a17d59aa0165faddcbf3..89adaae96463bb7a65141b24e93cc7eb9548a44d 100644
--- a/qt/widgets/common/CMakeLists.txt
+++ b/qt/widgets/common/CMakeLists.txt
@@ -879,7 +879,6 @@ mtd_add_qt_library(TARGET_NAME
                    ${_webwidgets_tgt}
                    Qt5::Qscintilla
                    INSTALL_DIR
-                   ${LIB_DIR}
                    ${WORKBENCH_LIB_DIR}
                    OSX_INSTALL_RPATH
                    @loader_path/../MacOS
diff --git a/qt/widgets/instrumentview/CMakeLists.txt b/qt/widgets/instrumentview/CMakeLists.txt
index cc1c97c5dbe6a9b439190bd9350ba4f32dfd71e0..96d53de03f938261df2de799dc02495afc33b27c 100644
--- a/qt/widgets/instrumentview/CMakeLists.txt
+++ b/qt/widgets/instrumentview/CMakeLists.txt
@@ -200,7 +200,6 @@ mtd_add_qt_library(TARGET_NAME MantidQtWidgetsInstrumentView
                      MantidQtWidgetsCommon
                      MantidQtWidgetsMplCpp
                    INSTALL_DIR
-                     ${LIB_DIR}
                      ${WORKBENCH_LIB_DIR}
                    OSX_INSTALL_RPATH
                      @loader_path/../MacOS
diff --git a/qt/widgets/mplcpp/CMakeLists.txt b/qt/widgets/mplcpp/CMakeLists.txt
index 73519ae02fb5779e7491da198622c7adf3c128cd..400428a0f47dc316717ec29218e52ff7f084db0c 100644
--- a/qt/widgets/mplcpp/CMakeLists.txt
+++ b/qt/widgets/mplcpp/CMakeLists.txt
@@ -77,7 +77,6 @@ mtd_add_qt_library(
   MTD_QT_LINK_LIBS
     MantidQtWidgetsCommon
   INSTALL_DIR
-    ${LIB_DIR}
     ${WORKBENCH_LIB_DIR}
   OSX_INSTALL_RPATH
     @loader_path/../MacOS
diff --git a/qt/widgets/mplcpp/src/Figure.cpp b/qt/widgets/mplcpp/src/Figure.cpp
index 462e12873c593de07ec4866c1694db6bf8edfc74..5ce49fcd6dbbe57d9363c7aad3cc41be2d4cc353 100644
--- a/qt/widgets/mplcpp/src/Figure.cpp
+++ b/qt/widgets/mplcpp/src/Figure.cpp
@@ -122,7 +122,7 @@ Python::Object Figure::colorbar(const ScalarMappable &mappable, const Axes &cax,
       Py_BuildValue("(OO)", mappable.pyobj().ptr(), cax.pyobj().ptr()));
   const auto kwargs = Python::NewRef(
       Py_BuildValue("{sOsO}", "ticks", ticks.ptr(), "format", format.ptr()));
-  Python::Object attr{pyobj().attr("colorbar")};
+  Python::Object attr(pyobj().attr("colorbar"));
   return Python::NewRef(PyObject_Call(attr.ptr(), args.ptr(), kwargs.ptr()));
 }
 
diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt
index b096bbade445e91a238154e4abe250d7a1b6ff8e..c213cef13a5ce5458ad6a10d7d33b64589e41f20 100644
--- a/scripts/CMakeLists.txt
+++ b/scripts/CMakeLists.txt
@@ -86,6 +86,7 @@ install(FILES ${_scripts_pth_install}
 
 # Testing
 add_subdirectory(test)
+add_subdirectory(Diffraction/isis_powder)
 
 # Ensure we don't get stale pyc files around
 clean_orphaned_pyc_files(${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/scripts/Diffraction/isis_powder/CMakeLists.txt b/scripts/Diffraction/isis_powder/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..245d5f2ba81f11e00fb7470de6069336547a88a2
--- /dev/null
+++ b/scripts/Diffraction/isis_powder/CMakeLists.txt
@@ -0,0 +1,18 @@
+# Tests for ISIS Powder
+
+set(TEST_PY_FILES
+    test/ISISPowderAbsorptionTest.py
+    test/ISISPowderAbstractInstrumentTest.py
+    test/ISISPowderCommonTest.py
+    test/ISISPowderGemOutputTest.py
+    test/ISISPowderInstrumentSettingsTest.py
+    test/ISISPowderRunDetailsTest.py
+    test/ISISPowderSampleDetailsTest.py
+    test/ISISPowderYamlParserTest.py
+    test/ISISPowderFocusCropTest.py
+)
+
+check_tests_valid(${CMAKE_CURRENT_SOURCE_DIR} ${TEST_PY_FILES})
+
+pyunittest_add_test(${CMAKE_CURRENT_SOURCE_DIR} python.IsisPowder
+                    ${TEST_PY_FILES})
diff --git a/scripts/Diffraction/isis_powder/abstract_inst.py b/scripts/Diffraction/isis_powder/abstract_inst.py
index d5644f8b92507d51fd7893e74c040b2f387e6ba8..b5d0c81cd5e791f0e24256767236d0a94f8aed7d 100644
--- a/scripts/Diffraction/isis_powder/abstract_inst.py
+++ b/scripts/Diffraction/isis_powder/abstract_inst.py
@@ -8,7 +8,8 @@ from __future__ import (absolute_import, division, print_function)
 
 import os
 from isis_powder.routines import calibrate, focus, common, common_enums, common_output
-
+from mantid.kernel import config, logger
+from six import iteritems
 
 # This class provides common hooks for instruments to override
 # if they want to define the behaviour of the hook. Otherwise it
@@ -19,6 +20,7 @@ from isis_powder.routines import calibrate, focus, common, common_enums, common_
 # to denote internal methods to abstract_inst we will use '_abs_' to denote it as a
 # private method for the scripts
 
+
 class AbstractInst(object):
     def __init__(self, user_name, calibration_dir, output_dir, inst_prefix):
         # ----- Properties common to ALL instruments -------- #
@@ -27,6 +29,13 @@ class AbstractInst(object):
         self._user_name = user_name
         self._calibration_dir = calibration_dir
         self._inst_prefix = inst_prefix
+        try:
+            self._inst_prefix_short = config.getInstrument(inst_prefix).shortName()
+        except RuntimeError:
+            logger.warning(
+                "Unknown instrument {}. Setting short prefix equal to full prefix".format(
+                    inst_prefix))
+            self._inst_prefix_short = inst_prefix
         self._output_dir = output_dir
         self._is_vanadium = None
         self._beam_parameters = None
@@ -53,10 +62,15 @@ class AbstractInst(object):
         """
         self._is_vanadium = True
         run_details = self._get_run_details(run_number_string)
-        return calibrate.create_van(instrument=self, run_details=run_details,
+        return calibrate.create_van(instrument=self,
+                                    run_details=run_details,
                                     absorb=do_absorb_corrections)
 
-    def _focus(self, run_number_string, do_van_normalisation, do_absorb_corrections, sample_details=None):
+    def _focus(self,
+               run_number_string,
+               do_van_normalisation,
+               do_absorb_corrections,
+               sample_details=None):
         """
         Focuses the user specified run - should be called by the concrete instrument
         :param run_number_string: The run number(s) to be processed
@@ -64,8 +78,11 @@ class AbstractInst(object):
         :return:
         """
         self._is_vanadium = False
-        return focus.focus(run_number_string=run_number_string, perform_vanadium_norm=do_van_normalisation,
-                           instrument=self, absorb=do_absorb_corrections, sample_details=sample_details)
+        return focus.focus(run_number_string=run_number_string,
+                           perform_vanadium_norm=do_van_normalisation,
+                           instrument=self,
+                           absorb=do_absorb_corrections,
+                           sample_details=sample_details)
 
     def mask_prompt_pulses_if_necessary(self, ws_list):
         """
@@ -86,12 +103,11 @@ class AbstractInst(object):
             height = float(height)
             width = float(width)
         except ValueError:
-                raise ValueError("Beam height and width must be numbers.")
+            raise ValueError("Beam height and width must be numbers.")
         if height <= 0 or width <= 0:
             raise ValueError("Beam height and width must be more than 0.")
         else:
-            self._beam_parameters = {'height': height,
-                                     'width': width}
+            self._beam_parameters = {'height': height, 'width': width}
 
     def should_subtract_empty_inst(self):
         """
@@ -109,13 +125,14 @@ class AbstractInst(object):
         """
         raise NotImplementedError("get_run_details must be implemented per instrument")
 
-    def _generate_input_file_name(self, run_number):
+    def _generate_input_file_name(self, run_number, file_ext=None):
         """
         Generates a name which Mantid uses within Load to find the file.
         :param run_number: The run number to convert into a valid format for Mantid
+        :param file_ext: An optional file extension to add to force a particular format
         :return: A filename that will allow Mantid to find the correct run for that instrument.
         """
-        return self._generate_inst_filename(run_number=run_number)
+        return self._generate_inst_filename(run_number=run_number, file_ext=file_ext)
 
     def _apply_absorb_corrections(self, run_details, ws_to_correct):
         """
@@ -124,7 +141,8 @@ class AbstractInst(object):
                 :param ws_to_correct: A reference vanadium workspace to match the binning of or correct
                 :return: A workspace containing the corrections
                 """
-        raise NotImplementedError("apply_absorb_corrections Not implemented for this instrument yet")
+        raise NotImplementedError(
+            "apply_absorb_corrections Not implemented for this instrument yet")
 
     def _generate_output_file_name(self, run_number_string):
         """
@@ -236,16 +254,12 @@ class AbstractInst(object):
         :param output_mode: Optional - Sets additional saving/grouping behaviour depending on the instrument
         :return: d-spacing group of the processed output workspaces
         """
-        d_spacing_group, tof_group = common_output.split_into_tof_d_spacing_groups(run_details=run_details,
-                                                                                   processed_spectra=processed_spectra)
-        output_paths = self._generate_out_file_paths(run_details=run_details)
-
-        file_ext = run_details.file_extension[1:] if run_details.file_extension else ""
-
-        common_output.save_focused_data(d_spacing_group=d_spacing_group, tof_group=tof_group,
-                                        output_paths=output_paths, inst_prefix=self._inst_prefix,
-                                        run_number_string=run_details.output_run_string,
-                                        file_ext=file_ext)
+        d_spacing_group, tof_group = common_output.split_into_tof_d_spacing_groups(
+            run_details=run_details, processed_spectra=processed_spectra)
+        common_output.save_focused_data(
+            d_spacing_group=d_spacing_group,
+            tof_group=tof_group,
+            output_paths=self._generate_out_file_paths(run_details=run_details))
 
         return d_spacing_group, tof_group
 
@@ -283,31 +297,56 @@ class AbstractInst(object):
         """
         output_directory = os.path.join(self._output_dir, run_details.label, self._user_name)
         output_directory = os.path.abspath(os.path.expanduser(output_directory))
-        file_name = str(self._generate_output_file_name(run_number_string=run_details.output_run_string))
-        # Prepend the file extension used if it was set, this groups the files nicely in the file browser
-        # Also remove the dot at the start so we don't make hidden files in *nix systems
-        file_name = run_details.file_extension[1:] + file_name if run_details.file_extension else file_name
-        file_name += run_details.output_suffix if run_details.output_suffix is not None else ""
-
-        nxs_file = os.path.join(output_directory, (file_name + ".nxs"))
-        gss_file = os.path.join(output_directory, (file_name + ".gsas"))
-        tof_xye_file = os.path.join(output_directory, (file_name + "_tof_xye.dat"))
-        d_xye_file = os.path.join(output_directory, (file_name + "_d_xye.dat"))
-        out_name = file_name
-
-        out_file_names = {"nxs_filename": nxs_file,
-                          "gss_filename": gss_file,
-                          "tof_xye_filename": tof_xye_file,
-                          "dspacing_xye_filename": d_xye_file,
-                          "output_name": out_name,
-                          "output_folder": output_directory}
-
+        dat_files_directory = output_directory
+        if self._inst_settings.dat_files_directory:
+            dat_files_directory = os.path.join(output_directory,
+                                               self._inst_settings.dat_files_directory)
+
+        file_type = "" if run_details.file_extension is None else run_details.file_extension.lstrip(
+            ".")
+        out_file_names = {"output_folder": output_directory}
+        format_options = {
+            "inst": self._inst_prefix,
+            "instlow": self._inst_prefix.lower(),
+            "instshort": self._inst_prefix_short,
+            "runno": run_details.output_run_string,
+            "fileext": file_type,
+            "_fileext": "_" + file_type if file_type else "",
+            "suffix": run_details.output_suffix if run_details.output_suffix else ""
+        }
+        format_options = self._add_formatting_options(format_options)
+
+        output_formats = {
+            "nxs_filename": output_directory,
+            "gss_filename": output_directory,
+            "tof_xye_filename": dat_files_directory,
+            "dspacing_xye_filename": dat_files_directory
+        }
+        for key, output_dir in iteritems(output_formats):
+            filepath = os.path.join(output_dir,
+                                    getattr(self._inst_settings, key).format(**format_options))
+            out_file_names[key] = filepath
+
+        out_file_names['output_name'] = os.path.splitext(
+            os.path.basename(out_file_names['nxs_filename']))[0]
         return out_file_names
 
-    def _generate_inst_filename(self, run_number):
+    def _generate_inst_filename(self, run_number, file_ext):
         if isinstance(run_number, list):
             # Multiple entries
-            return [self._generate_inst_filename(run) for run in run_number]
+            return [self._generate_inst_filename(run, file_ext) for run in run_number]
         else:
             # Individual entry
-            return self._inst_prefix + str(run_number)
+            runfile = self._inst_prefix + str(run_number)
+            if file_ext is not None:
+                runfile += file_ext
+            return runfile
+
+    def _add_formatting_options(self, format_options):
+        """
+        Add any instrument-specific format options to the given
+        list
+        :param format_options: A dictionary of string format keys mapped to their expansions
+        :return: format_options as it is passed in
+        """
+        return format_options
diff --git a/scripts/Diffraction/isis_powder/gem_routines/gem_advanced_config.py b/scripts/Diffraction/isis_powder/gem_routines/gem_advanced_config.py
index 7e59abb1ba6e4546208579d5e36dad697f15bd86..3f6ed9c86d7aad4bf8c9039686aee89b6a3d1cf2 100644
--- a/scripts/Diffraction/isis_powder/gem_routines/gem_advanced_config.py
+++ b/scripts/Diffraction/isis_powder/gem_routines/gem_advanced_config.py
@@ -6,6 +6,11 @@
 # SPDX - License - Identifier: GPL - 3.0 +
 from __future__ import (absolute_import, division, print_function)
 
+import copy
+
+from isis_powder.routines.common import ADVANCED_CONFIG as COMMON_ADVANCED_CONFIG
+
+
 absorption_correction_params = {
     # These are read directly by the generate absorb corrections functions instead of being parsed.
     # Therefore they cannot be overridden using basic config files or keyword arguments.
@@ -130,7 +135,7 @@ calibration_params = {
 all_adv_variables = {
     "gsas_calib_filename": "GEM_PF1_PROFILE.IPF",
     "maud_grouping_scheme": [1] * 3 + [2] * 8 + [3] * 20 + [4] * 42 + [5] * 52 + [6] * 35,
-    "raw_tof_cropping_values": gem_adv_config_params
+    "raw_tof_cropping_values": gem_adv_config_params,
 }
 
 
@@ -145,7 +150,9 @@ def get_mode_specific_variables(is_texture_mode, is_save_all):
 
 
 def get_all_adv_variables():
-    return all_adv_variables
+    advanced = copy.copy(COMMON_ADVANCED_CONFIG)
+    advanced.update(all_adv_variables)
+    return advanced
 
 
 def get_calibration_variables():
diff --git a/scripts/Diffraction/isis_powder/gem_routines/gem_param_mapping.py b/scripts/Diffraction/isis_powder/gem_routines/gem_param_mapping.py
index b0aa3cd085cce00bde2f5443853e910733d99562..ef80de36018b7a707fc3b11ba2ad09f9d0500da0 100644
--- a/scripts/Diffraction/isis_powder/gem_routines/gem_param_mapping.py
+++ b/scripts/Diffraction/isis_powder/gem_routines/gem_param_mapping.py
@@ -8,55 +8,57 @@ from __future__ import (absolute_import, division, print_function)
 
 from isis_powder.routines.param_map_entry import ParamMapEntry
 from isis_powder.gem_routines.gem_enums import GEM_CHOPPER_MODES
+from isis_powder.routines.common import PARAM_MAPPING as COMMON_PARAM_MAPPING
 from isis_powder.routines.common_enums import INPUT_BATCHING, WORKSPACE_UNITS
 
 #                 Maps friendly user name (ext_name) -> script name (int_name)
-attr_mapping = \
-    [
-     ParamMapEntry(ext_name="calibration_to_adjust",     int_name="cal_adjust", optional=True),
-     ParamMapEntry(ext_name="calibration_directory",     int_name="calibration_dir"),
-     ParamMapEntry(ext_name="calibration_mapping_file",  int_name="cal_mapping_path"),
-     ParamMapEntry(ext_name="config_file",               int_name="config_file"),
-
-     ParamMapEntry(ext_name="create_cal_rebin_1_params", int_name="cal_rebin_1"),
-     ParamMapEntry(ext_name="create_cal_rebin_2_params", int_name="cal_rebin_2"),
-     ParamMapEntry(ext_name="cross_corr_reference_spectra", int_name="reference_spectra"),
-     ParamMapEntry(ext_name="cross_corr_ws_index_max", int_name="cross_corr_ws_max"),
-     ParamMapEntry(ext_name="cross_corr_ws_index_min", int_name="cross_corr_ws_min"),
-     ParamMapEntry(ext_name="cross_corr_x_min", int_name="cross_corr_x_min"),
-     ParamMapEntry(ext_name="cross_corr_x_max", int_name="cross_corr_x_max"),
-
-     ParamMapEntry(ext_name="get_det_offsets_d_ref", int_name="d_reference"),
-     ParamMapEntry(ext_name="get_det_offsets_step", int_name="get_det_offsets_step"),
-     ParamMapEntry(ext_name="get_det_offsets_x_min", int_name="get_det_offsets_x_min"),
-     ParamMapEntry(ext_name="get_det_offsets_x_max", int_name="get_det_offsets_x_max"),
-
-     ParamMapEntry(ext_name="do_absorb_corrections",     int_name="do_absorb_corrections"),
-     ParamMapEntry(ext_name="file_ext",                  int_name="file_extension", optional=True),
-     ParamMapEntry(ext_name="first_cycle_run_no",        int_name="run_in_range"),
-     ParamMapEntry(ext_name="focused_cropping_values",   int_name="focused_cropping_values"),
-     ParamMapEntry(ext_name="grouping_file_name",        int_name="grouping_file_name"),
-     ParamMapEntry(ext_name="gsas_calib_filename",       int_name="gsas_calib_filename"),
-     ParamMapEntry(ext_name="input_mode",                int_name="input_batching", enum_class=INPUT_BATCHING),
-     ParamMapEntry(ext_name="maud_grouping_scheme",      int_name="maud_grouping_scheme"),
-     ParamMapEntry(ext_name="mode",                      int_name="mode",           enum_class=GEM_CHOPPER_MODES),
-     ParamMapEntry(ext_name="multiple_scattering",       int_name="multiple_scattering"),
-     ParamMapEntry(ext_name="raw_tof_cropping_values",   int_name="raw_tof_cropping_values"),
-     ParamMapEntry(ext_name="run_number",                int_name="run_number"),
-     ParamMapEntry(ext_name="sample_empty",              int_name="sample_empty",   optional=True),
-     ParamMapEntry(ext_name="sample_empty_scale",        int_name="sample_empty_scale"),
-     ParamMapEntry(ext_name="save_all",                  int_name="save_all"),
-     ParamMapEntry(ext_name="save_gda",                  int_name="save_gda"),
-     ParamMapEntry(ext_name="save_maud_calib",           int_name="save_maud_calib"),
-     ParamMapEntry(ext_name="save_maud",                 int_name="save_maud"),
-     ParamMapEntry(ext_name="spline_coefficient",        int_name="spline_coeff"),
-     ParamMapEntry(ext_name="subtract_empty_instrument", int_name="subtract_empty_inst", optional =True),
-     ParamMapEntry(ext_name="suffix",                    int_name="suffix",         optional=True),
-     ParamMapEntry(ext_name="texture_mode",              int_name="texture_mode",         optional=True),
-     ParamMapEntry(ext_name="output_directory",          int_name="output_dir"),
-     ParamMapEntry(ext_name="unit_to_keep",              int_name="unit_to_keep",
-                   enum_class=WORKSPACE_UNITS,           optional=True),
-     ParamMapEntry(ext_name="user_name",                 int_name="user_name"),
-     ParamMapEntry(ext_name="vanadium_cropping_values",  int_name="vanadium_cropping_values"),
-     ParamMapEntry(ext_name="vanadium_normalisation",    int_name="do_van_norm")
-    ]
+attr_mapping = [
+    ParamMapEntry(ext_name="calibration_to_adjust", int_name="cal_adjust", optional=True),
+    ParamMapEntry(ext_name="calibration_directory", int_name="calibration_dir"),
+    ParamMapEntry(ext_name="calibration_mapping_file", int_name="cal_mapping_path"),
+    ParamMapEntry(ext_name="config_file", int_name="config_file"),
+    ParamMapEntry(ext_name="create_cal_rebin_1_params", int_name="cal_rebin_1"),
+    ParamMapEntry(ext_name="create_cal_rebin_2_params", int_name="cal_rebin_2"),
+    ParamMapEntry(ext_name="cross_corr_reference_spectra", int_name="reference_spectra"),
+    ParamMapEntry(ext_name="cross_corr_ws_index_max", int_name="cross_corr_ws_max"),
+    ParamMapEntry(ext_name="cross_corr_ws_index_min", int_name="cross_corr_ws_min"),
+    ParamMapEntry(ext_name="cross_corr_x_min", int_name="cross_corr_x_min"),
+    ParamMapEntry(ext_name="cross_corr_x_max", int_name="cross_corr_x_max"),
+    ParamMapEntry(ext_name="get_det_offsets_d_ref", int_name="d_reference"),
+    ParamMapEntry(ext_name="get_det_offsets_step", int_name="get_det_offsets_step"),
+    ParamMapEntry(ext_name="get_det_offsets_x_min", int_name="get_det_offsets_x_min"),
+    ParamMapEntry(ext_name="get_det_offsets_x_max", int_name="get_det_offsets_x_max"),
+    ParamMapEntry(ext_name="do_absorb_corrections", int_name="do_absorb_corrections"),
+    ParamMapEntry(ext_name="file_ext", int_name="file_extension", optional=True),
+    ParamMapEntry(ext_name="first_cycle_run_no", int_name="run_in_range"),
+    ParamMapEntry(ext_name="focused_cropping_values", int_name="focused_cropping_values"),
+    ParamMapEntry(ext_name="grouping_file_name", int_name="grouping_file_name"),
+    ParamMapEntry(ext_name="gsas_calib_filename", int_name="gsas_calib_filename"),
+    ParamMapEntry(ext_name="input_mode", int_name="input_batching", enum_class=INPUT_BATCHING),
+    ParamMapEntry(ext_name="maud_grouping_scheme", int_name="maud_grouping_scheme"),
+    ParamMapEntry(ext_name="mode", int_name="mode", enum_class=GEM_CHOPPER_MODES),
+    ParamMapEntry(ext_name="multiple_scattering", int_name="multiple_scattering"),
+    ParamMapEntry(ext_name="raw_tof_cropping_values", int_name="raw_tof_cropping_values"),
+    ParamMapEntry(ext_name="run_number", int_name="run_number"),
+    ParamMapEntry(ext_name="sample_empty", int_name="sample_empty", optional=True),
+    ParamMapEntry(ext_name="sample_empty_scale", int_name="sample_empty_scale"),
+    ParamMapEntry(ext_name="save_all", int_name="save_all"),
+    ParamMapEntry(ext_name="save_gda", int_name="save_gda"),
+    ParamMapEntry(ext_name="save_maud_calib", int_name="save_maud_calib"),
+    ParamMapEntry(ext_name="save_maud", int_name="save_maud"),
+    ParamMapEntry(ext_name="spline_coefficient", int_name="spline_coeff"),
+    ParamMapEntry(ext_name="subtract_empty_instrument",
+                  int_name="subtract_empty_inst",
+                  optional=True),
+    ParamMapEntry(ext_name="suffix", int_name="suffix", optional=True),
+    ParamMapEntry(ext_name="texture_mode", int_name="texture_mode", optional=True),
+    ParamMapEntry(ext_name="output_directory", int_name="output_dir"),
+    ParamMapEntry(ext_name="unit_to_keep",
+                  int_name="unit_to_keep",
+                  enum_class=WORKSPACE_UNITS,
+                  optional=True),
+    ParamMapEntry(ext_name="user_name", int_name="user_name"),
+    ParamMapEntry(ext_name="vanadium_cropping_values", int_name="vanadium_cropping_values"),
+    ParamMapEntry(ext_name="vanadium_normalisation", int_name="do_van_norm")
+]
+attr_mapping.extend(COMMON_PARAM_MAPPING)
diff --git a/scripts/Diffraction/isis_powder/hrpd.py b/scripts/Diffraction/isis_powder/hrpd.py
index 748787090f457410be502f5ce9d4553b92bb1bba..ee7dbaf0b64371515d5c75c0b563a2dbdbd7e602 100644
--- a/scripts/Diffraction/isis_powder/hrpd.py
+++ b/scripts/Diffraction/isis_powder/hrpd.py
@@ -14,6 +14,16 @@ from isis_powder.hrpd_routines import hrpd_advanced_config, hrpd_algs, hrpd_para
 
 import mantid.simpleapi as mantid
 
+# Force using raw files
+# A bug in loading old NeXus files prevents using NeXus.
+RAW_DATA_EXT = '.raw'
+
+
+# Constants
+PROMPT_PULSE_INTERVAL = 20000.0
+PROMPT_PULSE_RIGHT_WIDTH = 140.0
+PROMPT_PULSE_LEFT_WIDTH = 30.0
+
 
 class HRPD(AbstractInst):
 
@@ -27,12 +37,6 @@ class HRPD(AbstractInst):
                                    output_dir=self._inst_settings.output_dir,
                                    inst_prefix="HRPD")
 
-        # Cannot load older .nxs files into Mantid from HRPD
-        # because of a long-term bug which was not reported.
-        # Instead, ask Mantid to use .raw files in this case
-        if not self._inst_settings.file_extension:
-            self._inst_settings.file_extension = ".raw"
-
         self._cached_run_details = {}
         self._sample_details = None
 
@@ -102,6 +106,15 @@ class HRPD(AbstractInst):
             raise RuntimeError("Could not find " + os.path.join(path, name)+" please run create_vanadium with "
                                                                             "\"do_solid_angle_corrections=True\"")
 
+    def _generate_input_file_name(self, run_number, file_ext=None):
+        """
+        Generates a name which Mantid uses within Load to find the file.
+        :param run_number: The run number to convert into a valid format for Mantid
+        :param file_ext: An optional file extension to add to force a particular format
+        :return: A filename that will allow Mantid to find the correct run for that instrument.
+        """
+        return self._generate_inst_filename(run_number=run_number, file_ext=RAW_DATA_EXT)
+
     def _apply_absorb_corrections(self, run_details, ws_to_correct):
         if self._is_vanadium:
             return hrpd_algs.calculate_van_absorb_corrections(
@@ -140,14 +153,25 @@ class HRPD(AbstractInst):
         return self._cached_run_details[run_number_string_key]
 
     def _mask_prompt_pulses(self, ws):
-        left_crop = 30
-        right_crop = 140
-        pulse_interval = 20000
-        for i in range(1, 6):
-            middle = i*pulse_interval
-            min_crop = middle - left_crop
-            max_crop = middle + right_crop
-            mantid.MaskBins(InputWorkspace=ws, OutputWorkspace=ws, XMin=min_crop, XMax=max_crop)
+        """
+        HRPD has a long flight path from the moderator resulting
+        in sharp peaks from the proton pulse that maintain their
+        sharp resolution. Here we mask these pulses out that occur
+        at 20ms intervals.
+
+        :param ws: The workspace containing the pulses. It is
+        masked in place.
+        """
+        # The number of pulse can vary depending on the data range
+        # Compute number of pulses that occur at each 20ms interval.
+        x_data = ws.readX(0)
+        pulse_min = int(round(x_data[0]) / PROMPT_PULSE_INTERVAL) + 1
+        pulse_max = int(round(x_data[-1]) / PROMPT_PULSE_INTERVAL) + 1
+        for i in range(pulse_min, pulse_max):
+            centre = PROMPT_PULSE_INTERVAL * float(i)
+            mantid.MaskBins(InputWorkspace=ws, OutputWorkspace=ws,
+                            XMin=centre - PROMPT_PULSE_LEFT_WIDTH,
+                            XMax=centre + PROMPT_PULSE_RIGHT_WIDTH)
 
     def _switch_tof_window_inst_settings(self, tof_window):
         self._inst_settings.update_attributes(
diff --git a/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_advanced_config.py b/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_advanced_config.py
index 2adac4400ca34eaa82ce372e028464b2d2bdaf5c..ed5a028b8c5c1d30a68c62f07ba1fbf257b77e7a 100644
--- a/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_advanced_config.py
+++ b/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_advanced_config.py
@@ -20,40 +20,39 @@ absorption_correction_params = {
 window_10_50_params = {
     "vanadium_tof_cropping": (1.1e4, 5e4),
     "focused_cropping_values": [
-        (1.2e4, 4.99e4),  # Bank 1
-        (1.2e4, 4.99e4),  # Bank 2
-        (1.2e4, 4.99e4),  # Bank 3
+       (1.2e4, 4.99e4),  # Bank 1
+       (1.2e4, 4.99e4),  # Bank 2
+       (1.2e4, 4.99e4),  # Bank 3
     ]
 }
 
 window_10_110_params = {
     "vanadium_tof_cropping": (1e4, 1.2e5),
     "focused_cropping_values": [
-        (1.5e4, 1.08e5),  # Bank 1
-        (1.5e4, 1.12e5),  # Bank 2
-        (1.5e4, 1e5)      # Bank 3
+       (1.046e4, 1.0577e5),  # Bank 1
+       (0.981e4, 1.147e5),   # Bank 2
+       (1e4, 1.106e5)        # Bank 3
     ]
 }
 
 window_30_130_params = {
     "vanadium_tof_cropping": (3e4, 1.4e5),
     "focused_cropping_values": [
-        (3.5e4, 1.3e5),  # Bank 1
-        (3.4e4, 1.4e5),  # Bank 2
-        (3.3e4, 1.3e5)   # Bank 3
+       (3.137e4, 1.25e5),  # Bank 1
+       (3e4, 1.3555e5),    # Bank 2
+       (3e4, 1.3e5)        # Bank 3
     ]
 }
 
 window_100_200_params = {
     "vanadium_tof_cropping": (1e5, 200500),
     "focused_cropping_values": [
-        (100200, 1.99e5),  # Bank 1
-        (100100, 1.99e5),  # Bank 2
-        (100100, 1.99e5)   # Bank 3
+        (1.0457e5, 1.92337e5),  # Bank 1
+        (0.981e5, 2.0861e5),    # Bank 2
+        (0.981e5, 2.0118e5)     # Bank 3
     ]
 }
 
-
 window_180_280_params = {
     "vanadium_tof_cropping": (1.8e5, 2.8e5),
     "focused_cropping_values": [
@@ -65,7 +64,12 @@ window_180_280_params = {
 
 file_names = {
     "vanadium_peaks_masking_file": "VanaPeaks.dat",
-    "grouping_file_name": "hrpd_new_072_01_corr.cal"
+    "grouping_file_name": "hrpd_new_072_01_corr.cal",
+    "nxs_filename": "{instlow}{runno}{suffix}.nxs",
+    "gss_filename": "{instlow}{runno}{suffix}.gss",
+    "dat_files_directory": "dat_files",
+    "tof_xye_filename": "{instlow}{runno}{_fileext}{suffix}_b{{bankno}}_TOF.dat",
+    "dspacing_xye_filename": "{instlow}{runno}{_fileext}{suffix}_b{{bankno}}_D.dat",
 }
 
 general_params = {
@@ -73,7 +77,7 @@ general_params = {
     "focused_bin_widths": [
         -0.0003,  # Bank 1
         -0.0007,  # Bank 2
-        -0.0012   # Bank 3
+        -0.0012  # Bank 3
     ],
     "mode": "coupled"
 }
diff --git a/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_algs.py b/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_algs.py
index 90f52cad695f1ef7bc48ea03ad4647e297e70483..94599dc3cc8d600ca1fe3cbb5ac96185093eae73 100644
--- a/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_algs.py
+++ b/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_algs.py
@@ -95,41 +95,3 @@ def _get_run_numbers_for_key(tof_dict, key):
     err_message = "this must be under 'coupled' or 'decoupled' and the time of flight window eg 10-110."
     return common.cal_map_dictionary_key_helper(tof_dict, key=key,
                                                 append_to_error_message=err_message)
-
-
-def process_vanadium_for_focusing(bank_spectra, spline_number):
-    output = common.spline_workspaces(num_splines=spline_number, focused_vanadium_spectra=bank_spectra)
-    return output
-
-
-# The following 2 functions may be moved to common
-def _apply_bragg_peaks_masking(workspaces_to_mask, mask_list):
-    output_workspaces = list(workspaces_to_mask)
-
-    for ws_index, (bank_mask_list, workspace) in enumerate(zip(mask_list, output_workspaces)):
-        output_name = "masked_vanadium-" + str(ws_index + 1)
-        for mask_params in bank_mask_list:
-            output_workspaces[ws_index] = mantid.MaskBins(InputWorkspace=output_workspaces[ws_index],
-                                                          OutputWorkspace=output_name,
-                                                          XMin=mask_params[0], XMax=mask_params[1])
-    return output_workspaces
-
-
-def _read_masking_file(masking_file_path):
-    all_banks_masking_list = []
-    bank_masking_list = []
-    ignore_line_prefixes = (' ', '\n', '\t', '#')  # Matches whitespace or # symbol
-    with open(masking_file_path) as mask_file:
-        for line in mask_file:
-            if line.startswith(ignore_line_prefixes):
-                # Push back onto new bank
-                if bank_masking_list:
-                    all_banks_masking_list.append(bank_masking_list)
-                bank_masking_list = []
-            else:
-                # Parse and store in current list
-                line.rstrip()
-                bank_masking_list.append(line.split())
-    if bank_masking_list:
-        all_banks_masking_list.append(bank_masking_list)
-    return all_banks_masking_list
diff --git a/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_param_mapping.py b/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_param_mapping.py
index 02eb39434b1c8e51d812f6ea4251ea471041d9ca..1eeb947310ea74262138be97a01234388659c82a 100644
--- a/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_param_mapping.py
+++ b/scripts/Diffraction/isis_powder/hrpd_routines/hrpd_param_mapping.py
@@ -7,32 +7,35 @@
 from __future__ import (absolute_import, division, print_function)
 
 from isis_powder.hrpd_routines.hrpd_enums import HRPD_MODES, HRPD_TOF_WINDOWS
+from isis_powder.routines.common import PARAM_MAPPING as COMMON_PARAM_MAPPING
 from isis_powder.routines.param_map_entry import ParamMapEntry
 
-attr_mapping = \
-    [
-        ParamMapEntry(ext_name="calibration_directory",      int_name="calibration_dir"),
-        ParamMapEntry(ext_name="calibration_mapping_file",   int_name="cal_mapping_path"),
-        ParamMapEntry(ext_name="config_file",                int_name="config_file_name"),
-        ParamMapEntry(ext_name="do_absorb_corrections",      int_name="do_absorb_corrections"),
-        ParamMapEntry(ext_name="file_ext",                   int_name="file_extension", optional=True),
-        ParamMapEntry(ext_name="focused_bin_widths",         int_name="focused_bin_widths"),
-        ParamMapEntry(ext_name="focused_cropping_values",    int_name="tof_cropping_values"),
-        ParamMapEntry(ext_name="grouping_file_name",         int_name="grouping_file_name"),
-        ParamMapEntry(ext_name="output_directory",           int_name="output_dir"),
-        ParamMapEntry(ext_name="spline_coefficient",         int_name="spline_coeff"),
-        ParamMapEntry(ext_name="first_cycle_run_no",         int_name="run_in_range"),
-        ParamMapEntry(ext_name="mode",                       int_name="mode", enum_class=HRPD_MODES),
-        ParamMapEntry(ext_name="multiple_scattering",        int_name="multiple_scattering"),
-        ParamMapEntry(ext_name="run_number",                 int_name="run_number"),
-        ParamMapEntry(ext_name="sample_empty",               int_name="sample_empty", optional=True),
-        ParamMapEntry(ext_name="sample_empty_scale",         int_name="sample_empty_scale"),
-        ParamMapEntry(ext_name="subtract_empty_instrument",  int_name="subtract_empty_inst", optional=True),
-        ParamMapEntry(ext_name="do_solid_angle_corrections", int_name="do_solid_angle",optional=True),
-        ParamMapEntry(ext_name="suffix",                     int_name="suffix", optional=True),
-        ParamMapEntry(ext_name="user_name",                  int_name="user_name"),
-        ParamMapEntry(ext_name="vanadium_normalisation",     int_name="do_van_norm"),
-        ParamMapEntry(ext_name="vanadium_tof_cropping",      int_name="van_tof_cropping"),
-        ParamMapEntry(ext_name="vanadium_peaks_masking_file", int_name="masking_file_name"),
-        ParamMapEntry(ext_name="window",                     int_name="tof_window", enum_class=HRPD_TOF_WINDOWS)
-    ]
+attr_mapping = [
+    ParamMapEntry(ext_name="calibration_directory", int_name="calibration_dir"),
+    ParamMapEntry(ext_name="calibration_mapping_file", int_name="cal_mapping_path"),
+    ParamMapEntry(ext_name="config_file", int_name="config_file_name"),
+    ParamMapEntry(ext_name="do_absorb_corrections", int_name="do_absorb_corrections"),
+    ParamMapEntry(ext_name="file_ext", int_name="file_extension", optional=True),
+    ParamMapEntry(ext_name="focused_bin_widths", int_name="focused_bin_widths"),
+    ParamMapEntry(ext_name="focused_cropping_values", int_name="tof_cropping_values"),
+    ParamMapEntry(ext_name="grouping_file_name", int_name="grouping_file_name"),
+    ParamMapEntry(ext_name="output_directory", int_name="output_dir"),
+    ParamMapEntry(ext_name="spline_coefficient", int_name="spline_coeff"),
+    ParamMapEntry(ext_name="first_cycle_run_no", int_name="run_in_range"),
+    ParamMapEntry(ext_name="mode", int_name="mode", enum_class=HRPD_MODES),
+    ParamMapEntry(ext_name="multiple_scattering", int_name="multiple_scattering"),
+    ParamMapEntry(ext_name="run_number", int_name="run_number"),
+    ParamMapEntry(ext_name="sample_empty", int_name="sample_empty", optional=True),
+    ParamMapEntry(ext_name="sample_empty_scale", int_name="sample_empty_scale"),
+    ParamMapEntry(ext_name="subtract_empty_instrument",
+                  int_name="subtract_empty_inst",
+                  optional=True),
+    ParamMapEntry(ext_name="do_solid_angle_corrections", int_name="do_solid_angle", optional=True),
+    ParamMapEntry(ext_name="suffix", int_name="suffix", optional=True),
+    ParamMapEntry(ext_name="user_name", int_name="user_name"),
+    ParamMapEntry(ext_name="vanadium_normalisation", int_name="do_van_norm"),
+    ParamMapEntry(ext_name="vanadium_tof_cropping", int_name="van_tof_cropping"),
+    ParamMapEntry(ext_name="vanadium_peaks_masking_file", int_name="masking_file_name"),
+    ParamMapEntry(ext_name="window", int_name="tof_window", enum_class=HRPD_TOF_WINDOWS)
+]
+attr_mapping.extend(COMMON_PARAM_MAPPING)
diff --git a/scripts/Diffraction/isis_powder/pearl.py b/scripts/Diffraction/isis_powder/pearl.py
index cdbe0f8fca7f288ad95f9ddfb352f9a2ec6bb6c9..79f42fcbe3eb2b8c4ee214985e46723b3b3041af 100644
--- a/scripts/Diffraction/isis_powder/pearl.py
+++ b/scripts/Diffraction/isis_powder/pearl.py
@@ -19,16 +19,17 @@ import copy
 
 
 class Pearl(AbstractInst):
-
     def __init__(self, **kwargs):
         self._inst_settings = instrument_settings.InstrumentSettings(
-           param_map=pearl_param_mapping.attr_mapping, adv_conf_dict=pearl_advanced_config.get_all_adv_variables(),
-           kwargs=kwargs)
+            param_map=pearl_param_mapping.attr_mapping,
+            adv_conf_dict=pearl_advanced_config.get_all_adv_variables(),
+            kwargs=kwargs)
         self._default_inst_settings = copy.deepcopy(self._inst_settings)
 
         super(Pearl, self).__init__(user_name=self._inst_settings.user_name,
                                     calibration_dir=self._inst_settings.calibration_dir,
-                                    output_dir=self._inst_settings.output_dir, inst_prefix="PEARL")
+                                    output_dir=self._inst_settings.output_dir,
+                                    inst_prefix="PEARL")
 
         self._cached_run_details = {}
 
@@ -39,7 +40,8 @@ class Pearl(AbstractInst):
                                do_van_normalisation=self._inst_settings.van_norm)
 
     def create_vanadium(self, **kwargs):
-        kwargs["perform_attenuation"] = None  # Hard code this off as we do not need an attenuation file
+        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"]:
@@ -52,25 +54,31 @@ class Pearl(AbstractInst):
         with self._apply_temporary_inst_settings(kwargs, kwargs.get("run_number")):
             run_details = self._get_run_details(self._inst_settings.run_number)
 
-            cross_correlate_params = {"ReferenceSpectra": self._inst_settings.reference_spectra,
-                                      "WorkspaceIndexMin": self._inst_settings.cross_corr_ws_min,
-                                      "WorkspaceIndexMax": self._inst_settings.cross_corr_ws_max,
-                                      "XMin": self._inst_settings.cross_corr_x_min,
-                                      "XMax": self._inst_settings.cross_corr_x_max}
-            get_detector_offsets_params = {"DReference": self._inst_settings.d_reference,
-                                           "Step": self._inst_settings.get_det_offsets_step,
-                                           "XMin": self._inst_settings.get_det_offsets_x_min,
-                                           "XMax": self._inst_settings.get_det_offsets_x_max}
-
-            return pearl_calibration_algs.create_calibration(calibration_runs=self._inst_settings.run_number,
-                                                             instrument=self,
-                                                             offset_file_name=run_details.offset_file_path,
-                                                             grouping_file_name=run_details.grouping_file_path,
-                                                             calibration_dir=self._inst_settings.calibration_dir,
-                                                             rebin_1_params=self._inst_settings.cal_rebin_1,
-                                                             rebin_2_params=self._inst_settings.cal_rebin_2,
-                                                             cross_correlate_params=cross_correlate_params,
-                                                             get_det_offset_params=get_detector_offsets_params)
+            cross_correlate_params = {
+                "ReferenceSpectra": self._inst_settings.reference_spectra,
+                "WorkspaceIndexMin": self._inst_settings.cross_corr_ws_min,
+                "WorkspaceIndexMax": self._inst_settings.cross_corr_ws_max,
+                "XMin": self._inst_settings.cross_corr_x_min,
+                "XMax": self._inst_settings.cross_corr_x_max
+            }
+            get_detector_offsets_params = {
+                "DReference": self._inst_settings.d_reference,
+                "Step": self._inst_settings.get_det_offsets_step,
+                "XMin": self._inst_settings.get_det_offsets_x_min,
+                "XMax": self._inst_settings.get_det_offsets_x_max
+            }
+            output_file_paths = self._generate_out_file_paths(run_details)
+            return pearl_calibration_algs.create_calibration(
+                calibration_runs=self._inst_settings.run_number,
+                instrument=self,
+                offset_file_name=run_details.offset_file_path,
+                grouping_file_name=run_details.grouping_file_path,
+                calibration_dir=self._inst_settings.calibration_dir,
+                rebin_1_params=self._inst_settings.cal_rebin_1,
+                rebin_2_params=self._inst_settings.cal_rebin_2,
+                cross_correlate_params=cross_correlate_params,
+                get_det_offset_params=get_detector_offsets_params,
+                output_name=output_file_paths["output_name"] + "_grouped")
 
     def should_subtract_empty_inst(self):
         return self._inst_settings.subtract_empty_inst
@@ -87,16 +95,16 @@ class Pearl(AbstractInst):
             self._inst_settings.update_attributes(kwargs=kwargs)
 
         # check that cache exists
-        run_number_string_key = self._generate_run_details_fingerprint(run,
-                                                                       self._inst_settings.file_extension,
-                                                                       self._inst_settings.tt_mode)
+        run_number_string_key = self._generate_run_details_fingerprint(
+            run, self._inst_settings.file_extension, self._inst_settings.tt_mode)
         if run_number_string_key in self._cached_run_details:
             # update spline path of cache
 
             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_spline(
+                self._inst_settings, add_spline)
         yield
         # reset instrument settings
         self._inst_settings = copy.deepcopy(self._default_inst_settings)
@@ -105,7 +113,8 @@ 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_spline(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
@@ -113,20 +122,27 @@ class Pearl(AbstractInst):
                                      do_absorb_corrections=self._inst_settings.absorb_corrections)
 
     def _get_run_details(self, run_number_string):
-        run_number_string_key = self._generate_run_details_fingerprint(run_number_string,
-                                                                       self._inst_settings.file_extension,
-                                                                       self._inst_settings.tt_mode)
+        run_number_string_key = self._generate_run_details_fingerprint(
+            run_number_string, self._inst_settings.file_extension, self._inst_settings.tt_mode)
         if run_number_string_key in self._cached_run_details:
             return self._cached_run_details[run_number_string_key]
 
         self._cached_run_details[run_number_string_key] = pearl_algs.get_run_details(
-            run_number_string=run_number_string, inst_settings=self._inst_settings, is_vanadium_run=self._is_vanadium)
+            run_number_string=run_number_string,
+            inst_settings=self._inst_settings,
+            is_vanadium_run=self._is_vanadium)
         return self._cached_run_details[run_number_string_key]
 
-    def _generate_output_file_name(self, run_number_string):
+    def _add_formatting_options(self, format_options):
+        """
+        Add any instrument-specific format options to the given
+        list
+        :param format_options: A dictionary of string format keys mapped to their expansions
+        :return: format_options as it is passed in
+        """
         inst = self._inst_settings
-        return pearl_algs.generate_out_name(run_number_string=run_number_string,
-                                            long_mode_on=inst.long_mode, tt_mode=inst.tt_mode)
+        format_options.update({'tt_mode': str(inst.tt_mode), '_long_mode': '_long' if inst.long_mode else ''})
+        return format_options
 
     def _normalise_ws_current(self, ws_to_correct):
         monitor_spectra = self._inst_settings.monitor_spec_no
@@ -134,11 +150,13 @@ class Pearl(AbstractInst):
         monitor_ws = common.extract_single_spectrum(ws_to_process=ws_to_correct,
                                                     spectrum_number_to_extract=monitor_spectra)
 
-        normalised_ws = pearl_algs.normalise_ws_current(ws_to_correct=ws_to_correct, monitor_ws=monitor_ws,
-                                                        spline_coeff=self._inst_settings.monitor_spline,
-                                                        integration_range=self._inst_settings.monitor_integration_range,
-                                                        lambda_values=self._inst_settings.monitor_lambda,
-                                                        ex_regions=self._inst_settings.monitor_mask_regions)
+        normalised_ws = pearl_algs.normalise_ws_current(
+            ws_to_correct=ws_to_correct,
+            monitor_ws=monitor_ws,
+            spline_coeff=self._inst_settings.monitor_spline,
+            integration_range=self._inst_settings.monitor_integration_range,
+            lambda_values=self._inst_settings.monitor_lambda,
+            ex_regions=self._inst_settings.monitor_mask_regions)
         common.remove_intermediate_workspace(monitor_ws)
         return normalised_ws
 
@@ -153,7 +171,8 @@ class Pearl(AbstractInst):
         new_workspace_names = []
         for ws in splined_list:
             new_name = ws.name() + '_' + self._inst_settings.tt_mode
-            new_workspace_names.append(mantid.RenameWorkspace(InputWorkspace=ws, OutputWorkspace=new_name))
+            new_workspace_names.append(
+                mantid.RenameWorkspace(InputWorkspace=ws, OutputWorkspace=new_name))
 
         return new_workspace_names
 
@@ -176,20 +195,25 @@ class Pearl(AbstractInst):
 
         group_name = "PEARL{0!s}_{1}{2}-Results-D-Grp"
         mode = "_long" if self._inst_settings.long_mode else ""
-        group_name = group_name.format(run_details.output_run_string, self._inst_settings.tt_mode, mode)
-        grouped_d_spacing = mantid.GroupWorkspaces(InputWorkspaces=output_spectra, OutputWorkspace=group_name)
+        group_name = group_name.format(run_details.output_run_string, self._inst_settings.tt_mode,
+                                       mode)
+        grouped_d_spacing = mantid.GroupWorkspaces(InputWorkspaces=output_spectra,
+                                                   OutputWorkspace=group_name)
         return grouped_d_spacing, None
 
     def _crop_banks_to_user_tof(self, focused_banks):
-        return common.crop_banks_using_crop_list(focused_banks, self._inst_settings.tof_cropping_values)
+        return common.crop_banks_using_crop_list(focused_banks,
+                                                 self._inst_settings.tof_cropping_values)
 
     def _crop_raw_to_expected_tof_range(self, ws_to_crop):
-        out_ws = common.crop_in_tof(ws_to_crop=ws_to_crop, x_min=self._inst_settings.raw_data_crop_vals[0],
+        out_ws = common.crop_in_tof(ws_to_crop=ws_to_crop,
+                                    x_min=self._inst_settings.raw_data_crop_vals[0],
                                     x_max=self._inst_settings.raw_data_crop_vals[-1])
         return out_ws
 
     def _crop_van_to_expected_tof_range(self, van_ws_to_crop):
-        cropped_ws = common.crop_in_tof(ws_to_crop=van_ws_to_crop, x_min=self._inst_settings.van_tof_cropping[0],
+        cropped_ws = common.crop_in_tof(ws_to_crop=van_ws_to_crop,
+                                        x_min=self._inst_settings.van_tof_cropping[0],
                                         x_max=self._inst_settings.van_tof_cropping[-1])
         return cropped_ws
 
@@ -197,17 +221,20 @@ class Pearl(AbstractInst):
         if self._inst_settings.gen_absorb:
             absorb_file_name = self._inst_settings.absorb_out_file
             if not absorb_file_name:
-                raise RuntimeError("\"absorb_corrections_out_filename\" must be supplied when generating absorption "
-                                   "corrections")
-            absorb_corrections = pearl_algs.generate_vanadium_absorb_corrections(van_ws=ws_to_correct,
-                                                                                 output_filename=absorb_file_name)
+                raise RuntimeError(
+                    "\"absorb_corrections_out_filename\" must be supplied when generating absorption "
+                    "corrections")
+            absorb_corrections = pearl_algs.generate_vanadium_absorb_corrections(
+                van_ws=ws_to_correct, output_filename=absorb_file_name)
         else:
             absorb_corrections = None
 
-        return pearl_algs.apply_vanadium_absorb_corrections(van_ws=ws_to_correct, run_details=run_details,
+        return pearl_algs.apply_vanadium_absorb_corrections(van_ws=ws_to_correct,
+                                                            run_details=run_details,
                                                             absorb_ws=absorb_corrections)
 
     def _switch_long_mode_inst_settings(self, long_mode_on):
-        self._inst_settings.update_attributes(advanced_config=pearl_advanced_config.get_long_mode_dict(long_mode_on))
+        self._inst_settings.update_attributes(
+            advanced_config=pearl_advanced_config.get_long_mode_dict(long_mode_on))
         if long_mode_on:
             setattr(self._inst_settings, "perform_atten", False)
diff --git a/scripts/Diffraction/isis_powder/pearl_routines/pearl_advanced_config.py b/scripts/Diffraction/isis_powder/pearl_routines/pearl_advanced_config.py
index 01e3b73e093072d8cde0c9abd45366fb696c16b1..9f06bca2cfd982e5f49cbeda9182fc65cb28a3a7 100644
--- a/scripts/Diffraction/isis_powder/pearl_routines/pearl_advanced_config.py
+++ b/scripts/Diffraction/isis_powder/pearl_routines/pearl_advanced_config.py
@@ -5,6 +5,7 @@
 #     & Institut Laue - Langevin
 # SPDX - License - Identifier: GPL - 3.0 +
 from __future__ import (absolute_import, division, print_function)
+
 from numpy import array
 
 general_params = {
@@ -18,7 +19,12 @@ general_params = {
          "vanadium_absorb_filename": "pearl_absorp_sphere_10mm_newinst2_long.nxs",
          "tt88_grouping_filename": "pearl_group_12_1_TT88.cal",
          "tt70_grouping_filename": "pearl_group_12_1_TT70.cal",
-         "tt35_grouping_filename": "pearl_group_12_1_TT35.cal"
+         "tt35_grouping_filename": "pearl_group_12_1_TT35.cal",
+         "nxs_filename": "{instshort}{runno}{suffix}_{tt_mode}{_long_mode}.nxs",
+         "gss_filename": "{instshort}{runno}{suffix}_{tt_mode}{_long_mode}.gsas",
+         "dat_files_directory": "",
+         "tof_xye_filename": "{instshort}{runno}{suffix}_{tt_mode}{_long_mode}_tof_xye.dat",
+         "dspacing_xye_filename": "{instshort}{runno}{suffix}_{tt_mode}{_long_mode}_d_xye.dat"
     },
 
     "subtract_empty_instrument": True,
diff --git a/scripts/Diffraction/isis_powder/pearl_routines/pearl_algs.py b/scripts/Diffraction/isis_powder/pearl_routines/pearl_algs.py
index e83da35df6985676ba9f38bc1a00a43099dd0b35..c639dfc8ea998c7b11e41fb35471c54912e6d40d 100644
--- a/scripts/Diffraction/isis_powder/pearl_routines/pearl_algs.py
+++ b/scripts/Diffraction/isis_powder/pearl_routines/pearl_algs.py
@@ -46,14 +46,6 @@ def apply_vanadium_absorb_corrections(van_ws, run_details, absorb_ws=None):
     return van_ws
 
 
-def generate_out_name(run_number_string, long_mode_on, tt_mode):
-    output_name = "PRL" + str(run_number_string)
-    # Append each mode of operation
-    output_name += "_" + str(tt_mode)
-    output_name += "_long" if long_mode_on else ""
-    return output_name
-
-
 def generate_vanadium_absorb_corrections(van_ws, output_filename):
     shape_ws = mantid.CloneWorkspace(InputWorkspace=van_ws)
     shape_ws = mantid.ConvertUnits(InputWorkspace=shape_ws, OutputWorkspace=shape_ws, Target="Wavelength")
diff --git a/scripts/Diffraction/isis_powder/pearl_routines/pearl_calibration_algs.py b/scripts/Diffraction/isis_powder/pearl_routines/pearl_calibration_algs.py
index dd7abc6b8aeb2208e42880a26a31b02cf3438ec6..4a27dbc0a7ad67bcb368ae729b61afc9ebd772ed 100644
--- a/scripts/Diffraction/isis_powder/pearl_routines/pearl_calibration_algs.py
+++ b/scripts/Diffraction/isis_powder/pearl_routines/pearl_calibration_algs.py
@@ -13,7 +13,8 @@ from isis_powder.routines.common_enums import INPUT_BATCHING, WORKSPACE_UNITS
 
 
 def create_calibration(calibration_runs, instrument, offset_file_name, grouping_file_name, calibration_dir,
-                       rebin_1_params, rebin_2_params, cross_correlate_params, get_det_offset_params):
+                       rebin_1_params, rebin_2_params, cross_correlate_params, get_det_offset_params,
+                       output_name):
     """
     Create a calibration file from (usually) a ceria run
     :param calibration_runs: Run number(s) for this run
@@ -25,6 +26,7 @@ def create_calibration(calibration_runs, instrument, offset_file_name, grouping_
     :param rebin_2_params: Parameters for the second rebin step (as a string in the usual format)
     :param cross_correlate_params: Parameters for CrossCorrelate (as a dictionary PropertyName: PropertyValue)
     :param get_det_offset_params: Parameters for GetDetectorOffsets (as a dictionary PropertyName: PropertyValue)
+    :param output_name: The name of the focused output workspace
     """
     input_ws_list = common.load_current_normalised_ws_list(run_number_string=calibration_runs, instrument=instrument,
                                                            input_batching=INPUT_BATCHING.Summed)
@@ -49,8 +51,7 @@ def create_calibration(calibration_runs, instrument, offset_file_name, grouping_
 
     grouping_file = os.path.join(calibration_dir, grouping_file_name)
     focused = mantid.DiffractionFocussing(InputWorkspace=aligned, GroupingFileName=grouping_file,
-                                          OutputWorkspace=instrument._generate_output_file_name(calibration_runs)
-                                          + "_grouped")
+                                          OutputWorkspace=output_name)
 
     common.remove_intermediate_workspace([calibration_ws, rebinned, cross_correlated, rebinned_tof, aligned,
                                           offsets_ws_name])
diff --git a/scripts/Diffraction/isis_powder/pearl_routines/pearl_param_mapping.py b/scripts/Diffraction/isis_powder/pearl_routines/pearl_param_mapping.py
index 695c007ff81da05d01076c2ceeb37f648041bdda..52de01fff18021c09af62e0cd71d070015a4a7f4 100644
--- a/scripts/Diffraction/isis_powder/pearl_routines/pearl_param_mapping.py
+++ b/scripts/Diffraction/isis_powder/pearl_routines/pearl_param_mapping.py
@@ -6,54 +6,57 @@
 # SPDX - License - Identifier: GPL - 3.0 +
 from __future__ import (absolute_import, division, print_function)
 
+from isis_powder.routines.common import PARAM_MAPPING
 from isis_powder.routines.param_map_entry import ParamMapEntry
 from isis_powder.pearl_routines.pearl_enums import PEARL_FOCUS_MODES, PEARL_TT_MODES
 
 #                 Maps friendly user name (ext_name) -> script name (int_name)
-attr_mapping = \
-    [
-        ParamMapEntry(ext_name="attenuation_file_path",           int_name="attenuation_file_path"),
-        ParamMapEntry(ext_name="config_file",                     int_name="config_file_name"),
-        ParamMapEntry(ext_name="calibration_mapping_file",        int_name="cal_mapping_path"),
-        ParamMapEntry(ext_name="calibration_directory",           int_name="calibration_dir"),
-        ParamMapEntry(ext_name="create_cal_rebin_1_params",       int_name="cal_rebin_1"),
-        ParamMapEntry(ext_name="create_cal_rebin_2_params",       int_name="cal_rebin_2"),
-        ParamMapEntry(ext_name="cross_corr_reference_spectra",    int_name="reference_spectra"),
-        ParamMapEntry(ext_name="cross_corr_ws_index_max",         int_name="cross_corr_ws_max"),
-        ParamMapEntry(ext_name="cross_corr_ws_index_min",         int_name="cross_corr_ws_min"),
-        ParamMapEntry(ext_name="cross_corr_x_min",                int_name="cross_corr_x_min"),
-        ParamMapEntry(ext_name="cross_corr_x_max",                int_name="cross_corr_x_max"),
-        ParamMapEntry(ext_name="do_absorb_corrections",           int_name="absorb_corrections"),
-        ParamMapEntry(ext_name="file_ext",                        int_name="file_extension", optional=True),
-        ParamMapEntry(ext_name="focused_bin_widths",              int_name="focused_bin_widths"),
-        ParamMapEntry(ext_name="focused_cropping_values",         int_name="tof_cropping_values"),
-        ParamMapEntry(ext_name="focus_mode",                      int_name="focus_mode", enum_class=PEARL_FOCUS_MODES),
-        ParamMapEntry(ext_name="generate_absorb_corrections",     int_name="gen_absorb"),
-        ParamMapEntry(ext_name="get_det_offsets_d_ref",           int_name="d_reference"),
-        ParamMapEntry(ext_name="get_det_offsets_step",            int_name="get_det_offsets_step"),
-        ParamMapEntry(ext_name="get_det_offsets_x_min",           int_name="get_det_offsets_x_min"),
-        ParamMapEntry(ext_name="get_det_offsets_x_max",           int_name="get_det_offsets_x_max"),
-        ParamMapEntry(ext_name="long_mode",                       int_name="long_mode"),
-        ParamMapEntry(ext_name="monitor_lambda_crop_range",       int_name="monitor_lambda"),
-        ParamMapEntry(ext_name="monitor_integration_range",       int_name="monitor_integration_range"),
-        ParamMapEntry(ext_name="monitor_mask_regions",            int_name="monitor_mask_regions"),
-        ParamMapEntry(ext_name="monitor_spectrum_number",         int_name="monitor_spec_no"),
-        ParamMapEntry(ext_name="monitor_spline_coefficient",      int_name="monitor_spline"),
-        ParamMapEntry(ext_name="output_directory",                int_name="output_dir"),
-        ParamMapEntry(ext_name="perform_attenuation",             int_name="perform_atten"),
-        ParamMapEntry(ext_name="raw_data_tof_cropping",           int_name="raw_data_crop_vals"),
-        ParamMapEntry(ext_name="run_in_cycle",                    int_name="run_in_range"),
-        ParamMapEntry(ext_name="run_number",                      int_name="run_number"),
-        ParamMapEntry(ext_name="spline_coefficient",              int_name="spline_coefficient"),
-        ParamMapEntry(ext_name="subtract_empty_instrument",       int_name="subtract_empty_inst"),
-        ParamMapEntry(ext_name="suffix",                          int_name="suffix", optional=True),
-        ParamMapEntry(ext_name="tt88_grouping_filename",          int_name="tt88_grouping"),
-        ParamMapEntry(ext_name="tt70_grouping_filename",          int_name="tt70_grouping"),
-        ParamMapEntry(ext_name="tt35_grouping_filename",          int_name="tt35_grouping"),
-        ParamMapEntry(ext_name="tt_mode",                         int_name="tt_mode", enum_class=PEARL_TT_MODES),
-        ParamMapEntry(ext_name="user_name",                       int_name="user_name"),
-        ParamMapEntry(ext_name="vanadium_absorb_filename",        int_name="van_absorb_file"),
-        ParamMapEntry(ext_name="absorb_corrections_out_filename", int_name="absorb_out_file", optional=True),
-        ParamMapEntry(ext_name="vanadium_tof_cropping",           int_name="van_tof_cropping"),
-        ParamMapEntry(ext_name="vanadium_normalisation",          int_name="van_norm")
-    ]
+attr_mapping = [
+    ParamMapEntry(ext_name="attenuation_file_path", int_name="attenuation_file_path"),
+    ParamMapEntry(ext_name="config_file", int_name="config_file_name"),
+    ParamMapEntry(ext_name="calibration_mapping_file", int_name="cal_mapping_path"),
+    ParamMapEntry(ext_name="calibration_directory", int_name="calibration_dir"),
+    ParamMapEntry(ext_name="create_cal_rebin_1_params", int_name="cal_rebin_1"),
+    ParamMapEntry(ext_name="create_cal_rebin_2_params", int_name="cal_rebin_2"),
+    ParamMapEntry(ext_name="cross_corr_reference_spectra", int_name="reference_spectra"),
+    ParamMapEntry(ext_name="cross_corr_ws_index_max", int_name="cross_corr_ws_max"),
+    ParamMapEntry(ext_name="cross_corr_ws_index_min", int_name="cross_corr_ws_min"),
+    ParamMapEntry(ext_name="cross_corr_x_min", int_name="cross_corr_x_min"),
+    ParamMapEntry(ext_name="cross_corr_x_max", int_name="cross_corr_x_max"),
+    ParamMapEntry(ext_name="do_absorb_corrections", int_name="absorb_corrections"),
+    ParamMapEntry(ext_name="file_ext", int_name="file_extension", optional=True),
+    ParamMapEntry(ext_name="focused_bin_widths", int_name="focused_bin_widths"),
+    ParamMapEntry(ext_name="focused_cropping_values", int_name="tof_cropping_values"),
+    ParamMapEntry(ext_name="focus_mode", int_name="focus_mode", enum_class=PEARL_FOCUS_MODES),
+    ParamMapEntry(ext_name="generate_absorb_corrections", int_name="gen_absorb"),
+    ParamMapEntry(ext_name="get_det_offsets_d_ref", int_name="d_reference"),
+    ParamMapEntry(ext_name="get_det_offsets_step", int_name="get_det_offsets_step"),
+    ParamMapEntry(ext_name="get_det_offsets_x_min", int_name="get_det_offsets_x_min"),
+    ParamMapEntry(ext_name="get_det_offsets_x_max", int_name="get_det_offsets_x_max"),
+    ParamMapEntry(ext_name="long_mode", int_name="long_mode"),
+    ParamMapEntry(ext_name="monitor_lambda_crop_range", int_name="monitor_lambda"),
+    ParamMapEntry(ext_name="monitor_integration_range", int_name="monitor_integration_range"),
+    ParamMapEntry(ext_name="monitor_mask_regions", int_name="monitor_mask_regions"),
+    ParamMapEntry(ext_name="monitor_spectrum_number", int_name="monitor_spec_no"),
+    ParamMapEntry(ext_name="monitor_spline_coefficient", int_name="monitor_spline"),
+    ParamMapEntry(ext_name="output_directory", int_name="output_dir"),
+    ParamMapEntry(ext_name="perform_attenuation", int_name="perform_atten"),
+    ParamMapEntry(ext_name="raw_data_tof_cropping", int_name="raw_data_crop_vals"),
+    ParamMapEntry(ext_name="run_in_cycle", int_name="run_in_range"),
+    ParamMapEntry(ext_name="run_number", int_name="run_number"),
+    ParamMapEntry(ext_name="spline_coefficient", int_name="spline_coefficient"),
+    ParamMapEntry(ext_name="subtract_empty_instrument", int_name="subtract_empty_inst"),
+    ParamMapEntry(ext_name="suffix", int_name="suffix", optional=True),
+    ParamMapEntry(ext_name="tt88_grouping_filename", int_name="tt88_grouping"),
+    ParamMapEntry(ext_name="tt70_grouping_filename", int_name="tt70_grouping"),
+    ParamMapEntry(ext_name="tt35_grouping_filename", int_name="tt35_grouping"),
+    ParamMapEntry(ext_name="tt_mode", int_name="tt_mode", enum_class=PEARL_TT_MODES),
+    ParamMapEntry(ext_name="user_name", int_name="user_name"),
+    ParamMapEntry(ext_name="vanadium_absorb_filename", int_name="van_absorb_file"),
+    ParamMapEntry(ext_name="absorb_corrections_out_filename",
+                  int_name="absorb_out_file",
+                  optional=True),
+    ParamMapEntry(ext_name="vanadium_tof_cropping", int_name="van_tof_cropping"),
+    ParamMapEntry(ext_name="vanadium_normalisation", int_name="van_norm")
+]
+attr_mapping.extend(PARAM_MAPPING)
diff --git a/scripts/Diffraction/isis_powder/polaris.py b/scripts/Diffraction/isis_powder/polaris.py
index 2a2e8bd9d05347974acce26cf9badf15df3758dd..04b15eb7105acc0acc98247f7931dfe247ce1802 100644
--- a/scripts/Diffraction/isis_powder/polaris.py
+++ b/scripts/Diffraction/isis_powder/polaris.py
@@ -20,7 +20,8 @@ class Polaris(AbstractInst):
 
         super(Polaris, self).__init__(user_name=self._inst_settings.user_name,
                                       calibration_dir=self._inst_settings.calibration_dir,
-                                      output_dir=self._inst_settings.output_dir, inst_prefix="POL")
+                                      output_dir=self._inst_settings.output_dir,
+                                      inst_prefix="POLARIS")
 
         # Hold the last dictionary later to avoid us having to keep parsing the YAML
         self._run_details_cached_obj = {}
diff --git a/scripts/Diffraction/isis_powder/polaris_routines/polaris_advanced_config.py b/scripts/Diffraction/isis_powder/polaris_routines/polaris_advanced_config.py
index 3a5d0c563298e5d21f03bb26dfbe37b83c60c998..a9239a955e183dda0cb55d7dbb6e8666ec319c26 100644
--- a/scripts/Diffraction/isis_powder/polaris_routines/polaris_advanced_config.py
+++ b/scripts/Diffraction/isis_powder/polaris_routines/polaris_advanced_config.py
@@ -7,6 +7,12 @@
 # Note all changes in this file require a restart of Mantid
 # additionally any long term changes should be sent back to the development team so any changes can be merged
 # into future versions of Mantid.
+from __future__ import (absolute_import, unicode_literals)
+
+import copy
+
+from isis_powder.routines.common import ADVANCED_CONFIG as COMMON_ADVANCED_CONFIG
+
 
 absorption_correction_params = {
     # These are read directly by the generate absorb corrections functions instead of being parsed.
@@ -110,7 +116,7 @@ def get_mode_specific_dict(mode):
 
 
 def get_all_adv_variables(mode=None):
-    advanced_config_dict = {}
+    advanced_config_dict = copy.copy(COMMON_ADVANCED_CONFIG)
     advanced_config_dict.update(variables)
     advanced_config_dict.update(get_mode_specific_dict(mode))
     return advanced_config_dict
diff --git a/scripts/Diffraction/isis_powder/polaris_routines/polaris_algs.py b/scripts/Diffraction/isis_powder/polaris_routines/polaris_algs.py
index e28f2f5dd8c994661ce44ab6c84221edafb53b0e..89264ec8115398cae24930347bc77fddfb3f4dac 100644
--- a/scripts/Diffraction/isis_powder/polaris_routines/polaris_algs.py
+++ b/scripts/Diffraction/isis_powder/polaris_routines/polaris_algs.py
@@ -12,7 +12,6 @@ from isis_powder.routines import absorb_corrections, common
 from isis_powder.routines.common_enums import WORKSPACE_UNITS
 from isis_powder.routines.run_details import create_run_details_object, get_cal_mapping_dict
 from isis_powder.polaris_routines import polaris_advanced_config
-from six import PY3
 
 
 def calculate_van_absorb_corrections(ws_to_correct, multiple_scattering, is_vanadium):
@@ -62,15 +61,6 @@ def get_run_details(run_number_string, inst_settings, is_vanadium_run):
                                      vanadium_string=vanadium_runs, grouping_file_name=grouping_file_name)
 
 
-def process_vanadium_for_focusing(bank_spectra, mask_path, spline_number):
-    bragg_masking_list = _read_masking_file(mask_path)
-    masked_workspace_list = _apply_bragg_peaks_masking(bank_spectra, mask_list=bragg_masking_list)
-    output = common.spline_workspaces(focused_vanadium_spectra=masked_workspace_list,
-                                      num_splines=spline_number)
-    common.remove_intermediate_workspace(masked_workspace_list)
-    return output
-
-
 def save_unsplined_vanadium(vanadium_ws, output_path):
     converted_workspaces = []
 
@@ -127,48 +117,6 @@ def _obtain_focused_run(run_number, focus_file_path):
     return focused_ws
 
 
-def _apply_bragg_peaks_masking(workspaces_to_mask, mask_list):
-    output_workspaces = list(workspaces_to_mask)
-
-    for ws_index, (bank_mask_list, workspace) in enumerate(zip(mask_list, output_workspaces)):
-        output_name = "masked_vanadium-" + str(ws_index + 1)
-        for mask_params in bank_mask_list:
-            output_workspaces[ws_index] = mantid.MaskBins(InputWorkspace=output_workspaces[ws_index],
-                                                          OutputWorkspace=output_name,
-                                                          XMin=mask_params[0], XMax=mask_params[1])
-    return output_workspaces
-
-
-def _read_masking_file(masking_file_path):
-    all_banks_masking_list = []
-    bank_masking_list = []
-    ignore_line_prefixes = (' ', '\n', '\t', '#')  # Matches whitespace or # symbol
-
-    # Python 3 requires the encoding to be included so an Angstrom
-    # symbol can be read, I'm assuming all file read here are
-    # `latin-1` which may not be true in the future. Python 2 `open`
-    # doesn't have an encoding option
-    if PY3:
-        encoding = {"encoding": "latin-1"}
-    else:
-        encoding = {}
-    with open(masking_file_path, **encoding) as mask_file:
-        for line in mask_file:
-            if line.startswith(ignore_line_prefixes):
-                # Push back onto new bank
-                if bank_masking_list:
-                    all_banks_masking_list.append(bank_masking_list)
-                bank_masking_list = []
-            else:
-                # Parse and store in current list
-                line.rstrip()
-                bank_masking_list.append(line.split())
-
-    if bank_masking_list:
-        all_banks_masking_list.append(bank_masking_list)
-    return all_banks_masking_list
-
-
 def _determine_chopper_mode(ws):
     if ws.getRun().hasProperty('Frequency'):
         frequency = ws.getRun()['Frequency'].lastValue()
diff --git a/scripts/Diffraction/isis_powder/polaris_routines/polaris_param_mapping.py b/scripts/Diffraction/isis_powder/polaris_routines/polaris_param_mapping.py
index 0f9b0bb316a4537f44c8e957c2080a9373e8f1b5..102354db49e47d4677020b9cd35041c26bbc69c7 100644
--- a/scripts/Diffraction/isis_powder/polaris_routines/polaris_param_mapping.py
+++ b/scripts/Diffraction/isis_powder/polaris_routines/polaris_param_mapping.py
@@ -7,34 +7,36 @@
 from __future__ import (absolute_import, division, print_function)
 
 from isis_powder.routines.param_map_entry import ParamMapEntry
+from isis_powder.routines.common import PARAM_MAPPING as COMMON_PARAM_MAPPING
 from isis_powder.routines.common_enums import INPUT_BATCHING
 from isis_powder.polaris_routines.polaris_enums import POLARIS_CHOPPER_MODES
 
 #                 Maps friendly user name (ext_name) -> script name (int_name)
-attr_mapping = \
-    [
-     ParamMapEntry(ext_name="calibration_directory",       int_name="calibration_dir"),
-     ParamMapEntry(ext_name="calibration_mapping_file",    int_name="cal_mapping_path"),
-     ParamMapEntry(ext_name="config_file",                 int_name="config_file"),
-     ParamMapEntry(ext_name="do_absorb_corrections",       int_name="do_absorb_corrections"),
-     ParamMapEntry(ext_name="do_van_normalisation",        int_name="do_van_normalisation"),
-     ParamMapEntry(ext_name="file_ext",                    int_name="file_extension", optional=True),
-     ParamMapEntry(ext_name="first_cycle_run_no",          int_name="run_in_range"),
-     ParamMapEntry(ext_name="focused_cropping_values",     int_name="focused_cropping_values"),
-     ParamMapEntry(ext_name="focused_bin_widths",          int_name="focused_bin_widths"),
-     ParamMapEntry(ext_name="grouping_file_name",          int_name="grouping_file_name"),
-     ParamMapEntry(ext_name="input_mode",                  int_name="input_mode", enum_class=INPUT_BATCHING),
-     ParamMapEntry(ext_name="merge_banks",                 int_name="merge_banks"),
-     ParamMapEntry(ext_name="mode",                        int_name="mode", enum_class=POLARIS_CHOPPER_MODES, optional=True),
-     ParamMapEntry(ext_name="multiple_scattering",         int_name="multiple_scattering", optional=True),
-     ParamMapEntry(ext_name="raw_data_cropping_values",    int_name="raw_data_crop_values"),
-     ParamMapEntry(ext_name="run_number",                  int_name="run_number"),
-     ParamMapEntry(ext_name="sample_empty",                int_name="sample_empty",   optional=True),
-     ParamMapEntry(ext_name="sample_empty_scale",          int_name="sample_empty_scale"),
-     ParamMapEntry(ext_name="suffix",                      int_name="suffix", optional=True),
-     ParamMapEntry(ext_name="spline_coefficient",          int_name="spline_coeff"),
-     ParamMapEntry(ext_name="output_directory",            int_name="output_dir"),
-     ParamMapEntry(ext_name="user_name",                   int_name="user_name"),
-     ParamMapEntry(ext_name="vanadium_cropping_values",    int_name="van_crop_values"),
-     ParamMapEntry(ext_name="vanadium_peaks_masking_file", int_name="masking_file_name")
-    ]
+attr_mapping = [
+    ParamMapEntry(ext_name="calibration_directory", int_name="calibration_dir"),
+    ParamMapEntry(ext_name="calibration_mapping_file", int_name="cal_mapping_path"),
+    ParamMapEntry(ext_name="config_file", int_name="config_file"),
+    ParamMapEntry(ext_name="do_absorb_corrections", int_name="do_absorb_corrections"),
+    ParamMapEntry(ext_name="do_van_normalisation", int_name="do_van_normalisation"),
+    ParamMapEntry(ext_name="file_ext", int_name="file_extension", optional=True),
+    ParamMapEntry(ext_name="first_cycle_run_no", int_name="run_in_range"),
+    ParamMapEntry(ext_name="focused_cropping_values", int_name="focused_cropping_values"),
+    ParamMapEntry(ext_name="focused_bin_widths", int_name="focused_bin_widths"),
+    ParamMapEntry(ext_name="grouping_file_name", int_name="grouping_file_name"),
+    ParamMapEntry(ext_name="input_mode", int_name="input_mode", enum_class=INPUT_BATCHING),
+    ParamMapEntry(ext_name="merge_banks", int_name="merge_banks"),
+    ParamMapEntry(ext_name="mode", int_name="mode", enum_class=POLARIS_CHOPPER_MODES,
+                  optional=True),
+    ParamMapEntry(ext_name="multiple_scattering", int_name="multiple_scattering", optional=True),
+    ParamMapEntry(ext_name="raw_data_cropping_values", int_name="raw_data_crop_values"),
+    ParamMapEntry(ext_name="run_number", int_name="run_number"),
+    ParamMapEntry(ext_name="sample_empty", int_name="sample_empty", optional=True),
+    ParamMapEntry(ext_name="sample_empty_scale", int_name="sample_empty_scale"),
+    ParamMapEntry(ext_name="suffix", int_name="suffix", optional=True),
+    ParamMapEntry(ext_name="spline_coefficient", int_name="spline_coeff"),
+    ParamMapEntry(ext_name="output_directory", int_name="output_dir"),
+    ParamMapEntry(ext_name="user_name", int_name="user_name"),
+    ParamMapEntry(ext_name="vanadium_cropping_values", int_name="van_crop_values"),
+    ParamMapEntry(ext_name="vanadium_peaks_masking_file", int_name="masking_file_name")
+]
+attr_mapping.extend(COMMON_PARAM_MAPPING)
diff --git a/scripts/Diffraction/isis_powder/routines/common.py b/scripts/Diffraction/isis_powder/routines/common.py
index 58324df1c285bf955386265e0a8b7339388dfc33..113b6cbe6d4a38b3cb989a7ba032ac83084c4006 100644
--- a/scripts/Diffraction/isis_powder/routines/common.py
+++ b/scripts/Diffraction/isis_powder/routines/common.py
@@ -11,6 +11,25 @@ import warnings
 import mantid.kernel as kernel
 import mantid.simpleapi as mantid
 from isis_powder.routines.common_enums import INPUT_BATCHING, WORKSPACE_UNITS
+from isis_powder.routines.param_map_entry import ParamMapEntry
+
+# Common param mapping entries
+PARAM_MAPPING = [
+    ParamMapEntry(ext_name="nxs_filename", int_name="nxs_filename"),
+    ParamMapEntry(ext_name="gss_filename", int_name="gss_filename"),
+    ParamMapEntry(ext_name="dat_files_directory", int_name="dat_files_directory"),
+    ParamMapEntry(ext_name="tof_xye_filename", int_name="tof_xye_filename"),
+    ParamMapEntry(ext_name="dspacing_xye_filename", int_name="dspacing_xye_filename"),
+]
+
+# Set of defaults for the advanced config settings
+ADVANCED_CONFIG = {
+    "nxs_filename": "{fileext}{inst}{runno}{suffix}.nxs",
+    "gss_filename": "{fileext}{inst}{runno}{suffix}.gsas",
+    "dat_files_directory": "dat_files",
+    "tof_xye_filename": "{fileext}{instshort}{runno}{suffix}-b_{{bankno}}-TOF.dat",
+    "dspacing_xye_filename": "{fileext}{instshort}{runno}{suffix}-b_{{bankno}}-d.dat"
+}
 
 
 def apply_bragg_peaks_masking(workspaces_to_mask, mask_list):
diff --git a/scripts/Diffraction/isis_powder/routines/common_output.py b/scripts/Diffraction/isis_powder/routines/common_output.py
index a0554a3ed394b099ea29f53b9445afdd29863b93..138d899ad7f623cd9d99e3b10e1d8bc5edfe67ee 100644
--- a/scripts/Diffraction/isis_powder/routines/common_output.py
+++ b/scripts/Diffraction/isis_powder/routines/common_output.py
@@ -29,61 +29,64 @@ def split_into_tof_d_spacing_groups(run_details, processed_spectra):
         d_spacing_out_name = run_number + ext + "-ResultD_" + str(name_index)
         tof_out_name = run_number + ext + "-ResultTOF_" + str(name_index)
 
-        d_spacing_output.append(mantid.ConvertUnits(InputWorkspace=ws, OutputWorkspace=d_spacing_out_name,
-                                                    Target="dSpacing"))
-        tof_output.append(mantid.ConvertUnits(InputWorkspace=ws, OutputWorkspace=tof_out_name, Target="TOF"))
+        d_spacing_output.append(
+            mantid.ConvertUnits(InputWorkspace=ws,
+                                OutputWorkspace=d_spacing_out_name,
+                                Target="dSpacing"))
+        tof_output.append(
+            mantid.ConvertUnits(InputWorkspace=ws, OutputWorkspace=tof_out_name, Target="TOF"))
 
     # Group the outputs
     d_spacing_group_name = run_number + ext + "-ResultD"
-    d_spacing_group = mantid.GroupWorkspaces(InputWorkspaces=d_spacing_output, OutputWorkspace=d_spacing_group_name)
+    d_spacing_group = mantid.GroupWorkspaces(InputWorkspaces=d_spacing_output,
+                                             OutputWorkspace=d_spacing_group_name)
     tof_group_name = run_number + ext + "-ResultTOF"
     tof_group = mantid.GroupWorkspaces(InputWorkspaces=tof_output, OutputWorkspace=tof_group_name)
 
     return d_spacing_group, tof_group
 
 
-def save_focused_data(d_spacing_group, tof_group, output_paths, run_number_string, inst_prefix, file_ext):
+def save_focused_data(d_spacing_group, tof_group, output_paths):
     """
     Saves out focused data into nxs, GSAS and .dat formats. Requires the grouped workspace
-    in TOF and dSpacing, the a dictionary of output paths generated by abstract_inst,
-    the user inputted run number string and the prefix of the instrument.
+    in TOF and dSpacing and the dictionary of output paths generated by abstract_inst.
     :param d_spacing_group: The focused workspace group in dSpacing
     :param tof_group: The focused workspace group in TOF
     :param output_paths: A dictionary containing the full paths to save to
-    :param run_number_string: The user input run number(s) as a string to generate output name
-    :param inst_prefix: The instrument prefix to generate output name
     :return: None
     """
-    mantid.SaveGSS(InputWorkspace=tof_group, Filename=output_paths["gss_filename"], SplitFiles=False, Append=False)
-    mantid.SaveNexusProcessed(InputWorkspace=tof_group, Filename=output_paths["nxs_filename"], Append=False)
+    def ensure_dir_exists(filename):
+        dirname = os.path.dirname(filename)
+        if not os.path.exists(dirname):
+            os.makedirs(dirname)
+        return filename
 
-    dat_folder_name = "dat_files"
-    dat_file_destination = os.path.join(output_paths["output_folder"], dat_folder_name)
-    if not os.path.exists(dat_file_destination):
-        os.makedirs(dat_file_destination)
+    mantid.SaveGSS(InputWorkspace=tof_group,
+                   Filename=ensure_dir_exists(output_paths["gss_filename"]),
+                   SplitFiles=False,
+                   Append=False)
+    mantid.SaveNexusProcessed(InputWorkspace=tof_group,
+                              Filename=ensure_dir_exists(output_paths["nxs_filename"]),
+                              Append=False)
 
-    _save_xye(ws_group=d_spacing_group, ws_units="d", run_number=run_number_string,
-              output_folder=dat_file_destination, inst_prefix=inst_prefix, file_ext=file_ext)
-    _save_xye(ws_group=tof_group, ws_units="TOF", run_number=run_number_string,
-              output_folder=dat_file_destination, inst_prefix=inst_prefix, file_ext=file_ext)
+    _save_xye(ws_group=d_spacing_group,
+              filename_template=ensure_dir_exists(output_paths["tof_xye_filename"]))
+    _save_xye(ws_group=tof_group,
+              filename_template=ensure_dir_exists(output_paths["dspacing_xye_filename"]))
 
 
-def _save_xye(ws_group, ws_units, run_number, output_folder, inst_prefix, file_ext):
+def _save_xye(ws_group, filename_template):
     """
     Saves XYE data into .dat files. This expects the .dat folder to be created and passed.
     It saves the specified group with the specified units into a file whose name contains that
     information.
     :param ws_group: The workspace group to save out to .dat files
-    :param ws_units: The units of all workspaces in that group
-    :param run_number: The run number string to use when generating the filename
-    :param output_folder: The output folder to save these files to
-    :param inst_prefix: The prefix of the instrument to generate the filename
-    :return:
+    :param filename_template: A string containing a fullpath with a string format template {bankno} to
+    denote where the bank number should be inserted
     """
-    prefix_filename = str(inst_prefix) + str(run_number)
     for bank_index, ws in enumerate(ws_group):
         bank_index += 1  # Ensure we start a 1 when saving out
-        outfile_name = file_ext + prefix_filename + "-b_" + str(bank_index) + "-" + ws_units + ".dat"
-        full_file_path = os.path.join(output_folder, outfile_name)
-
-        mantid.SaveFocusedXYE(InputWorkspace=ws, Filename=full_file_path, SplitFiles=False, IncludeHeader=False)
+        mantid.SaveFocusedXYE(InputWorkspace=ws,
+                              Filename=filename_template.format(bankno=bank_index),
+                              SplitFiles=False,
+                              IncludeHeader=False)
diff --git a/scripts/test/isis_powder/ISISPowderAbsorptionTest.py b/scripts/Diffraction/isis_powder/test/ISISPowderAbsorptionTest.py
similarity index 87%
rename from scripts/test/isis_powder/ISISPowderAbsorptionTest.py
rename to scripts/Diffraction/isis_powder/test/ISISPowderAbsorptionTest.py
index 05bf7d489b40c8c75aedecd1b3762a5319161ae4..b1bfc835a10141d132aa68d3b15e60533b741ea6 100644
--- a/scripts/test/isis_powder/ISISPowderAbsorptionTest.py
+++ b/scripts/Diffraction/isis_powder/test/ISISPowderAbsorptionTest.py
@@ -36,8 +36,6 @@ class ISISPowderAbsorptionTest(unittest.TestCase):
             "chemical_formula": "V"
         }
 
-        ws = mantid.CreateSampleWorkspace(Function='Flat background', NumBanks=1, BankPixelWidth=1, XMax=2, BinWidth=1)
-
         # Test each key one at a time
         for blacklisted_key in iterkeys(sample_properties):
             # Force python to make a shallow copy
@@ -46,11 +44,11 @@ class ISISPowderAbsorptionTest(unittest.TestCase):
 
             # Check that is raises an error
             with assertRaisesRegex(self, KeyError, "The following key was not found in the advanced configuration"):
-                ws = absorb_corrections.create_vanadium_sample_details_obj(config_dict=modified_dict)
+                absorb_corrections.create_vanadium_sample_details_obj(config_dict=modified_dict)
 
             # Then check the error actually has the key name in it
             with assertRaisesRegex(self, KeyError, blacklisted_key):
-                ws = absorb_corrections.create_vanadium_sample_details_obj(config_dict=modified_dict)
+                absorb_corrections.create_vanadium_sample_details_obj(config_dict=modified_dict)
 
 
 if __name__ == "__main__":
diff --git a/scripts/Diffraction/isis_powder/test/ISISPowderAbstractInstrumentTest.py b/scripts/Diffraction/isis_powder/test/ISISPowderAbstractInstrumentTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..349aaf7ae9d5aa7839adbd4db6c845fa216ba13d
--- /dev/null
+++ b/scripts/Diffraction/isis_powder/test/ISISPowderAbstractInstrumentTest.py
@@ -0,0 +1,229 @@
+# 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
+# SPDX - License - Identifier: GPL - 3.0 +
+from __future__ import (absolute_import, division, print_function)
+
+import mantid
+
+from isis_powder.abstract_inst import AbstractInst
+from isis_powder.routines.instrument_settings import InstrumentSettings
+from isis_powder.routines.param_map_entry import ParamMapEntry
+from isis_powder.routines import common, run_details, yaml_parser
+
+import os
+import random
+import string
+import tempfile
+import unittest
+import warnings
+
+
+def _gen_random_string():
+    return ''.join(random.choice(string.ascii_lowercase) for _ in range(10))
+
+
+class _MockInst(AbstractInst):
+
+    _param_map = [
+        ParamMapEntry(ext_name="cal_dir", int_name="calibration_dir"),
+        ParamMapEntry(ext_name="cal_map", int_name="cal_mapping_path"),
+        ParamMapEntry(ext_name="file_ext", int_name="file_extension", optional=True),
+        ParamMapEntry(ext_name="group_file", int_name="grouping_file_name"),
+        ParamMapEntry(ext_name="out_dir", int_name="output_dir"),
+        ParamMapEntry(ext_name="suffix", int_name="suffix", optional=True),
+        ParamMapEntry(ext_name="user_name", int_name="user_name"),
+        ParamMapEntry(ext_name="nxs_filename", int_name="nxs_filename"),
+        ParamMapEntry(ext_name="gss_filename", int_name="gss_filename"),
+        ParamMapEntry(ext_name="dat_files_directory", int_name="dat_files_directory"),
+        ParamMapEntry(ext_name="tof_xye_filename", int_name="tof_xye_filename"),
+        ParamMapEntry(ext_name="dspacing_xye_filename", int_name="dspacing_xye_filename"),
+    ]
+    _advanced_config = {
+        "user_name": "ISISPowderAbstractInstrumentTest",
+        "nxs_filename": "{inst}{runno}{suffix}.nxs",
+        "gss_filename": "{inst}{runno}{suffix}.gss",
+        "dat_files_directory": "dat_files",
+        "tof_xye_filename": "{inst}{runno}_{fileext}{suffix}_b{{bankno}}_TOF.dat",
+        "dspacing_xye_filename": "{inst}{runno}{fileext}{suffix}_b{{bankno}}_D.dat",
+    }
+    INST_PREFIX = "MOCK"
+
+    def __init__(self, cal_file_path, **kwargs):
+        self._inst_settings = InstrumentSettings(param_map=self._param_map,
+                                                 adv_conf_dict=self._advanced_config,
+                                                 kwargs=kwargs)
+        self.cal_mapping_path = cal_file_path
+
+        super(_MockInst, self).__init__(user_name=self._inst_settings.user_name,
+                                        calibration_dir=self._inst_settings.calibration_dir,
+                                        output_dir=self._inst_settings.output_dir,
+                                        inst_prefix=self.INST_PREFIX)
+
+
+class ISISPowderAbstractInstrumentTest(unittest.TestCase):
+
+    _folders_to_remove = set()
+    CALIB_FILE_NAME = "ISISPowderRunDetailsTest.yaml"
+    GROUPING_FILE_NAME = "hrpd_new_072_01.cal"
+
+    def _create_temp_dir(self):
+        temp_dir = tempfile.mkdtemp()
+        self._folders_to_remove.add(temp_dir)
+        return temp_dir
+
+    def _find_file_or_die(self, name):
+        full_path = mantid.api.FileFinder.getFullPath(name)
+        if not full_path:
+            self.fail("Could not find file \"{}\"".format(name))
+        return full_path
+
+    def _setup_mock_inst(self, yaml_file_path, calibration_dir, output_dir,
+                         suffix=None, nxs_filename=None, tof_xye_filename=None,
+                         file_ext=None, dat_files_directory=""):
+        calib_file_path = self._find_file_or_die(self.CALIB_FILE_NAME)
+        grouping_file_path = self._find_file_or_die(self.GROUPING_FILE_NAME)
+        test_configuration_path = mantid.api.FileFinder.getFullPath(yaml_file_path)
+        if not test_configuration_path or len(test_configuration_path) <= 0:
+            self.fail("Could not find the unit test input file called: " + str(yaml_file_path))
+        return _MockInst(cal_file_path=test_configuration_path,
+                         group_file=grouping_file_path,
+                         cal_dir=calibration_dir,
+                         out_dir=output_dir,
+                         cal_map=calib_file_path,
+                         suffix=suffix, nxs_filename=nxs_filename, tof_xye_filename=tof_xye_filename,
+                         file_ext=file_ext, dat_files_directory=dat_files_directory)
+
+    def tearDown(self):
+        for folder in self._folders_to_remove:
+            try:
+                os.rmdir(folder)
+            except OSError as exc:
+                warnings.warn("Could not remove folder at \"{}\"\n"
+                              "Error message:\n{}".format(folder, exc))
+
+    def test_generate_out_file_paths_standard_inst_prefix(self):
+        mock_inst, run_details, out_dir = self._setup_for_generate_out_file_paths(
+            nxs_template="{inst}{runno}{suffix}.nxs", tof_xye_template="",
+            suffix="")
+
+        output_paths = mock_inst._generate_out_file_paths(run_details=run_details)
+        expected_nxs_filename = os.path.join(out_dir, "16_4", "ISISPowderAbstractInstrumentTest",
+                                             "MOCK15.nxs")
+
+        self.assertEqual(output_paths["nxs_filename"], expected_nxs_filename)
+
+    def test_generate_out_file_paths_lower_inst_prefix(self):
+        mock_inst, run_details, out_dir = self._setup_for_generate_out_file_paths(
+            nxs_template="{instlow}{runno}{suffix}.nxs", suffix="")
+
+        output_paths = mock_inst._generate_out_file_paths(run_details=run_details)
+        expected_nxs_filename = os.path.join(out_dir, "16_4", "ISISPowderAbstractInstrumentTest",
+                                             "mock15.nxs")
+
+        self.assertEqual(output_paths["nxs_filename"], expected_nxs_filename)
+
+    def test_generate_out_file_paths_with_suffix(self):
+        mock_inst, run_details, out_dir = self._setup_for_generate_out_file_paths(
+            nxs_template="{inst}{runno}{suffix}.nxs", suffix="_suf")
+
+        output_paths = mock_inst._generate_out_file_paths(run_details=run_details)
+        expected_nxs_filename = os.path.join(out_dir, "16_4", "ISISPowderAbstractInstrumentTest",
+                                             "MOCK15_suf.nxs")
+
+        self.assertEqual(output_paths["nxs_filename"], expected_nxs_filename)
+
+    def test_generate_out_file_paths_including_fileext(self):
+        mock_inst, run_details, out_dir = self._setup_for_generate_out_file_paths(
+            nxs_template="{fileext}{inst}{runno}{suffix}.nxs", suffix="", file_ext='.s01')
+
+        output_paths = mock_inst._generate_out_file_paths(run_details=run_details)
+        expected_nxs_filename = os.path.join(out_dir, "16_4", "ISISPowderAbstractInstrumentTest",
+                                             "s01MOCK15.nxs")
+
+        self.assertEqual(output_paths["nxs_filename"], expected_nxs_filename)
+
+    def test_generate_out_file_paths_respects_dat_files_directory_where_appropriate(self):
+        mock_inst, run_details, out_dir = self._setup_for_generate_out_file_paths(
+            nxs_template="{inst}{runno}_{fileext}_1.nxs",
+            tof_xye_template="{inst}{runno}_{fileext}{suffix}_b1_TOF.dat",
+            suffix="_suf", file_ext=".s01",
+            dat_files_dir="dat_files")
+
+        output_paths = mock_inst._generate_out_file_paths(run_details=run_details)
+        expected_nxs_filename = os.path.join(out_dir, "16_4", "ISISPowderAbstractInstrumentTest",
+                                             "MOCK15_s01_1.nxs")
+        expected_xye_filename = os.path.join(out_dir, "16_4", "ISISPowderAbstractInstrumentTest", "dat_files",
+                                             "MOCK15_s01_suf_b1_TOF.dat")
+
+        self.assertEqual(output_paths["nxs_filename"], expected_nxs_filename)
+        self.assertEqual(output_paths["tof_xye_filename"], expected_xye_filename)
+
+    def _setup_for_generate_out_file_paths(self, nxs_template="", tof_xye_template="",
+                                           suffix=None, file_ext=None, dat_files_dir=""):
+        cal_dir = self._create_temp_dir()
+        out_dir = self._create_temp_dir()
+
+        mock_inst = self._setup_mock_inst(suffix=suffix, file_ext=file_ext,
+                                          yaml_file_path="ISISPowderRunDetailsTest.yaml",
+                                          calibration_dir=cal_dir,
+                                          output_dir=out_dir, nxs_filename=nxs_template,
+                                          tof_xye_filename=tof_xye_template,
+                                          dat_files_directory=dat_files_dir)
+
+        run_number = 15
+        run_number2 = common.get_first_run_number(run_number_string=run_number)
+        cal_mapping_dict = yaml_parser.get_run_dictionary(run_number_string=run_number2,
+                                                          file_path=mock_inst.cal_mapping_path)
+
+        grouping_filename = _gen_random_string()
+        empty_runs = common.cal_map_dictionary_key_helper(dictionary=cal_mapping_dict,
+                                                          key="empty_run_numbers")
+        vanadium_runs = common.cal_map_dictionary_key_helper(dictionary=cal_mapping_dict,
+                                                             key="vanadium_run_numbers")
+
+        run_details_obj = run_details.create_run_details_object(
+            run_number_string=run_number,
+            inst_settings=mock_inst._inst_settings,
+            is_vanadium_run=False,
+            grouping_file_name=grouping_filename,
+            empty_run_number=empty_runs,
+            vanadium_string=vanadium_runs)
+
+        return mock_inst, run_details_obj, out_dir
+
+    def test_set_valid_beam_parameters(self):
+        # Setup basic instrument mock
+        cal_dir = self._create_temp_dir()
+        out_dir = self._create_temp_dir()
+        mock_inst = self._setup_mock_inst(calibration_dir=cal_dir,
+                                          output_dir=out_dir,
+                                          yaml_file_path="ISISPowderRunDetailsTest.yaml")
+
+        # Test valid parameters are retained
+        mock_inst.set_beam_parameters(height=1.234, width=2)
+        self.assertEqual(mock_inst._beam_parameters['height'], 1.234)
+        self.assertEqual(mock_inst._beam_parameters['width'], 2)
+
+    def test_set_invalid_beam_parameters(self):
+        # Setup basic instrument mock
+        cal_dir = self._create_temp_dir()
+        out_dir = self._create_temp_dir()
+        mock_inst = self._setup_mock_inst(calibration_dir=cal_dir,
+                                          output_dir=out_dir,
+                                          yaml_file_path="ISISPowderRunDetailsTest.yaml")
+
+        # Test combination of positive / negative raise exceptions
+        self.assertRaises(ValueError, mock_inst.set_beam_parameters, height=1.234, width=-2)
+        self.assertRaises(ValueError, mock_inst.set_beam_parameters, height=-1.234, width=2)
+        self.assertRaises(ValueError, mock_inst.set_beam_parameters, height=-1.234, width=-2)
+
+        # Test non-numerical input
+        self.assertRaises(ValueError, mock_inst.set_beam_parameters, height='height', width=-2)
+        self.assertRaises(ValueError, mock_inst.set_beam_parameters, height=-1.234, width=True)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/scripts/test/isis_powder/ISISPowderCommonTest.py b/scripts/Diffraction/isis_powder/test/ISISPowderCommonTest.py
similarity index 100%
rename from scripts/test/isis_powder/ISISPowderCommonTest.py
rename to scripts/Diffraction/isis_powder/test/ISISPowderCommonTest.py
diff --git a/scripts/test/isis_powder/ISISPowderFocusCropTest.py b/scripts/Diffraction/isis_powder/test/ISISPowderFocusCropTest.py
similarity index 100%
rename from scripts/test/isis_powder/ISISPowderFocusCropTest.py
rename to scripts/Diffraction/isis_powder/test/ISISPowderFocusCropTest.py
diff --git a/scripts/test/isis_powder/ISISPowderGemOutputTest.py b/scripts/Diffraction/isis_powder/test/ISISPowderGemOutputTest.py
similarity index 99%
rename from scripts/test/isis_powder/ISISPowderGemOutputTest.py
rename to scripts/Diffraction/isis_powder/test/ISISPowderGemOutputTest.py
index 45498be0bd50fb01ec155bd8d849ed083b325e10..441aae8f513d5119156e346055a47142036514e4 100644
--- a/scripts/test/isis_powder/ISISPowderGemOutputTest.py
+++ b/scripts/Diffraction/isis_powder/test/ISISPowderGemOutputTest.py
@@ -52,10 +52,7 @@ class ISISPowderGemOutputTest(unittest.TestCase):
                                                         XUnit='DSpacing',
                                                         StoreInADS=True))
 
-
-
         gem_output_test_ws_group = mantid.GroupWorkspaces(wsgroup)
-
         gem_output.save_gda(d_spacing_group=gem_output_test_ws_group,
                             gsas_calib_filename=path_to_ipf,
                             grouping_scheme=self.GROUPING_SCHEME,
@@ -70,4 +67,4 @@ class ISISPowderGemOutputTest(unittest.TestCase):
 
 
 if __name__ == '__main__':
-    unittest.main()
\ No newline at end of file
+    unittest.main()
diff --git a/scripts/test/isis_powder/ISISPowderInstrumentSettingsTest.py b/scripts/Diffraction/isis_powder/test/ISISPowderInstrumentSettingsTest.py
similarity index 99%
rename from scripts/test/isis_powder/ISISPowderInstrumentSettingsTest.py
rename to scripts/Diffraction/isis_powder/test/ISISPowderInstrumentSettingsTest.py
index 3847fbf70a373dc6a14d8a6b1e5e5be4b4724ba3..1120108ddc6b872fb364c7f16037e7f1e7fc642d 100644
--- a/scripts/test/isis_powder/ISISPowderInstrumentSettingsTest.py
+++ b/scripts/Diffraction/isis_powder/test/ISISPowderInstrumentSettingsTest.py
@@ -6,7 +6,6 @@
 # SPDX - License - Identifier: GPL - 3.0 +
 from __future__ import (absolute_import, division, print_function)
 
-import mantid
 import unittest
 import warnings
 
diff --git a/scripts/test/isis_powder/ISISPowderRunDetailsTest.py b/scripts/Diffraction/isis_powder/test/ISISPowderRunDetailsTest.py
similarity index 100%
rename from scripts/test/isis_powder/ISISPowderRunDetailsTest.py
rename to scripts/Diffraction/isis_powder/test/ISISPowderRunDetailsTest.py
diff --git a/scripts/test/isis_powder/ISISPowderSampleDetailsTest.py b/scripts/Diffraction/isis_powder/test/ISISPowderSampleDetailsTest.py
similarity index 99%
rename from scripts/test/isis_powder/ISISPowderSampleDetailsTest.py
rename to scripts/Diffraction/isis_powder/test/ISISPowderSampleDetailsTest.py
index 4705e682cd2362c72bb42753149770f584f5e95a..af7218e40d8c842135440b6c9f8edb4b351b4286 100644
--- a/scripts/test/isis_powder/ISISPowderSampleDetailsTest.py
+++ b/scripts/Diffraction/isis_powder/test/ISISPowderSampleDetailsTest.py
@@ -6,7 +6,6 @@
 # SPDX - License - Identifier: GPL - 3.0 +
 from __future__ import (absolute_import, division, print_function)
 
-import mantid
 import io
 import six
 import sys
@@ -350,6 +349,7 @@ class ISISPowderSampleDetailsTest(unittest.TestCase):
         self.assertEqual(sample_details_obj_str.center(), [float(p) for p in center_string])
         self.assertEqual(sample_details_obj_str.angle(), float(angle_string))
 
+
 def get_std_out_buffer_obj():
     # Because of the way that strings and bytes
     # have changed between Python 2/3 we need to
diff --git a/scripts/test/isis_powder/ISISPowderYamlParserTest.py b/scripts/Diffraction/isis_powder/test/ISISPowderYamlParserTest.py
similarity index 99%
rename from scripts/test/isis_powder/ISISPowderYamlParserTest.py
rename to scripts/Diffraction/isis_powder/test/ISISPowderYamlParserTest.py
index 6aea3ede4dd926ebf973f8f688a06e3d4a98d4af..36fa66f7aca50dccbc289b4f5a15d12589fd1736 100644
--- a/scripts/test/isis_powder/ISISPowderYamlParserTest.py
+++ b/scripts/Diffraction/isis_powder/test/ISISPowderYamlParserTest.py
@@ -6,7 +6,6 @@
 # SPDX - License - Identifier: GPL - 3.0 +
 from __future__ import (absolute_import, division, print_function)
 
-import mantid
 import tempfile
 import os
 import unittest
diff --git a/scripts/Diffraction/isis_powder/test/__init__.py b/scripts/Diffraction/isis_powder/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d43ca442a3b069de62b966ee9f1e47beadcb570c
--- /dev/null
+++ b/scripts/Diffraction/isis_powder/test/__init__.py
@@ -0,0 +1,6 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
diff --git a/scripts/test/CMakeLists.txt b/scripts/test/CMakeLists.txt
index 29dcaeb11f27b7d5d38148fb6928b2af70d2cbdf..0e0ada5db78e398284794f0b5d3e3fa22e912276 100644
--- a/scripts/test/CMakeLists.txt
+++ b/scripts/test/CMakeLists.txt
@@ -60,7 +60,6 @@ unset(PYUNITTEST_QT_API)
 
 # Additional tests
 add_subdirectory(directtools)
-add_subdirectory(isis_powder)
 add_subdirectory(MultiPlotting)
 add_subdirectory(Muon)
 add_subdirectory(SANS)
diff --git a/scripts/test/isis_powder/CMakeLists.txt b/scripts/test/isis_powder/CMakeLists.txt
deleted file mode 100644
index 0947470963bd07e4c897b63a2310cc29ea6ed8b1..0000000000000000000000000000000000000000
--- a/scripts/test/isis_powder/CMakeLists.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-# Tests for ISIS Powder
-
-set(TEST_PY_FILES
-    ISISPowderAbsorptionTest.py
-    ISISPowderAbstractInstrumentTest.py
-    ISISPowderCommonTest.py
-    ISISPowderGemOutputTest.py
-    ISISPowderInstrumentSettingsTest.py
-    ISISPowderRunDetailsTest.py
-    ISISPowderSampleDetailsTest.py
-    ISISPowderYamlParserTest.py
-    ISISPowderFocusCropTest.py)
-
-check_tests_valid(${CMAKE_CURRENT_SOURCE_DIR} ${TEST_PY_FILES})
-
-# Prefix for test name=PythonAlgorithms
-pyunittest_add_test(${CMAKE_CURRENT_SOURCE_DIR} python.IsisPowder
-                    ${TEST_PY_FILES})
diff --git a/scripts/test/isis_powder/ISISPowderAbstractInstrumentTest.py b/scripts/test/isis_powder/ISISPowderAbstractInstrumentTest.py
deleted file mode 100644
index 20341b42e674fad8051d6b30307820dd6731ba4a..0000000000000000000000000000000000000000
--- a/scripts/test/isis_powder/ISISPowderAbstractInstrumentTest.py
+++ /dev/null
@@ -1,148 +0,0 @@
-# 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
-# SPDX - License - Identifier: GPL - 3.0 +
-from __future__ import (absolute_import, division, print_function)
-
-import mantid
-
-from isis_powder.abstract_inst import AbstractInst
-from isis_powder.routines.instrument_settings import InstrumentSettings
-from isis_powder.routines.param_map_entry import ParamMapEntry
-from isis_powder.routines import  common, run_details, yaml_parser
-
-import os
-import random
-import string
-import tempfile
-import unittest
-import warnings
-
-
-class ISISPowderAbstractInstrumentTest(unittest.TestCase):
-
-    _folders_to_remove = set()
-    CALIB_FILE_NAME = "ISISPowderRunDetailsTest.yaml"
-    GROUPING_FILE_NAME = "hrpd_new_072_01.cal"
-
-    def _create_temp_dir(self):
-        temp_dir = tempfile.mkdtemp()
-        self._folders_to_remove.add(temp_dir)
-        return temp_dir
-
-    def _find_file_or_die(self, name):
-        full_path = mantid.api.FileFinder.getFullPath(name)
-        if not full_path:
-            self.fail("Could not find file \"{}\"".format(name))
-        return full_path
-
-    def _setup_mock_inst(self, yaml_file_path, calibration_dir, output_dir, suffix=None):
-        calib_file_path = self._find_file_or_die(self.CALIB_FILE_NAME)
-        grouping_file_path = self._find_file_or_die(self.GROUPING_FILE_NAME)
-        test_configuration_path = mantid.api.FileFinder.getFullPath(yaml_file_path)
-        if not test_configuration_path or len(test_configuration_path) <= 0:
-            self.fail("Could not find the unit test input file called: " + str(yaml_file_path))
-        return _MockInst(cal_file_path=test_configuration_path, group_file=grouping_file_path, cal_dir=calibration_dir,
-                         out_dir=output_dir, cal_map=calib_file_path, suffix=suffix)
-
-    def tearDown(self):
-        for folder in self._folders_to_remove:
-            try:
-                os.rmdir(folder)
-            except OSError as exc:
-                warnings.warn("Could not remove folder at \"{}\"\n"
-                              "Error message:\n{}".format(folder, exc))
-
-    def test_generate_out_file_paths(self):
-        cal_dir = self._create_temp_dir()
-        out_dir = self._create_temp_dir()
-
-        mock_inst = self._setup_mock_inst(suffix="-suf", yaml_file_path="ISISPowderRunDetailsTest.yaml",
-                                          calibration_dir=cal_dir, output_dir=out_dir)
-        run_number = 15
-        run_number2 = common.get_first_run_number(run_number_string=run_number)
-        cal_mapping_dict = yaml_parser.get_run_dictionary(run_number_string=run_number2,
-                                                          file_path=mock_inst.cal_mapping_path)
-
-        grouping_filename = _gen_random_string()
-        empty_runs = common.cal_map_dictionary_key_helper(dictionary=cal_mapping_dict, key="empty_run_numbers")
-        vanadium_runs = common.cal_map_dictionary_key_helper(dictionary=cal_mapping_dict, key="vanadium_run_numbers")
-
-        run_details_obj = run_details.create_run_details_object(run_number_string=run_number,
-                                                                inst_settings=mock_inst._inst_settings,
-                                                                is_vanadium_run=False,
-                                                                grouping_file_name=grouping_filename,
-                                                                empty_run_number=empty_runs,
-                                                                vanadium_string=vanadium_runs)
-
-        output_paths = mock_inst._generate_out_file_paths(run_details=run_details_obj)
-
-        expected_nxs_filename = os.path.join(out_dir,
-                                             "16_4",
-                                             "ISISPowderAbstractInstrumentTest",
-                                             "MOCK15-suf.nxs")
-        self.assertEqual(output_paths["nxs_filename"], expected_nxs_filename)
-
-    def test_set_valid_beam_parameters(self):
-        # Setup basic instrument mock
-        cal_dir = self._create_temp_dir()
-        out_dir = self._create_temp_dir()
-        mock_inst = self._setup_mock_inst(calibration_dir=cal_dir, output_dir=out_dir,
-                                          yaml_file_path="ISISPowderRunDetailsTest.yaml"
-                                          )
-
-        # Test valid parameters are retained
-        mock_inst.set_beam_parameters(height=1.234, width=2)
-        self.assertEqual(mock_inst._beam_parameters['height'], 1.234)
-        self.assertEqual(mock_inst._beam_parameters['width'], 2)
-
-    def test_set_invalid_beam_parameters(self):
-        # Setup basic instrument mock
-        cal_dir = self._create_temp_dir()
-        out_dir = self._create_temp_dir()
-        mock_inst = self._setup_mock_inst(calibration_dir=cal_dir, output_dir=out_dir,
-                                          yaml_file_path="ISISPowderRunDetailsTest.yaml")
-
-        # Test combination of positive / negative raise exceptions
-        self.assertRaises(ValueError, mock_inst.set_beam_parameters, height=1.234, width=-2)
-        self.assertRaises(ValueError, mock_inst.set_beam_parameters, height=-1.234, width=2)
-        self.assertRaises(ValueError, mock_inst.set_beam_parameters, height=-1.234, width=-2)
-
-        # Test non-numerical input
-        self.assertRaises(ValueError, mock_inst.set_beam_parameters, height='height', width=-2)
-        self.assertRaises(ValueError, mock_inst.set_beam_parameters, height=-1.234, width=True)
-
-
-def _gen_random_string():
-    return ''.join(random.choice(string.ascii_lowercase) for _ in range(10))
-
-
-class _MockInst(AbstractInst):
-
-    _param_map = [
-        ParamMapEntry(ext_name="cal_dir",    int_name="calibration_dir"),
-        ParamMapEntry(ext_name="cal_map",    int_name="cal_mapping_path"),
-        ParamMapEntry(ext_name="file_ext",   int_name="file_extension", optional=True),
-        ParamMapEntry(ext_name="group_file", int_name="grouping_file_name"),
-        ParamMapEntry(ext_name="out_dir",    int_name="output_dir"),
-        ParamMapEntry(ext_name="suffix",     int_name="suffix", optional=True),
-        ParamMapEntry(ext_name="user_name",  int_name="user_name")
-    ]
-    _advanced_config = {"user_name": "ISISPowderAbstractInstrumentTest"}
-    INST_PREFIX = "MOCK"
-
-    def __init__(self, cal_file_path, **kwargs):
-        self._inst_settings = InstrumentSettings(param_map=self._param_map,
-                                                 adv_conf_dict=self._advanced_config,
-                                                 kwargs=kwargs)
-        self.cal_mapping_path = cal_file_path
-
-        super(_MockInst, self).__init__(user_name=self._inst_settings.user_name,
-                                        calibration_dir=self._inst_settings.calibration_dir,
-                                        output_dir=self._inst_settings.output_dir,
-                                        inst_prefix=self.INST_PREFIX)
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tools/sip/sipwrapper.py b/tools/sip/sipwrapper.py
new file mode 100755
index 0000000000000000000000000000000000000000..1ae794a8dcebdad1fd262e09fe97d617018b5c6c
--- /dev/null
+++ b/tools/sip/sipwrapper.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+"""
+Wraps a call to the sip code generation executable to strip any throw specifications
+from the generated code as these have been removed from C++17.
+"""
+from __future__ import (absolute_import, division, print_function, unicode_literals)
+
+# system imports
+import argparse
+import logging
+import os
+import re
+import subprocess
+import sys
+
+LOG_LEVEL = logging.INFO
+EXTENSION = '.cpp'
+GENERATED_FILENAME_TEMPLATE = 'sip{modulename:s}part0{extension:s}'
+THROW_SPEC_RE = re.compile(r'throw\(.*?\)')
+
+
+def main(argv):
+    """
+    Main entry point for the program.
+
+    :param argv: Command-line arguments. The first
+    argument is expected to be full path to the sip
+    program. Any subsequent arguments are passed
+    directly to sip invocation
+    """
+    configure_logging()
+    sip_cmd_line, spec_file, output_dir = parse_arguments(argv)
+    logging.debug("Processing specification: {}".format(spec_file))
+
+    run_vanilla_sip(sip_cmd_line)
+    generated_module_path = generated_filepath(output_dir, spec_file)
+    if not os.path.exists(generated_module_path):
+        raise RuntimeError("Unable to find sip-generated module '{}'".format(generated_module_path))
+    logging.debug("Found generated module: {}".format(generated_module_path))
+
+    sanitize_generated_module(generated_module_path)
+    return 0
+
+
+def configure_logging():
+    """
+    Configure the logging framework
+    """
+    logging.basicConfig(level=LOG_LEVEL)
+
+
+def parse_arguments(argv):
+    """
+    Process command-line arguments and extract
+    the output directory
+    :return: A 4-tuple (sip_cmd, path to spec file, output directory, nparts)
+    """
+    parser = argparse.ArgumentParser()
+    parser.add_argument('-c', dest='output_dir')
+    known_args, _ = parser.parse_known_args(argv)
+    sip_cmd_line = argv[1:]
+    if sys.platform == 'win32':
+        sip_cmd_line = map(lambda s: s.replace('\\', '\\\\'), sip_cmd_line)
+        # the executable may need .exe appending to be run with the cmd
+        # processor
+        sip_exe = sip_cmd_line[0] + '.exe'
+        if os.path.exists(sip_exe):
+            sip_cmd_line[0] = sip_exe
+
+    spec_file = argv[-1]
+
+    return sip_cmd_line, spec_file, known_args.output_dir
+
+
+def run_vanilla_sip(sip_cmd_line):
+    """
+    Run unmodified sip command to produce the initial .cpp module
+
+    :param sip_cmd: The full sip command as passed to this program
+    :raises: RuntimeError if the sip command fails
+    """
+    logging.debug("Running sip: {}".format(' '.join(sip_cmd_line)))
+    retcode = subprocess.call(sip_cmd_line)
+    if retcode != 0:
+        raise RuntimeError("Error running sip.")
+
+
+def generated_filepath(output_dir, spec_filename):
+    """
+    Compute the path of the sip-generated module file.
+    :param output_dir: The directory the output has been placed in.
+    :param specfile: The original specfilename
+    :return: The full path to the generated file
+    """
+    modulename, _ = os.path.splitext(os.path.basename(spec_filename))
+    return os.path.join(
+        output_dir, GENERATED_FILENAME_TEMPLATE.format(modulename=modulename, extension=EXTENSION))
+
+
+def sanitize_generated_module(generated_module_filepath):
+    """
+    Takes the module code as generated by sip and sanitizes it to be compatible
+    with the current standard. It replaces the original file.
+
+    Currently:
+      - removes all throw() specifications as they are not supported in C++ 17
+
+    :param generated_module_filepath:
+    """
+    with open(generated_module_filepath, 'r') as sip_module_orig:
+        module_code_orig = sip_module_orig.readlines()
+
+    sanitized_code = []
+    for line_orig in module_code_orig:
+        sanitized_code.append(THROW_SPEC_RE.sub('', line_orig))
+
+    with open(generated_module_filepath, 'w') as sanitized_module:
+        sanitized_module.write(''.join(sanitized_code))
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv))