diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 11ab5428615d7af3c4a5b0382024de6520c27f3d..bb471b0b4c0e552eef7a511c4e41ce6d97b35d9e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -11,6 +11,7 @@ repos:
     hooks:
       - id: trailing-whitespace
         args: [--markdown-linebreak-ext=md]
+        exclude: .patch$
       - id: check-added-large-files
         args: ['--maxkb=4096']
       - id: check-xml
diff --git a/Framework/API/inc/MantidAPI/ExperimentInfo.h b/Framework/API/inc/MantidAPI/ExperimentInfo.h
index 663416ad2dbb3c991c0ec63c2ff7b8d6c2de782f..7325cf488d9b868b3aa951fa4368b5a95ac9aa0c 100644
--- a/Framework/API/inc/MantidAPI/ExperimentInfo.h
+++ b/Framework/API/inc/MantidAPI/ExperimentInfo.h
@@ -12,6 +12,7 @@
 #include "MantidGeometry/Instrument_fwd.h"
 
 #include "MantidKernel/DeltaEMode.h"
+#include "MantidKernel/Unit.h"
 #include "MantidKernel/V3D.h"
 #include "MantidKernel/cow_ptr.h"
 
@@ -110,6 +111,12 @@ public:
   double getEFixed(const std::shared_ptr<const Geometry::IDetector> &detector =
                        std::shared_ptr<const Geometry::IDetector>{
                            nullptr}) const;
+  double getEFixedGivenEMode(
+      const std::shared_ptr<const Geometry::IDetector> &detector,
+      const Kernel::DeltaEMode::Type emode) const;
+  double getEFixedForIndirect(
+      const std::shared_ptr<const Geometry::IDetector> &detector,
+      const std::vector<std::string> &parameterNames) const;
   /// Set the efixed value for a given detector ID
   void setEFixed(const detid_t detID, const double value);
 
diff --git a/Framework/API/inc/MantidAPI/SpectrumInfo.h b/Framework/API/inc/MantidAPI/SpectrumInfo.h
index 1ba9f3a6b4e610f4fd3551e57a45a7b2471d75d3..0bb3ea26da472fd0e17425b68c8316f2f14cdb37 100644
--- a/Framework/API/inc/MantidAPI/SpectrumInfo.h
+++ b/Framework/API/inc/MantidAPI/SpectrumInfo.h
@@ -8,6 +8,8 @@
 
 #include "MantidAPI/DllConfig.h"
 #include "MantidAPI/SpectrumInfoIterator.h"
+#include "MantidKernel/DeltaEMode.h"
+#include "MantidKernel/Unit.h"
 #include "MantidKernel/V3D.h"
 #include "MantidKernel/cow_ptr.h"
 
@@ -70,6 +72,11 @@ public:
   double azimuthal(const size_t index) const;
   std::pair<double, double> geographicalAngles(const size_t index) const;
   Kernel::V3D position(const size_t index) const;
+  Kernel::UnitParametersMap
+  diffractometerConstants(const size_t index,
+                          std::vector<detid_t> &uncalibratedDets) const;
+  Kernel::UnitParametersMap diffractometerConstants(const size_t index) const;
+  double difcUncalibrated(const size_t index) const;
   bool hasDetectors(const size_t index) const;
   bool hasUniqueDetector(const size_t index) const;
 
@@ -86,6 +93,14 @@ public:
   Kernel::V3D samplePosition() const;
   double l1() const;
 
+  void getDetectorValues(const Kernel::Unit &inputUnit,
+                         const Kernel::Unit &outputUnit,
+                         const Kernel::DeltaEMode::Type emode,
+                         const bool signedTheta, int64_t wsIndex,
+                         Kernel::UnitParametersMap &pmap) const;
+  void createDetectorIdLogMessages(const std::vector<detid_t> &detids,
+                                   int64_t wsIndex) const;
+
   SpectrumInfoIterator<SpectrumInfo> begin();
   SpectrumInfoIterator<SpectrumInfo> end();
   const SpectrumInfoIterator<const SpectrumInfo> cbegin() const;
diff --git a/Framework/API/src/ExperimentInfo.cpp b/Framework/API/src/ExperimentInfo.cpp
index f37808dbdc7f59f86120cf56d440707835edacd5..2192b7154392dc9896766721bdd8c49855685b4b 100644
--- a/Framework/API/src/ExperimentInfo.cpp
+++ b/Framework/API/src/ExperimentInfo.cpp
@@ -676,39 +676,70 @@ double ExperimentInfo::getEFixed(
     const std::shared_ptr<const Geometry::IDetector> &detector) const {
   populateIfNotLoaded();
   Kernel::DeltaEMode::Type emode = getEMode();
-  if (emode == Kernel::DeltaEMode::Direct) {
-    try {
-      return this->run().getPropertyValueAsType<double>("Ei");
-    } catch (Kernel::Exception::NotFoundError &) {
-      throw std::runtime_error(
-          "Experiment logs do not contain an Ei value. Have you run GetEi?");
-    }
-  } else if (emode == Kernel::DeltaEMode::Indirect) {
-    if (!detector)
-      throw std::runtime_error("ExperimentInfo::getEFixed - Indirect mode "
-                               "efixed requested without a valid detector.");
+  return getEFixedGivenEMode(detector, emode);
+}
+
+double ExperimentInfo::getEFixedForIndirect(
+    const std::shared_ptr<const Geometry::IDetector> &detector,
+    const std::vector<std::string> &parameterNames) const {
+  double efixed = 0.;
+  for (auto &parameterName : parameterNames) {
     Parameter_sptr par =
-        constInstrumentParameters().getRecursive(detector.get(), "Efixed");
+        constInstrumentParameters().getRecursive(detector.get(), parameterName);
     if (par) {
-      return par->value<double>();
+      efixed = par->value<double>();
     } else {
-      std::vector<double> efixedVec = detector->getNumberParameter("Efixed");
+      std::vector<double> efixedVec =
+          detector->getNumberParameter(parameterName);
       if (efixedVec.empty()) {
         int detid = detector->getID();
         IDetector_const_sptr detectorSingle =
             getInstrument()->getDetector(detid);
-        efixedVec = detectorSingle->getNumberParameter("Efixed");
+        efixedVec = detectorSingle->getNumberParameter(parameterName);
       }
       if (!efixedVec.empty()) {
-        return efixedVec.at(0);
-      } else {
-        std::ostringstream os;
-        os << "ExperimentInfo::getEFixed - Indirect mode efixed requested but "
-              "detector has no Efixed parameter attached. ID="
-           << detector->getID();
-        throw std::runtime_error(os.str());
+        efixed = efixedVec.at(0);
+      }
+    }
+  }
+  if (efixed == 0.) {
+    std::ostringstream os;
+    os << "ExperimentInfo::getEFixed - Indirect mode efixed requested but "
+          "detector has no Efixed parameter attached. ID="
+       << detector->getID();
+    throw std::runtime_error(os.str());
+  }
+  return efixed;
+}
+
+/**
+ * Easy access to the efixed value for this run & detector
+ * @param detector :: The detector object to ask for the efixed mode. Only
+ * required for Indirect mode
+ * @param emode :: enum value indicating whether elastic, direct or indirect
+ * @return The current efixed value
+ */
+double ExperimentInfo::getEFixedGivenEMode(
+    const std::shared_ptr<const Geometry::IDetector> &detector,
+    const Kernel::DeltaEMode::Type emode) const {
+  if (emode == Kernel::DeltaEMode::Direct) {
+    double efixed = 0.;
+    for (auto &parameterName : {"Ei", "EnergyRequested", "EnergyEstimate"}) {
+      if (run().hasProperty(parameterName)) {
+        efixed = run().getPropertyValueAsType<double>(parameterName);
+        break;
       }
     }
+    if (efixed == 0.) {
+      throw std::runtime_error("Experiment logs do not contain an Ei "
+                               "value. Have you run GetEi?");
+    }
+    return efixed;
+  } else if (emode == Kernel::DeltaEMode::Indirect) {
+    if (!detector)
+      throw std::runtime_error("ExperimentInfo::getEFixed - Indirect mode "
+                               "efixed requested without a valid detector.");
+    return getEFixedForIndirect(detector, {"Efixed", "EFixed-val"});
   } else {
     throw std::runtime_error("ExperimentInfo::getEFixed - EFixed requested for "
                              "elastic mode, don't know what to do!");
diff --git a/Framework/API/src/IFunction.cpp b/Framework/API/src/IFunction.cpp
index a69fe78f87fb4154727c4f6c5233181a6765e29b..9cc5aec67eadb004a3c8d4e5020c9a6c9173cfe6 100644
--- a/Framework/API/src/IFunction.cpp
+++ b/Framework/API/src/IFunction.cpp
@@ -1143,12 +1143,12 @@ void IFunction::setMatrixWorkspace(
               if (centreUnit) {
                 g_log.debug()
                     << "For FitParameter " << parameterName(i)
-                    << " centre of peak before any unit convertion is "
+                    << " centre of peak before any unit conversion is "
                     << centreValue << '\n';
                 centreValue =
                     convertValue(centreValue, centreUnit, workspace, wi);
                 g_log.debug() << "For FitParameter " << parameterName(i)
-                              << " centre of peak after any unit convertion is "
+                              << " centre of peak after any unit conversion is "
                               << centreValue << '\n';
               }
 
@@ -1165,11 +1165,11 @@ void IFunction::setMatrixWorkspace(
                     fitParam.getLookUpTable().getYUnit(); // from table
                 g_log.debug()
                     << "The FitParameter " << parameterName(i) << " = "
-                    << paramValue << " before y-unit convertion\n";
+                    << paramValue << " before y-unit conversion\n";
                 paramValue /= convertValue(1.0, resultUnit, workspace, wi);
                 g_log.debug()
                     << "The FitParameter " << parameterName(i) << " = "
-                    << paramValue << " after y-unit convertion\n";
+                    << paramValue << " after y-unit conversion\n";
               } else {
                 // so from formula
 
@@ -1195,12 +1195,12 @@ void IFunction::setMatrixWorkspace(
                     p.SetExpr(resultUnitStr);
                     g_log.debug() << "The FitParameter " << parameterName(i)
                                   << " = " << paramValue
-                                  << " before result-unit convertion (using "
+                                  << " before result-unit conversion (using "
                                   << resultUnitStr << ")\n";
                     paramValue *= p.Eval();
                     g_log.debug()
                         << "The FitParameter " << parameterName(i) << " = "
-                        << paramValue << " after result-unit convertion\n";
+                        << paramValue << " after result-unit conversion\n";
                   } catch (mu::Parser::exception_type &e) {
                     g_log.error()
                         << "Cannot convert formula unit to workspace unit"
@@ -1308,31 +1308,25 @@ void IFunction::convertValue(std::vector<double> &values,
     if (sample == nullptr) {
       g_log.error()
           << "No sample defined instrument. Cannot convert units for function\n"
-          << "Ignore convertion.";
+          << "Ignore conversion.";
       return;
     }
     const auto &spectrumInfo = ws->spectrumInfo();
     double l1 = spectrumInfo.l1();
     // If this is a monitor then l1+l2 = source-detector distance and twoTheta=0
-    double l2 = spectrumInfo.l2(wsIndex);
-    double twoTheta(0.0);
-    if (!spectrumInfo.isMonitor(wsIndex))
-      twoTheta = spectrumInfo.twoTheta(wsIndex);
-    auto emode = static_cast<int>(ws->getEMode());
-    double efixed(0.0);
+    auto emode = ws->getEMode();
+
+    Kernel::UnitParametersMap pmap{};
+    spectrumInfo.getDetectorValues(*wsUnit, *outUnit, emode, false, wsIndex,
+                                   pmap);
+    std::vector<double> emptyVec;
     try {
-      std::shared_ptr<const Geometry::IDetector> det(
-          &spectrumInfo.detector(wsIndex), NoDeleting());
-      efixed = ws->getEFixed(det);
+      wsUnit->toTOF(values, emptyVec, l1, emode, pmap);
+      outUnit->fromTOF(values, emptyVec, l1, emode, pmap);
     } catch (std::exception &) {
-      // assume elastic
-      efixed = 0.0;
-      emode = 0;
+      throw std::runtime_error("Unable to perform unit conversion to " +
+                               outUnit->unitID());
     }
-
-    std::vector<double> emptyVec;
-    wsUnit->toTOF(values, emptyVec, l1, l2, twoTheta, emode, efixed, 0.0);
-    outUnit->fromTOF(values, emptyVec, l1, l2, twoTheta, emode, efixed, 0.0);
   }
 }
 
diff --git a/Framework/API/src/SpectrumInfo.cpp b/Framework/API/src/SpectrumInfo.cpp
index 50b1ec904cc1b8b1f61947003bf88726b2884748..3cc369cac7d7cd38730aeeaa6f2ef4f8f7b4d06e 100644
--- a/Framework/API/src/SpectrumInfo.cpp
+++ b/Framework/API/src/SpectrumInfo.cpp
@@ -8,17 +8,23 @@
 #include "MantidAPI/ExperimentInfo.h"
 #include "MantidAPI/SpectrumInfoIterator.h"
 #include "MantidBeamline/SpectrumInfo.h"
+#include "MantidGeometry/Instrument.h"
 #include "MantidGeometry/Instrument/DetectorGroup.h"
 #include "MantidGeometry/Instrument/DetectorInfo.h"
 #include "MantidKernel/Exception.h"
+#include "MantidKernel/Logger.h"
 #include "MantidKernel/MultiThreaded.h"
 #include "MantidTypes/SpectrumDefinition.h"
 
 #include <algorithm>
 #include <memory>
 
+using namespace Mantid::Kernel;
+
 namespace Mantid {
 namespace API {
+/// static logger object
+Kernel::Logger g_log("ExperimentInfo");
 
 SpectrumInfo::SpectrumInfo(const Beamline::SpectrumInfo &spectrumInfo,
                            const ExperimentInfo &experimentInfo,
@@ -140,7 +146,175 @@ Kernel::V3D SpectrumInfo::position(const size_t index) const {
   return newPos / static_cast<double>(spectrumDefinition(index).size());
 }
 
-/// Returns true if the spectrum is associated with detectors in the instrument.
+/** Calculate average diffractometer constants (DIFA, DIFC, TZERO) of detectors
+ * associated with this spectrum. Use calibrated values where possible, filling
+ * in with uncalibrated values where they're missing
+ *  @param index Index of the spectrum that constants are required for
+ *  @param warningDets A vector containing the det ids where an uncalibrated
+ * value was used in the situation where some dets have calibrated values and
+ * some don't
+ *  @return map containing the average constants
+ */
+UnitParametersMap
+SpectrumInfo::diffractometerConstants(const size_t index,
+                                      std::vector<detid_t> &warningDets) const {
+  if (m_detectorInfo.isScanning()) {
+    throw std::runtime_error("Retrieval of diffractometer constants not "
+                             "implemented for scanning instrument");
+  }
+  auto spectrumDef = checkAndGetSpectrumDefinition(index);
+  std::vector<size_t> detectorIndicesOnly;
+  std::vector<detid_t> calibratedDets;
+  std::vector<detid_t> uncalibratedDets;
+  std::transform(spectrumDef.begin(), spectrumDef.end(),
+                 std::back_inserter(detectorIndicesOnly),
+                 [](auto const &pair) { return pair.first; });
+  double difa{0.}, difc{0.}, tzero{0.};
+  for (const auto &detIndex : detectorIndicesOnly) {
+    auto newDiffConstants = m_detectorInfo.diffractometerConstants(
+        detIndex, calibratedDets, uncalibratedDets);
+    difa += std::get<0>(newDiffConstants);
+    difc += std::get<1>(newDiffConstants);
+    tzero += std::get<2>(newDiffConstants);
+  }
+
+  if (calibratedDets.size() > 0 && uncalibratedDets.size() > 0) {
+    warningDets.insert(warningDets.end(), uncalibratedDets.begin(),
+                       uncalibratedDets.end());
+  };
+  // if no calibration is found then return difc only based on the average
+  // of the detector L2 and twoThetas.
+  if (calibratedDets.size() == 0) {
+    return {{UnitParams::difc, difcUncalibrated(index)}};
+  }
+  return {{UnitParams::difa,
+           difa / static_cast<double>(spectrumDefinition(index).size())},
+          {UnitParams::difc,
+           difc / static_cast<double>(spectrumDefinition(index).size())},
+          {UnitParams::tzero,
+           tzero / static_cast<double>(spectrumDefinition(index).size())}};
+}
+
+/** Calculate average diffractometer constants (DIFA, DIFC, TZERO) of
+ * detectors associated with this spectrum. Use calibrated values where
+ * possible, filling in with uncalibrated values where they're missing
+ *  @param index Index of the spectrum that constants are required for
+ *  @return map containing the average constants
+ */
+UnitParametersMap
+SpectrumInfo::diffractometerConstants(const size_t index) const {
+  std::vector<int> warningDets;
+  return diffractometerConstants(index, warningDets);
+}
+
+/** Calculate average uncalibrated DIFC value of detectors associated with
+ * this spectrum
+ *  @param index Index of the spectrum that DIFC is required for
+ *  @return The average DIFC
+ */
+double SpectrumInfo::difcUncalibrated(const size_t index) const {
+  // calculate difc based on the average of the detector L2 and twoThetas.
+  // This will be different to the average of the per detector difcs. This is
+  // for backwards compatibility because Mantid always used to calculate
+  // spectrum level difc's this way
+  return 1. / Kernel::Units::tofToDSpacingFactor(l1(), l2(index),
+                                                 twoTheta(index), 0.);
+}
+
+/** Get the detector values relevant to unit conversion for a workspace index
+ * @param inputUnit :: The input unit (Empty implies "all")
+ * @param outputUnit :: The output unit (Empty implies "all")
+ * @param emode :: The energy mode
+ * @param signedTheta :: Return twotheta with sign or without
+ * @param wsIndex :: The workspace index
+ * @param pmap :: a map containing values for conversion parameters that are
+required by unit classes to perform their conversions eg efixed. It can
+contain values on the way in if a look up isn't desired here eg if value
+supplied in parameters to the calling algorithm
+ */
+void SpectrumInfo::getDetectorValues(const Kernel::Unit &inputUnit,
+                                     const Kernel::Unit &outputUnit,
+                                     const Kernel::DeltaEMode::Type emode,
+                                     const bool signedTheta, int64_t wsIndex,
+                                     UnitParametersMap &pmap) const {
+  if (!hasDetectors(wsIndex))
+    return;
+  pmap[UnitParams::l2] = l2(wsIndex);
+
+  if (!isMonitor(wsIndex)) {
+    // The scattering angle for this detector (in radians).
+    try {
+      if (signedTheta)
+        pmap[UnitParams::twoTheta] = signedTwoTheta(wsIndex);
+      else
+        pmap[UnitParams::twoTheta] = twoTheta(wsIndex);
+    } catch (const std::runtime_error &e) {
+      g_log.warning(e.what());
+    }
+    if (emode != Kernel::DeltaEMode::Elastic &&
+        pmap.find(UnitParams::efixed) == pmap.end()) {
+      std::shared_ptr<const Geometry::IDetector> det(&detector(wsIndex),
+                                                     Mantid::NoDeleting());
+      try {
+        pmap[UnitParams::efixed] =
+            m_experimentInfo.getEFixedGivenEMode(det, emode);
+        g_log.debug() << "Detector: " << det->getID()
+                      << " EFixed: " << pmap[UnitParams::efixed] << "\n";
+      } catch (std::runtime_error) {
+        // let the unit classes work out if this is a problem
+      }
+    }
+
+    std::vector<detid_t> warnDetIds;
+    try {
+      std::set<std::string> diffConstUnits = {"dSpacing", "MomentumTransfer",
+                                              "Empty"};
+      if ((emode == Kernel::DeltaEMode::Elastic) &&
+          (diffConstUnits.count(inputUnit.unitID()) ||
+           diffConstUnits.count(outputUnit.unitID()))) {
+        auto diffConstsMap = diffractometerConstants(wsIndex, warnDetIds);
+        pmap.insert(diffConstsMap.begin(), diffConstsMap.end());
+        if (warnDetIds.size() > 0) {
+          createDetectorIdLogMessages(warnDetIds, wsIndex);
+        }
+      } else {
+        pmap[UnitParams::difc] = difcUncalibrated(wsIndex);
+      }
+    } catch (const std::runtime_error &e) {
+      g_log.warning(e.what());
+    }
+  } else {
+    pmap[UnitParams::twoTheta] = 0.0;
+    pmap[UnitParams::efixed] = DBL_MIN;
+    // Energy transfer is meaningless for a monitor, so set l2 to 0.
+    if (outputUnit.unitID().find("DeltaE") != std::string::npos) {
+      pmap[UnitParams::l2] = 0.0;
+    }
+    pmap[UnitParams::difc] = 0;
+  }
+}
+
+void SpectrumInfo::createDetectorIdLogMessages(
+    const std::vector<detid_t> &detids, int64_t wsIndex) const {
+  std::string detIDstring;
+  auto iter = detids.begin();
+  auto itEnd = detids.end();
+  for (; iter != itEnd; ++iter) {
+    detIDstring += std::to_string(*iter) + ",";
+  }
+
+  if (!detIDstring.empty()) {
+    detIDstring.pop_back(); // Drop last comma
+  }
+  g_log.warning(
+      "Incomplete set of calibrated diffractometer constants found for "
+      "workspace index" +
+      std::to_string(wsIndex) + ". Using uncalibrated values for detectors " +
+      detIDstring);
+}
+
+/// Returns true if the spectrum is associated with detectors in the
+/// instrument.
 bool SpectrumInfo::hasDetectors(const size_t index) const {
   // Workspaces can contain invalid detector IDs. Those IDs will be silently
   // ignored here until this is fixed.
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/AlignDetectors.h b/Framework/Algorithms/inc/MantidAlgorithms/AlignDetectors.h
index b2dba6d8716db4b3252f7a350b47245f59f04e81..c2fc047d5333b6088d9b62f3916de7fb7f3b4800 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/AlignDetectors.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/AlignDetectors.h
@@ -7,6 +7,7 @@
 #pragma once
 
 #include "MantidAPI/Algorithm.h"
+#include "MantidAPI/DeprecatedAlgorithm.h"
 #include "MantidAPI/ITableWorkspace_fwd.h"
 #include "MantidAlgorithms/DllConfig.h"
 #include "MantidDataObjects/OffsetsWorkspace.h"
@@ -39,7 +40,8 @@ class ConversionFactors;
     @author Russell Taylor, Tessella Support Services plc
     @date 18/08/2008
 */
-class MANTID_ALGORITHMS_DLL AlignDetectors : public API::Algorithm {
+class MANTID_ALGORITHMS_DLL AlignDetectors : public API::Algorithm,
+                                             public API::DeprecatedAlgorithm {
 public:
   AlignDetectors();
 
@@ -69,9 +71,7 @@ private:
   void exec() override;
 
   void align(const ConversionFactors &converter, API::Progress &progress,
-             API::MatrixWorkspace &outputWS);
-  void align(const ConversionFactors &converter, API::Progress &progress,
-             DataObjects::EventWorkspace &outputWS);
+             API::MatrixWorkspace_sptr &outputWS);
 
   void loadCalFile(const API::MatrixWorkspace_sptr &inputWS,
                    const std::string &filename);
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/CompareWorkspaces.h b/Framework/Algorithms/inc/MantidAlgorithms/CompareWorkspaces.h
index 32a5a735761e72b85a94a38125b1622f16ad05d0..d64deaf90f119f94081863d78cd05725f7b1e42b 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/CompareWorkspaces.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/CompareWorkspaces.h
@@ -10,6 +10,7 @@
 #include "MantidAPI/ITableWorkspace_fwd.h"
 #include "MantidAlgorithms/DllConfig.h"
 #include "MantidDataObjects/EventWorkspace.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 
 namespace Mantid {
@@ -103,6 +104,9 @@ private:
 
   void doPeaksComparison(DataObjects::PeaksWorkspace_sptr tws1,
                          DataObjects::PeaksWorkspace_sptr tws2);
+  void doLeanElasticPeaksComparison(
+      DataObjects::LeanElasticPeaksWorkspace_sptr tws1,
+      DataObjects::LeanElasticPeaksWorkspace_sptr tws2);
   void doTableComparison(const API::ITableWorkspace_const_sptr &tws1,
                          const API::ITableWorkspace_const_sptr &tws2);
   void doMDComparison(const API::Workspace_sptr &w1,
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/ConvertUnits.h b/Framework/Algorithms/inc/MantidAlgorithms/ConvertUnits.h
index aef3ddfa017ab62fa36e41e4a8270af7d220f1f9..ddf1e23f7ddf2683ad6a01d8ca84e03eb0eae29f 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/ConvertUnits.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/ConvertUnits.h
@@ -103,13 +103,6 @@ protected:
   convertQuickly(const API::MatrixWorkspace_const_sptr &inputWS,
                  const double &factor, const double &power);
 
-  /// Internal function to gather detector specific L2, theta and efixed values
-  bool getDetectorValues(const API::SpectrumInfo &spectrumInfo,
-                         const Kernel::Unit &outputUnit, int emode,
-                         const API::MatrixWorkspace &ws, const bool signedTheta,
-                         int64_t wsIndex, double &efixed, double &l2,
-                         double &twoTheta);
-
   /// Convert the workspace units using TOF as an intermediate step in the
   /// conversion
   virtual API::MatrixWorkspace_sptr
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/CreateDetectorTable.h b/Framework/Algorithms/inc/MantidAlgorithms/CreateDetectorTable.h
index 3f13c9c229b45d4df5db2c770447b58f6093a236..bfd4aaf2033ace55bbc8b770c87d792f5613145f 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/CreateDetectorTable.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/CreateDetectorTable.h
@@ -68,9 +68,10 @@ void populateTable(Mantid::API::ITableWorkspace_sptr &t,
                    const Mantid::Geometry::PointingAlong &beamAxisIndex,
                    const double sampleDist, const bool isScanning,
                    const bool include_data, const bool calcQ,
-                   Kernel::Logger &logger);
+                   const bool includeDiffConstants, Kernel::Logger &logger);
 std::vector<std::pair<std::string, std::string>>
-createColumns(const bool isScanning, const bool includeData, const bool calcQ);
+createColumns(const bool isScanning, const bool includeData, const bool calcQ,
+              const bool hasDiffConstants);
 
 } // namespace Algorithms
 } // namespace Mantid
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/PDCalibration.h b/Framework/Algorithms/inc/MantidAlgorithms/PDCalibration.h
index e4affc14c7eab18ad1b6a29457044ab7041dc17b..16a284bb66c5dad6a9009706297c44cc36db6f0c 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/PDCalibration.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/PDCalibration.h
@@ -44,7 +44,7 @@ private:
   void createCalTableFromExisting();
   void createCalTableNew();
   void createInformationWorkspaces();
-  std::function<double(double)>
+  std::tuple<double, double, double>
   getDSpacingToTof(const std::set<detid_t> &detIds);
   std::vector<double> dSpacingWindows(const std::vector<double> &centres,
                                       const double widthMax);
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/RemoveBackground.h b/Framework/Algorithms/inc/MantidAlgorithms/RemoveBackground.h
index 0b4cbfa584c6d44cc0a73a4f0109126e2d7bf5a5..66183e705f52cfb9c1cc38f5d6cd4348d10edb34 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/RemoveBackground.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/RemoveBackground.h
@@ -9,6 +9,7 @@
 #include "MantidAPI/Algorithm.h"
 #include "MantidAlgorithms/DllConfig.h"
 #include "MantidGeometry/IComponent.h"
+#include "MantidKernel/DeltaEMode.h"
 #include "MantidKernel/cow_ptr.h"
 
 namespace Mantid {
@@ -46,7 +47,8 @@ public:
   BackgroundHelper(const BackgroundHelper &) = delete;
 
   void initialize(const API::MatrixWorkspace_const_sptr &bkgWS,
-                  const API::MatrixWorkspace_sptr &sourceWS, int emode,
+                  const API::MatrixWorkspace_sptr &sourceWS,
+                  Kernel::DeltaEMode::Type emode,
                   Kernel::Logger *pLog = nullptr, int nThreads = 1,
                   bool inPlace = true, bool nullifyNegative = false);
   void removeBackground(int nHist, HistogramData::HistogramX &x_data,
@@ -81,16 +83,11 @@ private:
   // workspace
   double m_ErrSq;
   // energy conversion mode
-  int m_Emode;
-  // incident for direct or analysis for indirect energy for units conversion
-  double m_Efix;
+  Kernel::DeltaEMode::Type m_Emode;
   // if true, negative signals are nullified
   bool m_nullifyNegative;
   // removing negative values from ws with background removed previously.
   bool m_previouslyRemovedBkgMode;
-
-  // get Ei attached to direct or indirect instrument workspace
-  double getEi(const API::MatrixWorkspace_const_sptr &inputWS) const;
 };
 
 class MANTID_ALGORITHMS_DLL RemoveBackground : public API::Algorithm {
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/RemoveBins.h b/Framework/Algorithms/inc/MantidAlgorithms/RemoveBins.h
index b3862afea157cd89bf1012e731ef06ec3753c539..4e4b15b3ef576f84d2e81f4795f5c635d5384d09 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/RemoveBins.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/RemoveBins.h
@@ -72,8 +72,6 @@ private:
 
   void crop(const double &start, const double &end);
   void transformRangeUnit(const int index, double &startX, double &endX);
-  void calculateDetectorPosition(const int index, double &l1, double &l2,
-                                 double &twoTheta);
   int findIndex(const double &value, const HistogramData::HistogramX &vec);
   void RemoveFromEnds(int start, int end, HistogramData::HistogramY &Y,
                       HistogramData::HistogramE &E);
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/IMCInteractionVolume.h b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/IMCInteractionVolume.h
index 9e090711f2f0f9ed5a2c000c05bb5f5a4871a0ee..7873ffa3980cddf938744c96bd31d4118bf4f537 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/IMCInteractionVolume.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/IMCInteractionVolume.h
@@ -40,7 +40,6 @@ public:
   virtual TrackPair calculateBeforeAfterTrack(
       Kernel::PseudoRandomNumberGenerator &rng, const Kernel::V3D &startPos,
       const Kernel::V3D &endPos, MCInteractionStatistics &stats) const = 0;
-  virtual const Geometry::BoundingBox &getBoundingBox() const = 0;
   virtual const Geometry::BoundingBox getFullBoundingBox() const = 0;
   virtual void setActiveRegion(const Geometry::BoundingBox &region) = 0;
 };
diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h
index b479cf6249b9d81a0d751fcd38aa558fe7f4a93e..a4743e3a31ff6d0dde89fa810966023aa53ea331 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h
@@ -37,7 +37,6 @@ public:
                       const ScatteringPointVicinity pointsIn =
                           ScatteringPointVicinity::SAMPLEANDENVIRONMENT);
 
-  const Geometry::BoundingBox &getBoundingBox() const override;
   const Geometry::BoundingBox getFullBoundingBox() const override;
   virtual TrackPair calculateBeforeAfterTrack(
       Kernel::PseudoRandomNumberGenerator &rng, const Kernel::V3D &startPos,
diff --git a/Framework/Algorithms/src/AddPeak.cpp b/Framework/Algorithms/src/AddPeak.cpp
index 63be9394c4754b16ef801b5e7dd3060afa14b3e2..32b45a3b594478ed60e56bf64f66b322345ec63c 100644
--- a/Framework/Algorithms/src/AddPeak.cpp
+++ b/Framework/Algorithms/src/AddPeak.cpp
@@ -83,6 +83,9 @@ void AddPeak::exec() {
   double Qz = 1.0 - cos(theta2);
   double l1 = detectorInfo.l1();
   double l2 = detectorInfo.l2(detectorIndex);
+  std::vector<int> emptyWarningVec;
+  auto [difa, difc, tzero] = detectorInfo.diffractometerConstants(
+      detectorIndex, emptyWarningVec, emptyWarningVec);
 
   Mantid::Kernel::Unit_sptr unit = runWS->getAxis(0)->unit();
   if (unit->unitID() != "TOF") {
@@ -112,7 +115,13 @@ void AddPeak::exec() {
     }
     std::vector<double> xdata(1, tof);
     std::vector<double> ydata;
-    unit->toTOF(xdata, ydata, l1, l2, theta2, emode, efixed, 0.0);
+    unit->toTOF(xdata, ydata, l1, emode,
+                {{Kernel::UnitParams::l2, l2},
+                 {Kernel::UnitParams::twoTheta, theta2},
+                 {Kernel::UnitParams::efixed, efixed},
+                 {Kernel::UnitParams::difa, difa},
+                 {Kernel::UnitParams::difc, difc},
+                 {Kernel::UnitParams::tzero, tzero}});
     tof = xdata[0];
   }
 
diff --git a/Framework/Algorithms/src/AlignDetectors.cpp b/Framework/Algorithms/src/AlignDetectors.cpp
index 7b2e08c9c10e5e4e359ed34618e036d174f04d0f..b9981513252d6e6b6a7cab8a61cf23804988e187 100644
--- a/Framework/Algorithms/src/AlignDetectors.cpp
+++ b/Framework/Algorithms/src/AlignDetectors.cpp
@@ -17,7 +17,6 @@
 #include "MantidDataObjects/EventWorkspace.h"
 #include "MantidDataObjects/OffsetsWorkspace.h"
 #include "MantidKernel/CompositeValidator.h"
-#include "MantidKernel/Diffraction.h"
 #include "MantidKernel/PhysicalConstants.h"
 #include "MantidKernel/UnitFactory.h"
 #include "MantidKernel/V3D.h"
@@ -49,8 +48,8 @@ public:
     this->generateDetidToRow(table);
   }
 
-  std::function<double(double)>
-  getConversionFunc(const std::set<detid_t> &detIds) const {
+  std::tuple<double, double, double>
+  getDiffConstants(const std::set<detid_t> &detIds) const {
     const std::set<size_t> rows = this->getRow(detIds);
     double difc = 0.;
     double difa = 0.;
@@ -67,7 +66,7 @@ public:
       tzero = norm * tzero;
     }
 
-    return Kernel::Diffraction::getTofToDConversionFunc(difc, difa, tzero);
+    return {difc, difa, tzero};
   }
 
 private:
@@ -87,6 +86,16 @@ private:
         rows.insert(rowIter->second);
       }
     }
+    if (rows.empty()) {
+      std::string detIdsStr = std::accumulate(
+          std::begin(detIds), std::end(detIds), std::string{},
+          [](const std::string &a, const detid_t &b) {
+            return a.empty() ? std::to_string(b) : a + ',' + std::to_string(b);
+          });
+      throw Exception::NotFoundError(
+          "None of the detectors were found in the calibration table",
+          detIdsStr);
+    }
     return rows;
   }
 
@@ -110,8 +119,11 @@ const std::string AlignDetectors::summary() const {
          "values to account for small errors in the detector positions.";
 }
 
-/// (Empty) Constructor
-AlignDetectors::AlignDetectors() : m_numberOfSpectra(0) {}
+/// Constructor
+AlignDetectors::AlignDetectors() : m_numberOfSpectra(0) {
+  useAlgorithm("ConvertUnits");
+  deprecatedDate("2021-01-04");
+}
 
 void AlignDetectors::init() {
   auto wsValidator = std::make_shared<CompositeValidator>();
@@ -262,60 +274,58 @@ void AlignDetectors::exec() {
 
   Progress progress(this, 0.0, 1.0, m_numberOfSpectra);
 
-  auto eventW = std::dynamic_pointer_cast<EventWorkspace>(outputWS);
-  if (eventW) {
-    align(converter, progress, *eventW);
-  } else {
-    align(converter, progress, *outputWS);
-  }
+  align(converter, progress, outputWS);
 }
 
 void AlignDetectors::align(const ConversionFactors &converter,
-                           Progress &progress, MatrixWorkspace &outputWS) {
-  PARALLEL_FOR_IF(Kernel::threadSafe(outputWS))
+                           Progress &progress, MatrixWorkspace_sptr &outputWS) {
+  auto eventW = std::dynamic_pointer_cast<EventWorkspace>(outputWS);
+  PARALLEL_FOR_IF(Kernel::threadSafe(*outputWS))
   for (int64_t i = 0; i < m_numberOfSpectra; ++i) {
     PARALLEL_START_INTERUPT_REGION
     try {
       // Get the input spectrum number at this workspace index
-      auto &spec = outputWS.getSpectrum(size_t(i));
-      auto toDspacing = converter.getConversionFunc(spec.getDetectorIDs());
-
-      auto &x = outputWS.mutableX(i);
-      std::transform(x.begin(), x.end(), x.begin(), toDspacing);
-    } catch (const Exception::NotFoundError &) {
-      // Zero the data in this case
-      outputWS.setHistogram(i, BinEdges(outputWS.x(i).size()),
-                            Counts(outputWS.y(i).size()));
+      auto &spec = outputWS->getSpectrum(size_t(i));
+      auto [difc, difa, tzero] =
+          converter.getDiffConstants(spec.getDetectorIDs());
+
+      auto &x = outputWS->dataX(i);
+      Kernel::Units::dSpacing dSpacingUnit;
+      std::vector<double> yunused;
+      dSpacingUnit.fromTOF(
+          x, yunused, -1., 0,
+          UnitParametersMap{{Kernel::UnitParams::difa, difa},
+                            {Kernel::UnitParams::difc, difc},
+                            {Kernel::UnitParams::tzero, tzero}});
+
+      if (eventW) {
+        Kernel::Units::TOF tofUnit;
+        tofUnit.initialize(0, 0, {});
+        // EventWorkspace part, modifying the EventLists.
+        eventW->getSpectrum(i).convertUnitsViaTof(&tofUnit, &dSpacingUnit);
+      }
+    } catch (const std::runtime_error &) {
+      if (!eventW) {
+        // Zero the data in this case (detectors not found in cal table or
+        // conversion fails)
+        outputWS->setHistogram(i, BinEdges(outputWS->x(i).size()),
+                               Counts(outputWS->y(i).size()));
+      }
     }
     progress.report();
     PARALLEL_END_INTERUPT_REGION
   }
   PARALLEL_CHECK_INTERUPT_REGION
-}
-
-void AlignDetectors::align(const ConversionFactors &converter,
-                           Progress &progress, EventWorkspace &outputWS) {
-  PARALLEL_FOR_NO_WSP_CHECK()
-  for (int64_t i = 0; i < m_numberOfSpectra; ++i) {
-    PARALLEL_START_INTERUPT_REGION
-
-    auto toDspacing = converter.getConversionFunc(
-        outputWS.getSpectrum(size_t(i)).getDetectorIDs());
-    outputWS.getSpectrum(i).convertTof(toDspacing);
-
-    progress.report();
-    PARALLEL_END_INTERUPT_REGION
-  }
-  PARALLEL_CHECK_INTERUPT_REGION
-
-  if (outputWS.getTofMin() < 0.) {
-    std::stringstream msg;
-    msg << "Something wrong with the calibration. Negative minimum d-spacing "
-           "created. d_min = "
-        << outputWS.getTofMin() << " d_max " << outputWS.getTofMax();
-    g_log.warning(msg.str());
+  if (eventW) {
+    if (eventW->getTofMin() < 0.) {
+      std::stringstream msg;
+      msg << "Something wrong with the calibration. Negative minimum d-spacing "
+             "created. d_min = "
+          << eventW->getTofMin() << " d_max " << eventW->getTofMax();
+      g_log.warning(msg.str());
+    }
+    eventW->clearMRU();
   }
-  outputWS.clearMRU();
 }
 
 Parallel::ExecutionMode AlignDetectors::getParallelExecutionMode(
diff --git a/Framework/Algorithms/src/CalculatePlaczekSelfScattering.cpp b/Framework/Algorithms/src/CalculatePlaczekSelfScattering.cpp
index 3417c43b262daedffa95b5c09e2977d3113e7a34..c4fdc58b3727dc7b607352d10f29d3981c848582 100644
--- a/Framework/Algorithms/src/CalculatePlaczekSelfScattering.cpp
+++ b/Framework/Algorithms/src/CalculatePlaczekSelfScattering.cpp
@@ -163,12 +163,23 @@ void CalculatePlaczekSelfScattering::exec() {
     auto &y = outputWS->mutableY(specIndex);
     auto &x = outputWS->mutableX(specIndex);
     if (!specInfo.isMonitor(specIndex) && !(specInfo.l2(specIndex) == 0.0)) {
-      const double pathLength = specInfo.l1() + specInfo.l2(specIndex);
-      const double f = specInfo.l1() / pathLength;
-      const double sinThetaBy2 = sin(specInfo.twoTheta(specIndex) / 2.0);
       Kernel::Units::Wavelength wavelength;
-      wavelength.initialize(specInfo.l1(), specInfo.l2(specIndex),
-                            specInfo.twoTheta(specIndex), 0, 1.0, 1.0);
+      Kernel::Units::TOF tof;
+      Kernel::UnitParametersMap pmap{};
+      double l1 = specInfo.l1();
+      specInfo.getDetectorValues(wavelength, tof, Kernel::DeltaEMode::Elastic,
+                                 false, specIndex, pmap);
+      double l2 = 0., twoTheta = 0.;
+      if (pmap.find(Kernel::UnitParams::l2) != pmap.end()) {
+        l2 = pmap[Kernel::UnitParams::l2];
+      }
+      if (pmap.find(Kernel::UnitParams::twoTheta) != pmap.end()) {
+        twoTheta = pmap[Kernel::UnitParams::twoTheta];
+      }
+
+      const double sinThetaBy2 = sin(twoTheta / 2.0);
+      const double f = l1 / (l1 + l2);
+      wavelength.initialize(specInfo.l1(), 0, pmap);
       for (size_t xIndex = 0; xIndex < xLambda.size() - 1; xIndex++) {
         const double term1 = (f - 1.0) * phi1[xIndex];
         const double term2 = f * (1.0 - eps1[xIndex]);
diff --git a/Framework/Algorithms/src/CompareWorkspaces.cpp b/Framework/Algorithms/src/CompareWorkspaces.cpp
index 6c435f3ce71cd790e4f99f0a097a2d96c3f2d8d2..f56514c5d18c0cc2bec147dc77858127a0bddb44 100644
--- a/Framework/Algorithms/src/CompareWorkspaces.cpp
+++ b/Framework/Algorithms/src/CompareWorkspaces.cpp
@@ -16,6 +16,7 @@
 #include "MantidAPI/TableRow.h"
 #include "MantidAPI/WorkspaceGroup.h"
 #include "MantidDataObjects/EventWorkspace.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidDataObjects/TableWorkspace.h"
 #include "MantidGeometry/Crystal/IPeak.h"
@@ -322,18 +323,43 @@ void CompareWorkspaces::doComparison() {
   // ==============================================================================
   // Peaks workspaces
   // ==============================================================================
+  if (w1->id() == "PeaksWorkspace" || w2->id() == "PeaksWorkspace") {
+    // Check that both workspaces are the same type
+    PeaksWorkspace_sptr pws1 = std::dynamic_pointer_cast<PeaksWorkspace>(w1);
+    PeaksWorkspace_sptr pws2 = std::dynamic_pointer_cast<PeaksWorkspace>(w2);
+
+    // if any one of the pointer is null, record the error
+    // -- meaning at least one of the input workspace cannot be casted
+    //    into peakworkspace
+    if ((pws1 && !pws2) || (!pws1 && pws2)) {
+      recordMismatch("One workspace is a PeaksWorkspace and the other is not.");
+      return;
+    }
 
-  // Check that both workspaces are the same type
-  PeaksWorkspace_sptr pws1 = std::dynamic_pointer_cast<PeaksWorkspace>(w1);
-  PeaksWorkspace_sptr pws2 = std::dynamic_pointer_cast<PeaksWorkspace>(w2);
-  if ((pws1 && !pws2) || (!pws1 && pws2)) {
-    recordMismatch("One workspace is a PeaksWorkspace and the other is not.");
-    return;
+    // Check some peak-based stuff when both pointers are not null
+    if (pws1 && pws2) {
+      doPeaksComparison(pws1, pws2);
+      return;
+    }
   }
-  // Check some peak-based stuff
-  if (pws1 && pws2) {
-    doPeaksComparison(pws1, pws2);
-    return;
+
+  // ==============================================================================
+  // Lean Elastic Peaks workspaces
+  // ==============================================================================
+  if (w1->id() == "LeanElasticPeaksWorkspace" ||
+      w2->id() == "LeanElasticPeaksWorkspace") {
+    auto lpws1 = std::dynamic_pointer_cast<LeanElasticPeaksWorkspace>(w1);
+    auto lpws2 = std::dynamic_pointer_cast<LeanElasticPeaksWorkspace>(w2);
+
+    if ((lpws1 && !lpws2) || (!lpws1 && lpws2)) {
+      recordMismatch(
+          "One workspace is a LeanElasticPeaksWorkspace and the other is not.");
+    }
+
+    if (lpws1 && lpws2) {
+      doLeanElasticPeaksComparison(lpws1, lpws2);
+      return;
+    }
   }
 
   // ==============================================================================
@@ -876,10 +902,12 @@ bool CompareWorkspaces::checkInstrument(
   const Geometry::ParameterMap &ws1_parmap = ws1->constInstrumentParameters();
   const Geometry::ParameterMap &ws2_parmap = ws2->constInstrumentParameters();
 
-  if (ws1_parmap != ws2_parmap) {
+  const bool checkAllData = getProperty("CheckAllData");
+  auto errorStr = ws1_parmap.diff(ws2_parmap, !checkAllData);
+  if (!errorStr.empty()) {
     g_log.debug()
         << "Here information to help understand parameter map differences:\n";
-    g_log.debug() << ws1_parmap.diff(ws2_parmap);
+    g_log.debug() << errorStr;
     recordMismatch(
         "Instrument ParameterMap mismatch (differences in ordering ignored)");
     return false;
@@ -1062,14 +1090,16 @@ void CompareWorkspaces::doPeaksComparison(PeaksWorkspace_sptr tws1,
     sortPeaks->setProperty("ColumnNameToSortBy", "DSpacing");
     sortPeaks->setProperty("SortAscending", true);
     sortPeaks->executeAsChildAlg();
-    tws1 = sortPeaks->getProperty("OutputWorkspace");
+    IPeaksWorkspace_sptr tmp1 = sortPeaks->getProperty("OutputWorkspace");
+    tws1 = std::dynamic_pointer_cast<PeaksWorkspace>(tmp1);
 
     sortPeaks = createChildAlgorithm("SortPeaksWorkspace");
     sortPeaks->setProperty("InputWorkspace", tws2);
     sortPeaks->setProperty("ColumnNameToSortBy", "DSpacing");
     sortPeaks->setProperty("SortAscending", true);
     sortPeaks->executeAsChildAlg();
-    tws2 = sortPeaks->getProperty("OutputWorkspace");
+    IPeaksWorkspace_sptr tmp2 = sortPeaks->getProperty("OutputWorkspace");
+    tws2 = std::dynamic_pointer_cast<PeaksWorkspace>(tmp2);
   }
 
   const double tolerance = getProperty("Tolerance");
@@ -1124,6 +1154,100 @@ void CompareWorkspaces::doPeaksComparison(PeaksWorkspace_sptr tws1,
         s1 = peak1.getCol();
         s2 = peak2.getCol();
       }
+      if (std::fabs(s1 - s2) > tolerance) {
+        g_log.debug(name);
+        g_log.debug() << "s1 = " << s1 << "\n"
+                      << "s2 = " << s2 << "\n"
+                      << "std::fabs(s1 - s2) = " << std::fabs(s1 - s2) << "\n"
+                      << "tolerance = " << tolerance << "\n";
+        g_log.debug() << "Data mismatch at cell (row#,col#): (" << i << "," << j
+                      << ")\n";
+        recordMismatch("Data mismatch");
+        return;
+      }
+    }
+  }
+}
+
+//------------------------------------------------------------------------------------------------
+void CompareWorkspaces::doLeanElasticPeaksComparison(
+    LeanElasticPeaksWorkspace_sptr tws1, LeanElasticPeaksWorkspace_sptr tws2) {
+  // Check some table-based stuff
+  if (tws1->getNumberPeaks() != tws2->getNumberPeaks()) {
+    recordMismatch("Mismatched number of rows.");
+    return;
+  }
+  if (tws1->columnCount() != tws2->columnCount()) {
+    recordMismatch("Mismatched number of columns.");
+    return;
+  }
+
+  // sort the workspaces before comparing
+  auto sortPeaks = createChildAlgorithm("SortPeaksWorkspace");
+  sortPeaks->setProperty("InputWorkspace", tws1);
+  sortPeaks->setProperty("ColumnNameToSortBy", "DSpacing");
+  sortPeaks->setProperty("SortAscending", true);
+  sortPeaks->executeAsChildAlg();
+  IPeaksWorkspace_sptr ipws1 = sortPeaks->getProperty("OutputWorkspace");
+
+  sortPeaks = createChildAlgorithm("SortPeaksWorkspace");
+  sortPeaks->setProperty("InputWorkspace", tws2);
+  sortPeaks->setProperty("ColumnNameToSortBy", "DSpacing");
+  sortPeaks->setProperty("SortAscending", true);
+  sortPeaks->executeAsChildAlg();
+  IPeaksWorkspace_sptr ipws2 = sortPeaks->getProperty("OutputWorkspace");
+
+  const double tolerance = getProperty("Tolerance");
+  for (int i = 0; i < ipws1->getNumberPeaks(); i++) {
+    for (size_t j = 0; j < ipws1->columnCount(); j++) {
+      std::shared_ptr<const API::Column> col = ipws1->getColumn(j);
+      std::string name = col->name();
+      double s1 = 0.0;
+      double s2 = 0.0;
+      if (name == "RunNumber") {
+        s1 = double(ipws1->getPeak(i).getRunNumber());
+        s2 = double(ipws2->getPeak(i).getRunNumber());
+      } else if (name == "h") {
+        s1 = ipws1->getPeak(i).getH();
+        s2 = ipws2->getPeak(i).getH();
+      } else if (name == "k") {
+        s1 = ipws1->getPeak(i).getK();
+        s2 = ipws2->getPeak(i).getK();
+      } else if (name == "l") {
+        s1 = ipws1->getPeak(i).getL();
+        s2 = ipws2->getPeak(i).getL();
+      } else if (name == "Wavelength") {
+        s1 = ipws1->getPeak(i).getWavelength();
+        s2 = ipws2->getPeak(i).getWavelength();
+      } else if (name == "DSpacing") {
+        s1 = ipws1->getPeak(i).getDSpacing();
+        s2 = ipws2->getPeak(i).getDSpacing();
+      } else if (name == "Intens") {
+        s1 = ipws1->getPeak(i).getIntensity();
+        s2 = ipws2->getPeak(i).getIntensity();
+      } else if (name == "SigInt") {
+        s1 = ipws1->getPeak(i).getSigmaIntensity();
+        s2 = ipws2->getPeak(i).getSigmaIntensity();
+      } else if (name == "BinCount") {
+        s1 = ipws1->getPeak(i).getBinCount();
+        s2 = ipws2->getPeak(i).getBinCount();
+      } else if (name == "QLab") {
+        V3D q1 = ipws1->getPeak(i).getQLabFrame();
+        V3D q2 = ipws2->getPeak(i).getQLabFrame();
+        // using s1 here as the diff
+        for (int i = 0; i < 3; ++i) {
+          s1 += (q1[i] - q2[i]) * (q1[i] - q2[i]);
+        }
+        s1 = std::sqrt(s1);
+      } else if (name == "QSample") {
+        V3D q1 = ipws1->getPeak(i).getQSampleFrame();
+        V3D q2 = ipws2->getPeak(i).getQSampleFrame();
+        // using s1 here as the diff
+        for (int i = 0; i < 3; ++i) {
+          s1 += (q1[i] - q2[i]) * (q1[i] - q2[i]);
+        }
+        s1 = std::sqrt(s1);
+      }
       if (std::fabs(s1 - s2) > tolerance) {
         g_log.debug() << "Data mismatch at cell (row#,col#): (" << i << "," << j
                       << ")\n";
diff --git a/Framework/Algorithms/src/ConvertSpectrumAxis.cpp b/Framework/Algorithms/src/ConvertSpectrumAxis.cpp
index 0d535494a53ef4773218cbecec91e52eadcc7a1e..e8babeb8a12b5fcdb00cb395d9ebed628e63671c 100644
--- a/Framework/Algorithms/src/ConvertSpectrumAxis.cpp
+++ b/Framework/Algorithms/src/ConvertSpectrumAxis.cpp
@@ -94,35 +94,38 @@ void ConvertSpectrumAxis::exec() {
     std::vector<double> emptyVector;
     const double l1 = spectrumInfo.l1();
     const std::string emodeStr = getProperty("EMode");
-    int emode = 0;
-    if (emodeStr == "Direct")
-      emode = 1;
-    else if (emodeStr == "Indirect")
-      emode = 2;
-    const double delta = 0.0;
-    double efixed;
+    auto emode = Kernel::DeltaEMode::fromString(emodeStr);
+    size_t nfailures = 0;
     for (size_t i = 0; i < nHist; i++) {
       std::vector<double> xval{inputWS->x(i).front(), inputWS->x(i).back()};
-      double twoTheta, l1val, l2;
-      if (!spectrumInfo.isMonitor(i)) {
-        twoTheta = spectrumInfo.twoTheta(i);
-        l2 = spectrumInfo.l2(i);
-        l1val = l1;
-        efixed =
-            getEfixed(spectrumInfo.detector(i), inputWS, emode); // get efixed
-      } else {
-        twoTheta = 0.0;
-        l2 = l1;
-        l1val = 0.0;
-        efixed = DBL_MIN;
+      UnitParametersMap pmap{};
+
+      double efixedProp = getProperty("Efixed");
+      if (efixedProp != EMPTY_DBL()) {
+        pmap[UnitParams::efixed] = efixedProp;
+        g_log.debug() << "Detector: " << spectrumInfo.detector(i).getID()
+                      << " Efixed: " << efixedProp << "\n";
+      }
+
+      spectrumInfo.getDetectorValues(*fromUnit, *toUnit, emode, false, i, pmap);
+      double value = 0.;
+      try {
+        fromUnit->toTOF(xval, emptyVector, l1, emode, pmap);
+        toUnit->fromTOF(xval, emptyVector, l1, emode, pmap);
+        value = (xval.front() + xval.back()) / 2;
+      } catch (std::runtime_error &) {
+        nfailures++;
+        g_log.warning() << "Unable to calculate new spectrum axis value for "
+                           "workspace index "
+                        << i;
+        value = inputWS->getAxis(1)->getValue(i);
       }
-      fromUnit->toTOF(xval, emptyVector, l1val, l2, twoTheta, emode, efixed,
-                      delta);
-      toUnit->fromTOF(xval, emptyVector, l1val, l2, twoTheta, emode, efixed,
-                      delta);
-      double value = (xval.front() + xval.back()) / 2;
       indexMap.emplace(value, i);
     }
+    if (nfailures == nHist) {
+      throw std::runtime_error(
+          "Unable to convert spectrum axis values on all spectra");
+    }
   } else {
     // Set up binding to memeber funtion. Avoids condition as part of loop over
     // nHistograms.
diff --git a/Framework/Algorithms/src/ConvertUnits.cpp b/Framework/Algorithms/src/ConvertUnits.cpp
index 08ece60bd6c1c6e0c43f4bd325a85bcbc2c2f9c1..2b95f1f70b61a03a0f474452108448102bc2acd8 100644
--- a/Framework/Algorithms/src/ConvertUnits.cpp
+++ b/Framework/Algorithms/src/ConvertUnits.cpp
@@ -14,6 +14,7 @@
 #include "MantidDataObjects/Workspace2D.h"
 #include "MantidDataObjects/WorkspaceCreation.h"
 #include "MantidGeometry/Instrument.h"
+#include "MantidGeometry/Instrument/DetectorInfo.h"
 #include "MantidHistogramData/Histogram.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/CompositeValidator.h"
@@ -387,65 +388,6 @@ ConvertUnits::convertQuickly(const API::MatrixWorkspace_const_sptr &inputWS,
   return outputWS;
 }
 
-/** Get the L2, theta and efixed values for a workspace index
- * @param spectrumInfo :: SpectrumInfo of the workspace
- * @param outputUnit :: The output unit
- * @param emode :: The energy mode
- * @param ws :: The workspace
- * @param signedTheta :: Return twotheta with sign or without
- * @param wsIndex :: The workspace index
- * @param efixed :: the returned fixed energy
- * @param l2 :: The returned sample - detector distance
- * @param twoTheta :: the returned two theta angle
- * @returns true if lookup successful, false on error
- */
-bool ConvertUnits::getDetectorValues(const API::SpectrumInfo &spectrumInfo,
-                                     const Kernel::Unit &outputUnit, int emode,
-                                     const MatrixWorkspace &ws,
-                                     const bool signedTheta, int64_t wsIndex,
-                                     double &efixed, double &l2,
-                                     double &twoTheta) {
-  if (!spectrumInfo.hasDetectors(wsIndex))
-    return false;
-
-  l2 = spectrumInfo.l2(wsIndex);
-
-  if (!spectrumInfo.isMonitor(wsIndex)) {
-    // The scattering angle for this detector (in radians).
-    try {
-      if (signedTheta)
-        twoTheta = spectrumInfo.signedTwoTheta(wsIndex);
-      else
-        twoTheta = spectrumInfo.twoTheta(wsIndex);
-    } catch (const std::runtime_error &e) {
-      g_log.warning(e.what());
-      twoTheta = std::numeric_limits<double>::quiet_NaN();
-    }
-    // If an indirect instrument, try getting Efixed from the geometry
-    if (emode == 2 && efixed == EMPTY_DBL()) // indirect
-    {
-      if (spectrumInfo.hasUniqueDetector(wsIndex)) {
-        const auto &det = spectrumInfo.detector(wsIndex);
-        auto par = ws.constInstrumentParameters().getRecursive(&det, "Efixed");
-        if (par) {
-          efixed = par->value<double>();
-          g_log.debug() << "Detector: " << det.getID() << " EFixed: " << efixed
-                        << "\n";
-        }
-      }
-      // Non-unique detector (i.e., DetectorGroup): use single provided value
-    }
-  } else {
-    twoTheta = 0.0;
-    efixed = DBL_MIN;
-    // Energy transfer is meaningless for a monitor, so set l2 to 0.
-    if (outputUnit.unitID().find("DeltaE") != std::string::npos) {
-      l2 = 0.0;
-    }
-  }
-  return true;
-}
-
 /** Convert the workspace units using TOF as an intermediate step in the
  * conversion
  * @param fromUnit :: The unit of the input workspace
@@ -472,44 +414,18 @@ ConvertUnits::convertViaTOF(Kernel::Unit_const_sptr fromUnit,
   /// @todo No implementation for any of these in the geometry yet so using
   /// properties
   const std::string emodeStr = getProperty("EMode");
-  // Convert back to an integer representation
-  int emode = 0;
-  if (emodeStr == "Direct")
-    emode = 1;
-  else if (emodeStr == "Indirect")
-    emode = 2;
+  DeltaEMode::Type emode = DeltaEMode::fromString(emodeStr);
 
   // Not doing anything with the Y vector in to/fromTOF yet, so just pass
   // empty
   // vector
   std::vector<double> emptyVec;
-  const bool needEfixed =
-      (outputUnit->unitID().find("DeltaE") != std::string::npos ||
-       outputUnit->unitID().find("Wave") != std::string::npos);
   double efixedProp = getProperty("Efixed");
-  if (emode == 1) {
-    //... direct efixed gather
-    if (efixedProp == EMPTY_DBL()) {
-      // try and get the value from the run parameters
-      const API::Run &run = inputWS->run();
-      if (run.hasProperty("Ei")) {
-        try {
-          efixedProp = run.getPropertyValueAsType<double>("Ei");
-        } catch (Kernel::Exception::NotFoundError &) {
-          throw std::runtime_error("Cannot retrieve Ei value from the logs");
-        }
-      } else {
-        if (needEfixed) {
-          throw std::invalid_argument(
-              "Could not retrieve incident energy from run object");
-        } else {
-          efixedProp = 0.0;
-        }
-      }
+  if (efixedProp == EMPTY_DBL() && emode == DeltaEMode::Type::Direct) {
+    try {
+      efixedProp = inputWS->getEFixedGivenEMode(nullptr, emode);
+    } catch (std::runtime_error &) {
     }
-  } else if (emode == 0 && efixedProp == EMPTY_DBL()) // Elastic
-  {
-    efixedProp = 0.0;
   }
 
   std::vector<std::string> parameters =
@@ -518,25 +434,27 @@ ConvertUnits::convertViaTOF(Kernel::Unit_const_sptr fromUnit,
       (!parameters.empty()) &&
       find(parameters.begin(), parameters.end(), "Always") != parameters.end();
 
-  auto localFromUnit = std::unique_ptr<Unit>(fromUnit->clone());
-  auto localOutputUnit = std::unique_ptr<Unit>(outputUnit->clone());
+  auto checkFromUnit = std::unique_ptr<Unit>(fromUnit->clone());
+  auto checkOutputUnit = std::unique_ptr<Unit>(outputUnit->clone());
 
   // Perform Sanity Validation before creating workspace
-  double checkefixed = efixedProp;
-  double checkl2;
-  double checktwoTheta;
+  const double checkdelta = 0.0;
+  UnitParametersMap pmap = {{UnitParams::delta, checkdelta}};
+  if (efixedProp != EMPTY_DBL()) {
+    pmap[UnitParams::efixed] = efixedProp;
+  }
   size_t checkIndex = 0;
-  if (getDetectorValues(spectrumInfo, *outputUnit, emode, *inputWS, signedTheta,
-                        checkIndex, checkefixed, checkl2, checktwoTheta)) {
-    const double checkdelta = 0.0;
-    // copy the X values for the check
-    auto checkXValues = inputWS->readX(checkIndex);
+  spectrumInfo.getDetectorValues(*fromUnit, *outputUnit, emode, signedTheta,
+                                 checkIndex, pmap);
+  // copy the X values for the check
+  auto checkXValues = inputWS->readX(checkIndex);
+  try {
     // Convert the input unit to time-of-flight
-    localFromUnit->toTOF(checkXValues, emptyVec, l1, checkl2, checktwoTheta,
-                         emode, checkefixed, checkdelta);
+    checkFromUnit->toTOF(checkXValues, emptyVec, l1, emode, pmap);
     // Convert from time-of-flight to the desired unit
-    localOutputUnit->fromTOF(checkXValues, emptyVec, l1, checkl2, checktwoTheta,
-                             emode, checkefixed, checkdelta);
+    checkOutputUnit->fromTOF(checkXValues, emptyVec, l1, emode, pmap);
+  } catch (
+      std::runtime_error) { // if it's a detector specific problem then ignore
   }
 
   // create the output workspace
@@ -547,32 +465,38 @@ ConvertUnits::convertViaTOF(Kernel::Unit_const_sptr fromUnit,
 
   auto &outSpectrumInfo = outputWS->mutableSpectrumInfo();
   // Loop over the histograms (detector spectra)
+  PARALLEL_FOR_IF(Kernel::threadSafe(*outputWS))
   for (int64_t i = 0; i < numberOfSpectra_i; ++i) {
+    PARALLEL_START_INTERUPT_REGION
     double efixed = efixedProp;
 
     // Now get the detector object for this histogram
-    double l2;
-    double twoTheta;
-    if (getDetectorValues(outSpectrumInfo, *outputUnit, emode, *outputWS,
-                          signedTheta, i, efixed, l2, twoTheta)) {
+    /// @todo Don't yet consider hold-off (delta)
+    const double delta = 0.0;
 
-      /// @todo Don't yet consider hold-off (delta)
-      const double delta = 0.0;
+    auto localFromUnit = std::unique_ptr<Unit>(fromUnit->clone());
+    auto localOutputUnit = std::unique_ptr<Unit>(outputUnit->clone());
 
-      // TODO toTOF and fromTOF need to be reimplemented outside of kernel
-      localFromUnit->toTOF(outputWS->dataX(i), emptyVec, l1, l2, twoTheta,
-                           emode, efixed, delta);
+    // TODO toTOF and fromTOF need to be reimplemented outside of kernel
+    UnitParametersMap pmap = {{UnitParams::delta, delta}};
+    if (efixedProp != EMPTY_DBL()) {
+      pmap[UnitParams::efixed] = efixed;
+    }
+    outSpectrumInfo.getDetectorValues(*fromUnit, *outputUnit, emode,
+                                      signedTheta, i, pmap);
+    try {
+      localFromUnit->toTOF(outputWS->dataX(i), emptyVec, l1, emode, pmap);
       // Convert from time-of-flight to the desired unit
-      localOutputUnit->fromTOF(outputWS->dataX(i), emptyVec, l1, l2, twoTheta,
-                               emode, efixed, delta);
+      localOutputUnit->fromTOF(outputWS->dataX(i), emptyVec, l1, emode, pmap);
 
       // EventWorkspace part, modifying the EventLists.
       if (m_inputEvents) {
         eventWS->getSpectrum(i).convertUnitsViaTof(localFromUnit.get(),
                                                    localOutputUnit.get());
       }
-    } else {
-      // Get to here if exception thrown when calculating distance to detector
+    } catch (std::runtime_error) {
+      // Get to here if exception thrown in unit conversion eg when calculating
+      // distance to detector
       failedDetectorCount++;
       // Since you usually (always?) get to here when there's no attached
       // detectors, this call is
@@ -584,7 +508,9 @@ ConvertUnits::convertViaTOF(Kernel::Unit_const_sptr fromUnit,
     }
 
     prog.report("Convert to " + m_outputUnit->unitID());
+    PARALLEL_END_INTERUPT_REGION
   } // loop over spectra
+  PARALLEL_CHECK_INTERUPT_REGION
 
   if (failedDetectorCount != 0) {
     g_log.information() << "Unable to calculate sample-detector distance for "
diff --git a/Framework/Algorithms/src/ConvertUnitsUsingDetectorTable.cpp b/Framework/Algorithms/src/ConvertUnitsUsingDetectorTable.cpp
index 70aa822f32761b32e4c3fe32679a568eb982070c..01e5ac7d1a1705f50efe4a49659265931a6bc585 100644
--- a/Framework/Algorithms/src/ConvertUnitsUsingDetectorTable.cpp
+++ b/Framework/Algorithms/src/ConvertUnitsUsingDetectorTable.cpp
@@ -162,16 +162,14 @@ MatrixWorkspace_sptr ConvertUnitsUsingDetectorTable::convertViaTOF(
     // Convert the input unit to time-of-flight
     auto checkFromUnit = std::unique_ptr<Unit>(fromUnit->clone());
     auto checkOutputUnit = std::unique_ptr<Unit>(outputUnit->clone());
-    double checkdelta = 0;
+    UnitParametersMap pmap{{UnitParams::l2, l2Column[detectorRow]},
+                           {UnitParams::twoTheta, twoThetaColumn[detectorRow]},
+                           {UnitParams::efixed, efixedColumn[detectorRow]}};
     checkFromUnit->toTOF(checkXValues, emptyVec, l1Column[detectorRow],
-                         l2Column[detectorRow], twoThetaColumn[detectorRow],
-                         emodeColumn[detectorRow], efixedColumn[detectorRow],
-                         checkdelta);
+                         emodeColumn[detectorRow], pmap);
     // Convert from time-of-flight to the desired unit
     checkOutputUnit->fromTOF(checkXValues, emptyVec, l1Column[detectorRow],
-                             l2Column[detectorRow], twoThetaColumn[detectorRow],
-                             emodeColumn[detectorRow],
-                             efixedColumn[detectorRow], checkdelta);
+                             emodeColumn[detectorRow], pmap);
   }
 
   // create the output workspace
@@ -229,16 +227,16 @@ MatrixWorkspace_sptr ConvertUnitsUsingDetectorTable::convertViaTOF(
         auto localFromUnit = std::unique_ptr<Unit>(fromUnit->clone());
         auto localOutputUnit = std::unique_ptr<Unit>(outputUnit->clone());
         /// @todo Don't yet consider hold-off (delta)
-        const double delta = 0.0;
         std::vector<double> values(outputWS->x(wsid).begin(),
                                    outputWS->x(wsid).end());
 
+        UnitParametersMap pmap{{UnitParams::l2, l2},
+                               {UnitParams::twoTheta, twoTheta},
+                               {UnitParams::efixed, efixed}};
         // Convert the input unit to time-of-flight
-        localFromUnit->toTOF(values, emptyVec, l1, l2, twoTheta, emode, efixed,
-                             delta);
+        localFromUnit->toTOF(values, emptyVec, l1, emode, pmap);
         // Convert from time-of-flight to the desired unit
-        localOutputUnit->fromTOF(values, emptyVec, l1, l2, twoTheta, emode,
-                                 efixed, delta);
+        localOutputUnit->fromTOF(values, emptyVec, l1, emode, pmap);
 
         outputWS->mutableX(wsid) = std::move(values);
 
diff --git a/Framework/Algorithms/src/CreateDetectorTable.cpp b/Framework/Algorithms/src/CreateDetectorTable.cpp
index a466b80a733a5c3e0b757e64283fb12139007ddd..23029e752fc2fc86ef37da5f5c6e6575489e0367 100644
--- a/Framework/Algorithms/src/CreateDetectorTable.cpp
+++ b/Framework/Algorithms/src/CreateDetectorTable.cpp
@@ -147,8 +147,15 @@ createDetectorTableWorkspace(const MatrixWorkspace_sptr &ws,
     calcQ = false;
   }
 
+  bool hasDiffConstants{false};
+  auto emode = ws->getEMode();
+  if (emode == DeltaEMode::Elastic) {
+    hasDiffConstants = true;
+  }
+
   // Prepare column names
-  auto colNames = createColumns(isScanning, includeData, calcQ);
+  auto colNames =
+      createColumns(isScanning, includeData, calcQ, hasDiffConstants);
 
   const int ncols = static_cast<int>(colNames.size());
   const int nrows = indices.empty()
@@ -175,13 +182,14 @@ createDetectorTableWorkspace(const MatrixWorkspace_sptr &ws,
 
   populateTable(t, ws, nrows, indices, spectrumInfo, signedThetaParamRetrieved,
                 showSignedTwoTheta, beamAxisIndex, sampleDist, isScanning,
-                includeData, calcQ, logger);
+                includeData, calcQ, hasDiffConstants, logger);
 
   return t;
 }
 
 std::vector<std::pair<std::string, std::string>>
-createColumns(const bool isScanning, const bool includeData, const bool calcQ) {
+createColumns(const bool isScanning, const bool includeData, const bool calcQ,
+              const bool hasDiffConstants) {
   std::vector<std::pair<std::string, std::string>> colNames;
   colNames.emplace_back("double", "Index");
   colNames.emplace_back("int", "Spectrum No");
@@ -200,6 +208,12 @@ createColumns(const bool isScanning, const bool includeData, const bool calcQ) {
   }
   colNames.emplace_back("double", "Phi");
   colNames.emplace_back("str", "Monitor");
+  if (hasDiffConstants) {
+    colNames.emplace_back("double", "DIFA");
+    colNames.emplace_back("double", "DIFC");
+    colNames.emplace_back("double", "DIFC - Uncalibrated");
+    colNames.emplace_back("double", "TZERO");
+  }
   return colNames;
 }
 
@@ -209,7 +223,8 @@ void populateTable(ITableWorkspace_sptr &t, const MatrixWorkspace_sptr &ws,
                    bool signedThetaParamRetrieved, bool showSignedTwoTheta,
                    const PointingAlong &beamAxisIndex, const double sampleDist,
                    const bool isScanning, const bool includeData,
-                   const bool calcQ, Logger &logger) {
+                   const bool calcQ, const bool includeDiffConstants,
+                   Logger &logger) {
   PARALLEL_FOR_IF(Mantid::Kernel::threadSafe(*ws))
   for (int row = 0; row < nrows; ++row) {
     TableRow colValues = t->getRow(row);
@@ -307,7 +322,22 @@ void populateTable(ITableWorkspace_sptr &t, const MatrixWorkspace_sptr &ws,
 
       colValues << phi               // rtp
                 << isMonitorDisplay; // monitor
+
+      if (includeDiffConstants) {
+        if (isMonitor) {
+          colValues << 0. << 0. << 0. << 0.;
+        } else {
+          auto diffConsts = spectrumInfo.diffractometerConstants(wsIndex);
+          auto difcValueUncalibrated = spectrumInfo.difcUncalibrated(wsIndex);
+          // map will create an entry with zero value if not present already
+          colValues << diffConsts[UnitParams::difa]
+                    << diffConsts[UnitParams::difc] << difcValueUncalibrated
+                    << diffConsts[UnitParams::tzero];
+        }
+      }
     } catch (const std::exception &) {
+      colValues.row(row);
+      colValues << static_cast<double>(wsIndex);
       // spectrumNo=-1, detID=0
       colValues << -1 << "0";
       // Y/E
@@ -320,7 +350,10 @@ void populateTable(ITableWorkspace_sptr &t, const MatrixWorkspace_sptr &ws,
       }
       colValues << 0.0    // rtp
                 << "n/a"; // monitor
-    }                     // End catch for no spectrum
+      if (includeDiffConstants) {
+        colValues << 0.0 << 0.0 << 0.0 << 0.0;
+      }
+    } // End catch for no spectrum
   }
 }
 
diff --git a/Framework/Algorithms/src/GetAllEi.cpp b/Framework/Algorithms/src/GetAllEi.cpp
index e7a6d1fc263167074aa4493945fef0db21f36a2e..498fd7852c1d1fd127650526198f38119a695a35 100644
--- a/Framework/Algorithms/src/GetAllEi.cpp
+++ b/Framework/Algorithms/src/GetAllEi.cpp
@@ -255,7 +255,6 @@ void GetAllEi::exec() {
       (0.5 * 1.e+6) / chopSpeed; // 0.5 because some choppers open twice.
   // Would be nice to have it 1 or 0.5 depending on chopper type, but
   // it looks like not enough information on what chopper is available on ws;
-  double unused(0.0);
   auto destUnit = Kernel::UnitFactory::Instance().create("Energy");
 
   std::vector<double> guess_opening;
@@ -267,9 +266,8 @@ void GetAllEi::exec() {
         boost::lexical_cast<std::string>(TOF_range.first) + ':' +
         boost::lexical_cast<std::string>(TOF_range.second));
   } else {
-    destUnit->initialize(mon1Distance, 0., 0.,
-                         static_cast<int>(Kernel::DeltaEMode::Elastic), 0.,
-                         unused);
+    destUnit->initialize(mon1Distance,
+                         static_cast<int>(Kernel::DeltaEMode::Elastic), {});
     printDebugModeInfo(guess_opening, TOF_range, destUnit);
   }
   std::pair<double, double> Mon1_Erange =
@@ -286,9 +284,8 @@ void GetAllEi::exec() {
   // convert to energy
   std::vector<double> guess_ei;
   guess_ei.reserve(guess_opening.size());
-  destUnit->initialize(mon1Distance, 0., 0.,
-                       static_cast<int>(Kernel::DeltaEMode::Elastic), 0.,
-                       unused);
+  destUnit->initialize(mon1Distance,
+                       static_cast<int>(Kernel::DeltaEMode::Elastic), {});
   for (double time : guess_opening) {
     double eGuess = destUnit->singleFromTOF(time);
     if (eGuess > eMin && eGuess < eMax) {
diff --git a/Framework/Algorithms/src/MonteCarloAbsorption.cpp b/Framework/Algorithms/src/MonteCarloAbsorption.cpp
index 2b65a0e69ab64c1c53edda9eda1cec07bfc0db48..7e17eb07a51925312e4d40bee28557402832fcc5 100644
--- a/Framework/Algorithms/src/MonteCarloAbsorption.cpp
+++ b/Framework/Algorithms/src/MonteCarloAbsorption.cpp
@@ -448,7 +448,7 @@ MatrixWorkspace_uptr MonteCarloAbsorption::createOutputWorkspace(
 /**
  * Create the beam profile. Currently only supports Rectangular or Circular. The
  * dimensions are either specified by those provided by `SetBeam` algorithm or
- * default to the width and height of the samples bounding box
+ * default to the width and height of the sample's bounding box
  * @param instrument A reference to the instrument object
  * @param sample A reference to the sample object
  * @return A new IBeamProfile object
@@ -478,6 +478,9 @@ MonteCarloAbsorption::createBeamProfile(const Instrument &instrument,
                                                    beamRadius);
     }
   } // revert to sample dimensions if no return by this point
+  if (!sample.getShape().hasValidShape())
+    throw std::invalid_argument(
+        "Cannot determine beam profile without a sample shape");
   const auto bbox = sample.getShape().getBoundingBox().width();
   beamWidth = bbox[frame->pointingHorizontal()];
   beamHeight = bbox[frame->pointingUp()];
diff --git a/Framework/Algorithms/src/PDCalibration.cpp b/Framework/Algorithms/src/PDCalibration.cpp
index 09813e1df37cde7aae5dce9bf99025a7c0cf8aad..61829c36ffa897165ca65a1a7d003bb17aedec8f 100644
--- a/Framework/Algorithms/src/PDCalibration.cpp
+++ b/Framework/Algorithms/src/PDCalibration.cpp
@@ -29,10 +29,10 @@
 #include "MantidKernel/ArrayProperty.h"
 #include "MantidKernel/BoundedValidator.h"
 #include "MantidKernel/CompositeValidator.h"
-#include "MantidKernel/Diffraction.h"
 #include "MantidKernel/ListValidator.h"
 #include "MantidKernel/MandatoryValidator.h"
 #include "MantidKernel/RebinParamsValidator.h"
+#include "MantidKernel/Unit.h"
 
 #include <algorithm>
 #include <cassert>
@@ -120,11 +120,13 @@ public:
    *
    * @param peaksInD :: peak centers, in d-spacing
    * @param peaksInDWindows :: left and right fit ranges for each peak
-   * @param toTof :: function converting from d-spacing to TOF
+   * @param difa :: difa diffractometer constant (quadratic term)
+   * @param difc :: difc diffractometer constant (linear term)
+   * @param tzero :: tzero diffractometer constant (constant term)
    */
   void setPositions(const std::vector<double> &peaksInD,
                     const std::vector<double> &peaksInDWindows,
-                    const std::function<double(double)> &toTof) {
+                    const double difa, const double difc, const double tzero) {
     // clear out old values
     inDPos.clear();
     inTofPos.clear();
@@ -136,9 +138,16 @@ public:
     inTofWindows.assign(peaksInDWindows.begin(), peaksInDWindows.end());
 
     // convert the bits that matter to TOF
-    std::transform(inTofPos.begin(), inTofPos.end(), inTofPos.begin(), toTof);
-    std::transform(inTofWindows.begin(), inTofWindows.end(),
-                   inTofWindows.begin(), toTof);
+    Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> yunused;
+    dSpacingUnit.toTOF(inTofPos, yunused, -1, 0,
+                       {{Kernel::UnitParams::difa, difa},
+                        {Kernel::UnitParams::difc, difc},
+                        {Kernel::UnitParams::tzero, tzero}});
+    dSpacingUnit.toTOF(inTofWindows, yunused, -1, 0,
+                       {{Kernel::UnitParams::difa, difa},
+                        {Kernel::UnitParams::difc, difc},
+                        {Kernel::UnitParams::tzero, tzero}});
   }
 
   std::size_t wkspIndex;
@@ -581,8 +590,10 @@ void PDCalibration::exec() {
      // object to hold the information about the peak positions, detid, and wksp
      // index
      PDCalibration::FittedPeaks peaks(m_uncalibratedWS, wkspIndex);
-     auto toTof = getDSpacingToTof(peaks.detid);
-     peaks.setPositions(m_peaksInDspacing, windowsInDSpacing, toTof);
+     auto [difc, difa, tzero] = getDSpacingToTof(
+         peaks.detid); // doesn't matter which one - all have same difc etc.
+     peaks.setPositions(m_peaksInDspacing, windowsInDSpacing, difa, difc,
+                        tzero);
 
      // includes peaks that aren't used in the fit
      // The following data structures will hold information for the peaks
@@ -690,15 +701,18 @@ void PDCalibration::exec() {
          // and the peak positions using the GSAS formula with optimized difc,
          // difa, and tzero
          double chisq = 0.;
-         // `converter` if a function that returns a d-spacing for an input TOF
-         auto converter =
-             Kernel::Diffraction::getTofToDConversionFunc(difc, difa, t0);
+         Mantid::Kernel::Units::dSpacing dSpacingUnit;
+         dSpacingUnit.initialize(
+             -1., 0,
+             Kernel::UnitParametersMap{{Kernel::UnitParams::difa, difa},
+                                       {Kernel::UnitParams::difc, difc},
+                                       {Kernel::UnitParams::tzero, t0}});
          for (std::size_t i = 0; i < numPeaks; ++i) {
            if (std::isnan(tof_vec_full[i]))
              continue;
            // Find d-spacing using the GSAS formula with optimized difc, difa,
            // t0 for the TOF of the current peak's center.
-           const double dspacing = converter(tof_vec_full[i]);
+           const double dspacing = dSpacingUnit.singleFromTOF(tof_vec_full[i]);
            // `temp` is residual between the nominal position in d-spacing for
            // the current peak, and the fitted position in d-spacing
            const double temp = m_peaksInDspacing[i] - dspacing;
@@ -706,7 +720,7 @@ void PDCalibration::exec() {
            m_peakPositionTable->cell<double>(rowIndexOutputPeaks, i + 1) =
                dspacing;
            m_peakWidthTable->cell<double>(rowIndexOutputPeaks, i + 1) =
-               WIDTH_TO_FWHM * converter(width_vec_full[i]);
+               WIDTH_TO_FWHM * dSpacingUnit.singleFromTOF(width_vec_full[i]);
            m_peakHeightTable->cell<double>(rowIndexOutputPeaks, i + 1) =
                height_vec_full[i];
          }
@@ -804,13 +818,17 @@ double gsl_costFunction(const gsl_vector *v, void *peaks) {
     if (numParams > 2)
       difa = gsl_vector_get(v, 2);
   }
-  auto converter =
-      Kernel::Diffraction::getDToTofConversionFunc(difc, difa, tzero);
+  Mantid::Kernel::Units::dSpacing dSpacingUnit;
+  dSpacingUnit.initialize(
+      -1., 0,
+      Kernel::UnitParametersMap{{Kernel::UnitParams::difa, difa},
+                                {Kernel::UnitParams::difc, difc},
+                                {Kernel::UnitParams::tzero, tzero}});
 
   // calculate the sum of the residuals from observed peaks
   double errsum = 0.0;
   for (size_t i = 0; i < numPeaks; ++i) {
-    const double tofCalib = converter(dspace[i]);
+    const double tofCalib = dSpacingUnit.singleToTOF(dspace[i]);
     const double errsum_i = std::fabs(tofObs[i] - tofCalib) * weights[i];
     errsum += errsum_i;
   }
@@ -1056,9 +1074,8 @@ PDCalibration::dSpacingWindows(const std::vector<double> &centres,
  *
  * @param detIds :: set of detector IDs
  */
-std::function<double(double)>
+std::tuple<double, double, double>
 PDCalibration::getDSpacingToTof(const std::set<detid_t> &detIds) {
-
   // to start this is the old calibration values
   double difc = 0.;
   double difa = 0.;
@@ -1076,7 +1093,7 @@ PDCalibration::getDSpacingToTof(const std::set<detid_t> &detIds) {
     tzero = norm * tzero;
   }
 
-  return Kernel::Diffraction::getDToTofConversionFunc(difc, difa, tzero);
+  return {difc, difa, tzero};
 }
 
 void PDCalibration::setCalibrationValues(const detid_t detid, const double difc,
@@ -1111,8 +1128,9 @@ vector<double> PDCalibration::getTOFminmax(const double difc, const double difa,
                                            const double tzero) {
   vector<double> tofminmax(2);
 
-  tofminmax[0] = Kernel::Diffraction::calcTofMin(difc, difa, tzero, m_tofMin);
-  tofminmax[1] = Kernel::Diffraction::calcTofMax(difc, difa, tzero, m_tofMax);
+  Kernel::Units::dSpacing dSpacingUnit;
+  tofminmax[0] = dSpacingUnit.calcTofMin(difc, difa, tzero, m_tofMin);
+  tofminmax[1] = dSpacingUnit.calcTofMax(difc, difa, tzero, m_tofMax);
 
   return tofminmax;
 }
@@ -1433,9 +1451,9 @@ PDCalibration::createTOFPeakCenterFitWindowWorkspaces(
     PDCalibration::FittedPeaks peaks(dataws, static_cast<size_t>(iws));
     // toTof is a function that converts from d-spacing to TOF for a particular
     // pixel
-    auto toTof = getDSpacingToTof(peaks.detid);
+    auto [difc, difa, tzero] = getDSpacingToTof(peaks.detid);
     // setpositions initializes peaks.inTofPos and peaks.inTofWindows
-    peaks.setPositions(m_peaksInDspacing, windowsInDSpacing, toTof);
+    peaks.setPositions(m_peaksInDspacing, windowsInDSpacing, difa, difc, tzero);
     peak_pos_ws->setPoints(iws, peaks.inTofPos);
     peak_window_ws->setPoints(iws, peaks.inTofWindows);
     prog.report();
diff --git a/Framework/Algorithms/src/RemoveBackground.cpp b/Framework/Algorithms/src/RemoveBackground.cpp
index 2bda22e3fd52666972c504dfc20f01c0cea27587..f8e6c5268b7e64a2931efb4e7212d9b31d992c08 100644
--- a/Framework/Algorithms/src/RemoveBackground.cpp
+++ b/Framework/Algorithms/src/RemoveBackground.cpp
@@ -106,9 +106,8 @@ void RemoveBackground::exec() {
                                 "workspace");
   }
 
-  int eMode; // in convert units emode is still integer
   const std::string emodeStr = getProperty("EMode");
-  eMode = static_cast<int>(Kernel::DeltaEMode::fromString(emodeStr));
+  auto eMode = Kernel::DeltaEMode::fromString(emodeStr);
 
   // Removing background in-place
   bool inPlace = (inputWS == outputWS);
@@ -155,7 +154,7 @@ void RemoveBackground::exec() {
 BackgroundHelper::BackgroundHelper()
     : m_WSUnit(), m_bgWs(), m_wkWS(), m_spectrumInfo(nullptr), m_pgLog(nullptr),
       m_inPlace(true), m_singleValueBackground(false), m_NBg(0), m_dtBg(1),
-      m_ErrSq(0), m_Emode(0), m_Efix(0), m_nullifyNegative(false),
+      m_ErrSq(0), m_Emode(DeltaEMode::Elastic), m_nullifyNegative(false),
       m_previouslyRemovedBkgMode(false) {}
 
 /** Initialization method:
@@ -172,7 +171,8 @@ or target workspace has to be cloned.
 */
 void BackgroundHelper::initialize(const API::MatrixWorkspace_const_sptr &bkgWS,
                                   const API::MatrixWorkspace_sptr &sourceWS,
-                                  int emode, Kernel::Logger *pLog, int nThreads,
+                                  Kernel::DeltaEMode::Type emode,
+                                  Kernel::Logger *pLog, int nThreads,
                                   bool inPlace, bool nullifyNegative) {
   m_bgWs = bkgWS;
   m_wkWS = sourceWS;
@@ -224,8 +224,6 @@ void BackgroundHelper::initialize(const API::MatrixWorkspace_const_sptr &bkgWS,
     if (m_NBg < 1.e-7 && m_ErrSq < 1.e-7)
       m_previouslyRemovedBkgMode = true;
   }
-
-  m_Efix = this->getEi(sourceWS);
 }
 /**Method removes background from vectors which represent a histogram data for a
  * single spectra
@@ -257,10 +255,8 @@ void BackgroundHelper::removeBackground(int nHist, HistogramX &x_data,
   }
 
   try {
-    double twoTheta = m_spectrumInfo->twoTheta(nHist);
     double L1 = m_spectrumInfo->l1();
-    double L2 = m_spectrumInfo->l2(nHist);
-    double delta(std::numeric_limits<double>::quiet_NaN());
+
     // get access to source workspace in case if target is different from source
     auto &XValues = m_wkWS->x(nHist);
     auto &YValues = m_wkWS->y(nHist);
@@ -268,7 +264,10 @@ void BackgroundHelper::removeBackground(int nHist, HistogramX &x_data,
 
     // use thread-specific unit conversion class to avoid multithreading issues
     Kernel::Unit *unitConv = m_WSUnit[threadNum].get();
-    unitConv->initialize(L1, L2, twoTheta, m_Emode, m_Efix, delta);
+    Kernel::UnitParametersMap pmap{};
+    m_spectrumInfo->getDetectorValues(*unitConv, Kernel::Units::TOF{}, m_Emode,
+                                      false, nHist, pmap);
+    unitConv->initialize(L1, m_Emode, pmap);
 
     x_data[0] = XValues[0];
     double tof1 = unitConv->singleToTOF(x_data[0]);
@@ -315,46 +314,6 @@ void BackgroundHelper::removeBackground(int nHist, HistogramX &x_data,
           << nHist;
   }
 }
-/** Method returns the efixed or Ei value stored in properties of the input
- *workspace.
- *  Indirect instruments can have eFxed and Direct instruments can have Ei
- *defined as the properties of the workspace.
- *
- *  This method provide guess for efixed for all other kind of instruments.
- *Correct indirect instrument will overwrite
- *  this value while wrongly defined or different types of instruments will
- *provide the value of "Ei" property (log value)
- *  or undefined if "Ei" property is not found.
- *
- */
-double
-BackgroundHelper::getEi(const API::MatrixWorkspace_const_sptr &inputWS) const {
-  double Efi = std::numeric_limits<double>::quiet_NaN();
-
-  // is Ei on workspace properties? (it can be defined for some reason if
-  // detectors do not have one, and then it would exist as Ei)
-  bool EiFound(false);
-  try {
-    Efi = inputWS->run().getPropertyValueAsType<double>("Ei");
-    EiFound = true;
-  } catch (Kernel::Exception::NotFoundError &) {
-  }
-  // try to get Efixed as property on a workspace, obtained for indirect
-  // instrument
-  // bool eFixedFound(false);
-  if (!EiFound) {
-    try {
-      Efi = inputWS->run().getPropertyValueAsType<double>("eFixed");
-      // eFixedFound = true;
-    } catch (Kernel::Exception::NotFoundError &) {
-    }
-  }
-
-  // if (!(EiFound||eFixedFound))
-  //  g_log.debug()<<" Ei/eFixed requested but have not been found\n";
-
-  return Efi;
-}
 
 } // namespace Algorithms
 } // namespace Mantid
diff --git a/Framework/Algorithms/src/RemoveBins.cpp b/Framework/Algorithms/src/RemoveBins.cpp
index 1024f935fe3ade67750e3642ea2a1cc9a942481c..c27b740c4d319ae078b4d86cf9e9908eab298100 100644
--- a/Framework/Algorithms/src/RemoveBins.cpp
+++ b/Framework/Algorithms/src/RemoveBins.cpp
@@ -268,16 +268,36 @@ void RemoveBins::transformRangeUnit(const int index, double &startX,
     startX = factor * std::pow(m_startX, power);
     endX = factor * std::pow(m_endX, power);
   } else {
-    double l1, l2, theta;
-    this->calculateDetectorPosition(index, l1, l2, theta);
+    double l1 = m_spectrumInfo->l1();
+
+    Kernel::UnitParametersMap pmap{};
+    m_spectrumInfo->getDetectorValues(*m_rangeUnit, *inputUnit,
+                                      Kernel::DeltaEMode::Elastic, false, index,
+                                      pmap);
+    double l2 = 0.;
+    if (pmap.find(UnitParams::l2) != pmap.end()) {
+      l2 = pmap[UnitParams::l2];
+    }
+    double theta = 0.;
+    if (pmap.find(UnitParams::twoTheta) != pmap.end()) {
+      l2 = pmap[UnitParams::twoTheta];
+    }
+    g_log.debug() << "Detector for index " << index << " has L1+L2=" << l1 + l2
+                  << " & 2theta= " << theta << '\n';
     std::vector<double> endPoints;
     endPoints.emplace_back(startX);
     endPoints.emplace_back(endX);
-    std::vector<double> emptyVec;
-    m_rangeUnit->toTOF(endPoints, emptyVec, l1, l2, theta, 0, 0.0, 0.0);
-    inputUnit->fromTOF(endPoints, emptyVec, l1, l2, theta, 0, 0.0, 0.0);
-    startX = endPoints.front();
-    endX = endPoints.back();
+    try {
+      std::vector<double> emptyVec;
+      // assume elastic
+      m_rangeUnit->toTOF(endPoints, emptyVec, l1, 0, pmap);
+      inputUnit->fromTOF(endPoints, emptyVec, l1, 0, pmap);
+      startX = endPoints.front();
+      endX = endPoints.back();
+    } catch (std::exception &) {
+      throw std::runtime_error("Unable to retrieve detector properties "
+                               "required for unit conversion");
+    }
   }
 
   if (startX > endX) {
@@ -290,25 +310,6 @@ void RemoveBins::transformRangeUnit(const int index, double &startX,
                 << startX << "-" << endX << " in workspace's unit\n";
 }
 
-/** Retrieves the detector postion for a given spectrum
- *  @param index ::    The workspace index of the spectrum
- *  @param l1 ::       Returns the source-sample distance
- *  @param l2 ::       Returns the sample-detector distance
- *  @param twoTheta :: Returns the detector's scattering angle
- */
-void RemoveBins::calculateDetectorPosition(const int index, double &l1,
-                                           double &l2, double &twoTheta) {
-  l1 = m_spectrumInfo->l1();
-  l2 = m_spectrumInfo->l2(index);
-  if (m_spectrumInfo->isMonitor(index))
-    twoTheta = 0.0;
-  else
-    twoTheta = m_spectrumInfo->twoTheta(index);
-
-  g_log.debug() << "Detector for index " << index << " has L1+L2=" << l1 + l2
-                << " & 2theta= " << twoTheta << '\n';
-}
-
 /** Finds the index in an ordered vector which follows the given value
  *  @param value :: The value to search for
  *  @param vec ::   The vector to search
diff --git a/Framework/Algorithms/src/RemoveLowResTOF.cpp b/Framework/Algorithms/src/RemoveLowResTOF.cpp
index e829ec0fbe4b4f2f7050f77dab66506c5b86227c..50a2e71bc50c5dafbc75a890a3edb752523d9e09 100644
--- a/Framework/Algorithms/src/RemoveLowResTOF.cpp
+++ b/Framework/Algorithms/src/RemoveLowResTOF.cpp
@@ -246,17 +246,9 @@ double RemoveLowResTOF::calcTofMin(const std::size_t workspaceIndex,
 
   const double l1 = spectrumInfo.l1();
 
-  // Get a vector of detector IDs
-  std::vector<detid_t> detNumbers;
-  const auto &detSet = m_inputWS->getSpectrum(workspaceIndex).getDetectorIDs();
-  detNumbers.assign(detSet.begin(), detSet.end());
-
   double tmin = 0.;
   if (isEmpty(m_wavelengthMin)) {
-    std::map<detid_t, double> offsets; // just an empty offsets map
-    double dspmap = Conversion::tofToDSpacingFactor(
-        l1, spectrumInfo.l2(workspaceIndex),
-        spectrumInfo.twoTheta(workspaceIndex), detNumbers, offsets);
+    double dspmap = 1. / spectrumInfo.difcUncalibrated(workspaceIndex);
 
     // this is related to the reference tof
     double sqrtdmin =
@@ -274,7 +266,7 @@ double RemoveLowResTOF::calcTofMin(const std::size_t workspaceIndex,
     // unfortunately there isn't a good way to convert a single value
     std::vector<double> X(1), temp(1);
     X[0] = m_wavelengthMin;
-    wavelength->toTOF(X, temp, l1, l2, 0., 0, 0., 0.);
+    wavelength->toTOF(X, temp, l1, 0, {{UnitParams::l2, l2}});
     tmin = X[0];
   }
 
diff --git a/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp b/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp
index 7aadade448cb072f860db9caa30a2325fc02a861..dc5c1f1dc6a216372f06f1c4a9f8ef8a60621f56 100644
--- a/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp
+++ b/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp
@@ -79,7 +79,7 @@ void MCAbsorptionStrategy::calculate(Kernel::PseudoRandomNumberGenerator &rng,
                                      std::vector<double> &attenuationFactors,
                                      std::vector<double> &attFactorErrors,
                                      MCInteractionStatistics &stats) {
-  const auto scatterBounds = m_scatterVol.getBoundingBox();
+  const auto scatterBounds = m_scatterVol.getFullBoundingBox();
   const auto nbins = static_cast<int>(lambdas.size());
 
   std::vector<double> wgtMean(attenuationFactors.size()),
diff --git a/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp b/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp
index c542794cb024753b7f8d50ee1508f60282529163..9ab623081786067b5f6867a81161814b2fdf79ce 100644
--- a/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp
+++ b/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp
@@ -61,21 +61,13 @@ MCInteractionVolume::MCInteractionVolume(
   }
 }
 
-/**
- * Returns the axis-aligned bounding box for the volume
- * @return A reference to the bounding box
- */
-const Geometry::BoundingBox &MCInteractionVolume::getBoundingBox() const {
-  return m_sample->getBoundingBox();
-}
-
 /**
  * Returns the axis-aligned bounding box for the volume including env
  * @return A reference to the bounding box
  */
 const Geometry::BoundingBox MCInteractionVolume::getFullBoundingBox() const {
   auto sampleBox = m_sample->getBoundingBox();
-  if (m_env) {
+  if (m_pointsIn != ScatteringPointVicinity::SAMPLEONLY && m_env) {
     const auto &envBox = m_env->boundingBox();
     sampleBox.grow(envBox);
   }
diff --git a/Framework/Algorithms/test/CompareWorkspacesTest.h b/Framework/Algorithms/test/CompareWorkspacesTest.h
index bc0044309df884931fdcf390f1d32a5d635e98bd..84da1e095bb30c7c4462ebc88a78b17b67f1d7ef 100644
--- a/Framework/Algorithms/test/CompareWorkspacesTest.h
+++ b/Framework/Algorithms/test/CompareWorkspacesTest.h
@@ -17,6 +17,7 @@
 #include "MantidAlgorithms/CompareWorkspaces.h"
 #include "MantidAlgorithms/CreatePeaksWorkspace.h"
 #include "MantidDataObjects/EventWorkspace.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/MDBoxBase.h"
 #include "MantidDataObjects/MDHistoWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
@@ -124,6 +125,23 @@ public:
     TS_ASSERT_EQUALS(checker.getPropertyValue("Result"), PROPERTY_VALUE_TRUE);
   }
 
+  void test_LeanPeaksWorkspaceMatches() {
+    // generate a lean elastic peak workspace with two peaks
+    auto lpws = std::make_shared<LeanElasticPeaksWorkspace>();
+    // add peaks
+    LeanElasticPeak pk1(V3D(0.0, 0.0, 6.28319), 2.0);     // (100)
+    LeanElasticPeak pk2(V3D(6.28319, 0.0, 6.28319), 1.0); // (110)
+    lpws->addPeak(pk1);
+    lpws->addPeak(pk2);
+
+    TS_ASSERT_THROWS_NOTHING(checker.setProperty(
+        "Workspace1", std::dynamic_pointer_cast<Workspace>(lpws)));
+    TS_ASSERT_THROWS_NOTHING(checker.setProperty(
+        "Workspace2", std::dynamic_pointer_cast<Workspace>(lpws)));
+    TS_ASSERT(checker.execute());
+    TS_ASSERT_EQUALS(checker.getPropertyValue("Result"), PROPERTY_VALUE_TRUE);
+  }
+
   void testPeaks_extrapeak() {
     if (!checker.isInitialized())
       checker.initialize();
diff --git a/Framework/Algorithms/test/ConvertSpectrumAxisTest.h b/Framework/Algorithms/test/ConvertSpectrumAxisTest.h
index fcab91851b1cd6322fa37ee7400e873442b0c2f6..c6d9e4adffe41aa3befdbc1cef83233d619dcea8 100644
--- a/Framework/Algorithms/test/ConvertSpectrumAxisTest.h
+++ b/Framework/Algorithms/test/ConvertSpectrumAxisTest.h
@@ -142,7 +142,7 @@ public:
     TS_ASSERT_THROWS_NOTHING(conv.setPropertyValue("Target", "DeltaE"));
     TS_ASSERT_THROWS_NOTHING(conv.setPropertyValue("EMode", "Indirect"));
     conv.setRethrows(true);
-    TS_ASSERT_THROWS(conv.execute(), const std::logic_error &);
+    TS_ASSERT_THROWS(conv.execute(), const std::runtime_error &);
 
     TS_ASSERT_THROWS_NOTHING(conv.setPropertyValue("Efixed", "1.845"));
     TS_ASSERT_THROWS_NOTHING(conv.execute());
diff --git a/Framework/Algorithms/test/ConvertUnitsTest.h b/Framework/Algorithms/test/ConvertUnitsTest.h
index 43d01300fddd4c214419e9ccce2b587076b4d866..c3d4574daedf96ee859af1a1796fd02c019adb53 100644
--- a/Framework/Algorithms/test/ConvertUnitsTest.h
+++ b/Framework/Algorithms/test/ConvertUnitsTest.h
@@ -13,6 +13,7 @@
 #include "MantidAPI/AnalysisDataService.h"
 #include "MantidAPI/Axis.h"
 #include "MantidAPI/MatrixWorkspace.h"
+#include "MantidAPI/SpectrumInfo.h"
 #include "MantidAlgorithms/ConvertToDistribution.h"
 #include "MantidAlgorithms/ConvertUnits.h"
 #include "MantidDataHandling/LoadInstrument.h"
@@ -674,7 +675,11 @@ public:
             outputSpace));
     TS_ASSERT_EQUALS(output->getAxis(0)->unit()->unitID(), "MomentumTransfer");
     TS_ASSERT_EQUALS(Mantid::Kernel::DeltaEMode::Direct, output->getEMode());
-    TS_ASSERT(std::isnan(output->x(0)[0]));
+
+    // conversion fails due to error in two theta calculation and leaves
+    // spectrum masked and zeroed
+    TS_ASSERT_EQUALS(output->y(0)[0], 0);
+    TS_ASSERT(output->spectrumInfo().isMasked(0));
   }
 
   void setup_Event() {
@@ -762,6 +767,13 @@ public:
     ws->getAxis(0)->setUnit("TOF");
     ws->sortAll(sortType, nullptr);
 
+    // 0th detector unfortunately has difc=0 which doesn't support conversion to
+    // d spacing so give it a more helpful difc value
+    auto instrument = ws->getInstrument();
+    auto det = instrument->getDetector(100);
+    auto &paramMap = ws->instrumentParameters();
+    paramMap.addDouble(det->getComponentID(), "DIFC", 1000);
+
     if (sortType == TOF_SORT) {
       // Only threadsafe if all the event lists are sorted
       TS_ASSERT(ws->threadSafe());
diff --git a/Framework/Algorithms/test/CreateDetectorTableTest.h b/Framework/Algorithms/test/CreateDetectorTableTest.h
index 4a29716697918406535cc650580481f477abcdb4..66cf1963246414d8f299e2b8c2c31d8742e6f393 100644
--- a/Framework/Algorithms/test/CreateDetectorTableTest.h
+++ b/Framework/Algorithms/test/CreateDetectorTableTest.h
@@ -82,7 +82,7 @@ public:
     }
 
     // Check the results
-    TS_ASSERT_EQUALS(ws->columnCount(), 7);
+    TS_ASSERT_EQUALS(ws->columnCount(), 11);
     TS_ASSERT_EQUALS(ws->rowCount(), 2);
 
     // Remove workspace from the data service.
@@ -117,7 +117,7 @@ public:
     }
 
     // Check the results
-    TS_ASSERT_EQUALS(ws->columnCount(), 9);
+    TS_ASSERT_EQUALS(ws->columnCount(), 13);
     TS_ASSERT_EQUALS(ws->rowCount(), 1);
 
     // Remove workspace from the data service.
diff --git a/Framework/Algorithms/test/MCInteractionVolumeTest.h b/Framework/Algorithms/test/MCInteractionVolumeTest.h
index b05ac1ed6a8082ec7c82ce247244f62b024280c0..c1d382a7070c2a0c6b066af3a89ab92321f10b1d 100644
--- a/Framework/Algorithms/test/MCInteractionVolumeTest.h
+++ b/Framework/Algorithms/test/MCInteractionVolumeTest.h
@@ -43,7 +43,7 @@ public:
     MCInteractionVolume interactor(sample);
     interactor.setActiveRegion(sampleBox);
 
-    const auto interactionBox = interactor.getBoundingBox();
+    const auto interactionBox = interactor.getFullBoundingBox();
     TS_ASSERT_EQUALS(sampleBox.minPoint(), interactionBox.minPoint());
     TS_ASSERT_EQUALS(sampleBox.maxPoint(), interactionBox.maxPoint());
   }
diff --git a/Framework/Algorithms/test/PDCalibrationTest.h b/Framework/Algorithms/test/PDCalibrationTest.h
index 3fdd6915205d138fb527b5fd20e1b3492fdb8047..6ca3bad06bd963e3284630e2d15b976875474b4e 100644
--- a/Framework/Algorithms/test/PDCalibrationTest.h
+++ b/Framework/Algorithms/test/PDCalibrationTest.h
@@ -21,7 +21,7 @@
 #include "MantidDataHandling/MoveInstrumentComponent.h"
 #include "MantidDataHandling/RotateInstrumentComponent.h"
 #include "MantidDataObjects/TableColumn.h"
-#include "MantidKernel/Diffraction.h"
+#include "MantidKernel/Unit.h"
 
 using Mantid::Algorithms::ConvertToMatrixWorkspace;
 using Mantid::Algorithms::CreateSampleWorkspace;
@@ -161,10 +161,13 @@ public:
 
   void test_exec_difc() {
     // setup the peak postions based on transformation from detID=155
-    std::vector<double> dValues(PEAK_TOFS.size());
-    std::transform(
-        PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(),
-        Mantid::Kernel::Diffraction::getTofToDConversionFunc(DIFC_155, 0., 0.));
+    using Mantid::Kernel::UnitParams;
+    std::vector<double> dValues(PEAK_TOFS);
+    Mantid::Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> unusedy;
+    dSpacingUnit.fromTOF(
+        dValues, unusedy, -1., 0,
+        Mantid::Kernel::UnitParametersMap{{UnitParams::difc, DIFC_155}});
 
     const std::string prefix{"PDCalibration_difc"};
 
@@ -219,12 +222,16 @@ public:
   }
 
   void test_exec_difc_tzero() {
+    using Mantid::Kernel::UnitParams;
     // setup the peak postions based on transformation from detID=155
     const double TZERO = 20.;
-    std::vector<double> dValues(PEAK_TOFS.size());
-    std::transform(PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(),
-                   Mantid::Kernel::Diffraction::getTofToDConversionFunc(
-                       DIFC_155, 0., TZERO));
+    std::vector<double> dValues(PEAK_TOFS);
+    Mantid::Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> unusedy;
+    dSpacingUnit.fromTOF(
+        dValues, unusedy, -1., 0,
+        Mantid::Kernel::UnitParametersMap{{UnitParams::difc, DIFC_155},
+                                          {UnitParams::tzero, TZERO}});
 
     const std::string prefix{"PDCalibration_difc_tzero"};
 
@@ -282,13 +289,17 @@ public:
   }
 
   void test_exec_difc_tzero_difa() {
+    using Mantid::Kernel::UnitParams;
     // setup the peak postions based on transformation from detID=155
     // allow refining DIFA, but don't set the transformation to require it
     const double TZERO = 20.;
-    std::vector<double> dValues(PEAK_TOFS.size());
-    std::transform(PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(),
-                   Mantid::Kernel::Diffraction::getTofToDConversionFunc(
-                       DIFC_155, 0., TZERO));
+    std::vector<double> dValues(PEAK_TOFS);
+    Mantid::Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> unusedy;
+    dSpacingUnit.fromTOF(
+        dValues, unusedy, -1., 0,
+        Mantid::Kernel::UnitParametersMap{{UnitParams::difc, DIFC_155},
+                                          {UnitParams::tzero, TZERO}});
 
     const std::string prefix{"PDCalibration_difc_tzero_difa"};
 
@@ -348,6 +359,7 @@ public:
   // Crop workspace so that final peak is evaluated over a range that includes
   // the last bin (stop regression out of range bug for histo workspaces)
   void test_exec_difc_histo() {
+    using Mantid::Kernel::UnitParams;
     // convert to histo
     ConvertToMatrixWorkspace convMatWS;
     convMatWS.initialize();
@@ -365,10 +377,13 @@ public:
     cropWS.execute();
 
     // setup the peak postions based on transformation from detID=155
-    std::vector<double> dValues(PEAK_TOFS.size());
-    std::transform(
-        PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(),
-        Mantid::Kernel::Diffraction::getTofToDConversionFunc(DIFC_155, 0., 0.));
+    std::vector<double> dValues(PEAK_TOFS);
+
+    Mantid::Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> unusedy;
+    dSpacingUnit.fromTOF(
+        dValues, unusedy, -1., 0,
+        Mantid::Kernel::UnitParametersMap{{UnitParams::difc, DIFC_155}});
 
     const std::string prefix{"PDCalibration_difc"};
 
@@ -414,13 +429,16 @@ public:
   }
 
   void test_exec_fit_diff_constants_with_chisq() {
+    using Mantid::Kernel::UnitParams;
     // setup the peak postions based on transformation from detID=155
     // allow refining DIFA, but don't set the transformation to require it
     // setup the peak postions based on transformation from detID=155
-    std::vector<double> dValues(PEAK_TOFS.size());
-    std::transform(
-        PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(),
-        Mantid::Kernel::Diffraction::getTofToDConversionFunc(DIFC_155, 0., 0.));
+    std::vector<double> dValues(PEAK_TOFS);
+    Mantid::Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> unusedy;
+    dSpacingUnit.fromTOF(
+        dValues, unusedy, -1., 0,
+        Mantid::Kernel::UnitParametersMap{{UnitParams::difc, DIFC_155}});
 
     const std::string prefix{"PDCalibration_difc"};
 
@@ -468,6 +486,7 @@ public:
   }
 
   void test_exec_grouped_detectors() {
+    using Mantid::Kernel::UnitParams;
     // group detectors
     GroupDetectors2 groupDet;
     groupDet.initialize();
@@ -477,10 +496,12 @@ public:
     groupDet.execute();
 
     // setup the peak postions based on transformation from detID=155
-    std::vector<double> dValues(PEAK_TOFS.size());
-    std::transform(
-        PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(),
-        Mantid::Kernel::Diffraction::getTofToDConversionFunc(DIFC_155, 0., 0.));
+    std::vector<double> dValues(PEAK_TOFS);
+    Mantid::Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> unusedy;
+    dSpacingUnit.fromTOF(
+        dValues, unusedy, -1., 0,
+        Mantid::Kernel::UnitParametersMap{{UnitParams::difc, DIFC_155}});
 
     const std::string prefix{"PDCalibration_difc"};
 
@@ -532,11 +553,14 @@ public:
   PDCalibrationTestPerformance() { FrameworkManager::Instance(); }
 
   void setUp() override {
+    using Mantid::Kernel::UnitParams;
     // setup the peak postions based on transformation from detID=155
     std::vector<double> dValues(PEAK_TOFS.size());
-    std::transform(
-        PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(),
-        Mantid::Kernel::Diffraction::getTofToDConversionFunc(DIFC_155, 0., 0.));
+    Mantid::Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> unusedy;
+    dSpacingUnit.fromTOF(
+        dValues, unusedy, -1., 0,
+        Mantid::Kernel::UnitParametersMap{{UnitParams::difc, DIFC_155}});
     createSampleWS();
     pdc.initialize();
     pdc.setProperty("InputWorkspace", "PDCalibrationTest_WS");
diff --git a/Framework/Algorithms/test/RemoveBackgroundTest.h b/Framework/Algorithms/test/RemoveBackgroundTest.h
index a1f956fb63d79c7295a363f713d66e87f7f6a46a..de315e85e9bcba1912955871abee4169ff5f6c77 100644
--- a/Framework/Algorithms/test/RemoveBackgroundTest.h
+++ b/Framework/Algorithms/test/RemoveBackgroundTest.h
@@ -95,13 +95,14 @@ public:
         std::vector<double>(1, 10.));
     TSM_ASSERT_THROWS(
         "Should throw if background workspace is not in TOF units",
-        bgRemoval.initialize(bkgWS, SourceWS, 0),
+        bgRemoval.initialize(bkgWS, SourceWS, Kernel::DeltaEMode::Elastic),
         const std::invalid_argument &);
 
     bkgWS = WorkspaceCreationHelper::create2DWorkspaceWithFullInstrument(2, 15);
-    TSM_ASSERT_THROWS("Should throw if background is not 1 or equal to source",
-                      bgRemoval.initialize(bkgWS, SourceWS, 0),
-                      const std::invalid_argument &);
+    TSM_ASSERT_THROWS(
+        "Should throw if background is not 1 or equal to source",
+        bgRemoval.initialize(bkgWS, SourceWS, Kernel::DeltaEMode::Elastic),
+        const std::invalid_argument &);
   }
 
   void testBackgroundHelper() {
@@ -110,7 +111,7 @@ public:
 
     API::AnalysisDataService::Instance().addOrReplace("TestWS", clone);
 
-    int emode = static_cast<int>(Kernel::DeltaEMode().fromString("Direct"));
+    auto emode = Kernel::DeltaEMode().fromString("Direct");
     bgRemoval.initialize(BgWS, SourceWS, emode);
 
     auto &dataX = clone->mutableX(0);
diff --git a/Framework/Algorithms/test/RemoveSpectraTest.h b/Framework/Algorithms/test/RemoveSpectraTest.h
index dd158032d81b0124329189a56e102e775e786756..33ddd35a7e81f8beab5b13c7b6aed754fb053fe2 100644
--- a/Framework/Algorithms/test/RemoveSpectraTest.h
+++ b/Framework/Algorithms/test/RemoveSpectraTest.h
@@ -11,6 +11,7 @@
 #include "MantidAPI/AlgorithmManager.h"
 #include "MantidAPI/AnalysisDataService.h"
 #include "MantidAPI/MatrixWorkspace.h"
+#include "MantidAPI/SpectrumInfo.h"
 #include "MantidAPI/WorkspaceFactory.h"
 #include "MantidAlgorithms/RemoveSpectra.h"
 #include "MantidHistogramData/LinearGenerator.h"
@@ -89,6 +90,7 @@ public:
         AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(wsName);
     TS_ASSERT_EQUALS(inputWS->x(94).front(), 19900.0);
     TS_ASSERT_EQUALS(inputWS->x(144).front(), 19900.0);
+    TS_ASSERT(!inputWS->spectrumInfo().hasDetectors(144));
 
     RemoveSpectra alg;
     alg.initialize();
@@ -102,13 +104,13 @@ public:
     MatrixWorkspace_sptr outputWS =
         AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
             ouputWsName);
-    // Removed specs are workspace indices 140 and 144/specNum 141 and 145
+    // Removed specs are workspace indices 94 and 144/specNum 95 and 145
     TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 147);
     TS_ASSERT_DELTA(outputWS->x(93).front(), 0.41157, 0.0001);  // was 93
     TS_ASSERT_DELTA(outputWS->x(94).front(), 0.05484, 0.0001);  // was 95
     TS_ASSERT_DELTA(outputWS->x(95).front(), -0.15111, 0.0001); // was 96
-    TS_ASSERT_DIFFERS(outputWS->x(143).front(),
-                      19900.0); // Would be 144 if 94 wasn't also removed
+    TS_ASSERT(outputWS->spectrumInfo().hasDetectors(
+        143)); // Would be 144 if 144 wasn't also removed
   }
 
 private:
diff --git a/Framework/Crystal/src/AnvredCorrection.cpp b/Framework/Crystal/src/AnvredCorrection.cpp
index 614c2ca3904f43f4969a08208f7bda7563f05ad8..f2b7cb13e805b56ffb8065629973429191c9953e 100644
--- a/Framework/Crystal/src/AnvredCorrection.cpp
+++ b/Framework/Crystal/src/AnvredCorrection.cpp
@@ -179,12 +179,14 @@ void AnvredCorrection::exec() {
     if (!spectrumInfo.hasDetectors(i) || spectrumInfo.isMonitor(i))
       continue;
 
-    // This is the scattered beam direction
     Instrument_const_sptr inst = m_inputWS->getInstrument();
-    double L2 = spectrumInfo.l2(i);
-    // Two-theta = polar angle = scattering angle = between +Z vector and the
-    // scattered beam
-    double scattering = spectrumInfo.twoTheta(i);
+    UnitParametersMap pmap{};
+    Mantid::Kernel::Units::Wavelength wl;
+    Mantid::Kernel::Units::TOF tof;
+    spectrumInfo.getDetectorValues(tof, wl, Kernel::DeltaEMode::Elastic, false,
+                                   i, pmap);
+    double L2 = pmap.at(UnitParams::l2);
+    double scattering = pmap.at(UnitParams::twoTheta);
 
     double depth = 0.2;
 
@@ -196,7 +198,6 @@ void AnvredCorrection::exec() {
     if (m_useScaleFactors)
       scale_init(det, inst, L2, depth, pathlength, bankName);
 
-    Mantid::Kernel::Units::Wavelength wl;
     auto points = m_inputWS->points(i);
 
     // share bin boundaries
@@ -213,10 +214,9 @@ void AnvredCorrection::exec() {
     // Loop through the bins in the current spectrum
     for (int64_t j = 0; j < specSize; j++) {
 
-      double lambda =
-          (unitStr == "TOF")
-              ? wl.convertSingleFromTOF(points[j], L1, L2, scattering, 0, 0, 0)
-              : points[j];
+      double lambda = (unitStr == "TOF")
+                          ? wl.convertSingleFromTOF(points[j], L1, 0, pmap)
+                          : points[j];
 
       if (m_returnTransmissionOnly) {
         Y[j] = 1.0 / this->getEventWeight(lambda, scattering);
@@ -280,18 +280,18 @@ void AnvredCorrection::execEvent() {
     if (!spectrumInfo.hasDetectors(i) || spectrumInfo.isMonitor(i))
       continue;
 
-    // This is the scattered beam direction
-    double L2 = spectrumInfo.l2(i);
-    // Two-theta = polar angle = scattering angle = between +Z vector and the
-    // scattered beam
-    double scattering = spectrumInfo.twoTheta(i);
+    UnitParametersMap pmap{};
+    Mantid::Kernel::Units::Wavelength wl;
+    Mantid::Kernel::Units::TOF tof;
+    spectrumInfo.getDetectorValues(tof, wl, Kernel::DeltaEMode::Elastic, false,
+                                   i, pmap);
+    double L2 = pmap.at(UnitParams::l2);
+    double scattering = pmap.at(UnitParams::twoTheta);
 
     EventList el = eventW->getSpectrum(i);
     el.switchTo(WEIGHTED_NOTIME);
     std::vector<WeightedEventNoTime> events = el.getWeightedEventsNoTime();
 
-    Mantid::Kernel::Units::Wavelength wl;
-
     double depth = 0.2;
     double pathlength = 0.0;
     std::string bankName;
@@ -306,7 +306,7 @@ void AnvredCorrection::execEvent() {
       double lambda = ev.tof();
 
       if ("TOF" == unitStr)
-        lambda = wl.convertSingleFromTOF(lambda, L1, L2, scattering, 0, 0, 0);
+        lambda = wl.convertSingleFromTOF(lambda, L1, 0, pmap);
 
       double value = this->getEventWeight(lambda, scattering);
 
diff --git a/Framework/Crystal/src/CentroidPeaks.cpp b/Framework/Crystal/src/CentroidPeaks.cpp
index 0625243a671c72a6358b47cf53edc572fce943cc..85d239310be23896a62da0c851fa52ab35b01a44 100644
--- a/Framework/Crystal/src/CentroidPeaks.cpp
+++ b/Framework/Crystal/src/CentroidPeaks.cpp
@@ -168,7 +168,8 @@ void CentroidPeaks::integrate() {
       double scattering = peak.getScattering();
       double L1 = peak.getL1();
       double L2 = peak.getL2();
-      wl.fromTOF(timeflight, timeflight, L1, L2, scattering, 0, 0, 0);
+      wl.fromTOF(timeflight, timeflight, L1, 0,
+                 {{UnitParams::l2, L2}, {UnitParams::twoTheta, scattering}});
       const double lambda = timeflight[0];
       timeflight.clear();
 
@@ -289,7 +290,8 @@ void CentroidPeaks::integrateEvent() {
       double scattering = peak.getScattering();
       double L1 = peak.getL1();
       double L2 = peak.getL2();
-      wl.fromTOF(timeflight, timeflight, L1, L2, scattering, 0, 0, 0);
+      wl.fromTOF(timeflight, timeflight, L1, 0,
+                 {{UnitParams::l2, L2}, {UnitParams::twoTheta, scattering}});
       const double lambda = timeflight[0];
       timeflight.clear();
 
diff --git a/Framework/Crystal/src/FindSXPeaksHelper.cpp b/Framework/Crystal/src/FindSXPeaksHelper.cpp
index c36783614c4aea3d79324b8f9ae16dce75733388..729f5db9d39239624e1815e20c9ffdc35aae0b52 100644
--- a/Framework/Crystal/src/FindSXPeaksHelper.cpp
+++ b/Framework/Crystal/src/FindSXPeaksHelper.cpp
@@ -94,9 +94,17 @@ SXPeak::SXPeak(double t, double phi, double intensity,
   m_detId = spectrumInfo.detector(m_wsIndex).getID();
   m_nPixels = 1;
 
+  Mantid::Kernel::Units::TOF tof;
   const auto unit = Mantid::Kernel::UnitFactory::Instance().create("dSpacing");
-  unit->initialize(l1, l2, m_twoTheta, 0, 0, 0);
-  m_dSpacing = unit->singleFromTOF(m_tof);
+  Kernel::UnitParametersMap pmap{};
+  spectrumInfo.getDetectorValues(tof, *unit, Kernel::DeltaEMode::Elastic, false,
+                                 m_wsIndex, pmap);
+  unit->initialize(l1, 0, pmap);
+  try {
+    m_dSpacing = unit->singleFromTOF(m_tof);
+  } catch (std::exception &) {
+    m_dSpacing = 0;
+  }
 
   const auto samplePos = spectrumInfo.samplePosition();
   const auto sourcePos = spectrumInfo.sourcePosition();
@@ -365,9 +373,12 @@ double PeakFindingStrategy::convertToTOF(const double xValue,
     return xValue;
   } else {
     const auto unit = UnitFactory::Instance().create("dSpacing");
+    Mantid::Kernel::Units::TOF tof;
+    Kernel::UnitParametersMap pmap{};
+    m_spectrumInfo.getDetectorValues(*unit, tof, Kernel::DeltaEMode::Elastic,
+                                     false, workspaceIndex, pmap);
     // we're using d-spacing, convert the point to TOF
-    unit->initialize(m_spectrumInfo.l1(), m_spectrumInfo.l2(workspaceIndex),
-                     m_spectrumInfo.twoTheta(workspaceIndex), 0, 0, 0);
+    unit->initialize(m_spectrumInfo.l1(), 0, pmap);
     return unit->singleToTOF(xValue);
   }
 }
diff --git a/Framework/Crystal/src/LoadIsawPeaks.cpp b/Framework/Crystal/src/LoadIsawPeaks.cpp
index 47bcd6780c3f014e13d9d2ed2e798ddbe244fe21..75d27e0deb8fb43ae54209b45bcd178779dbac40 100644
--- a/Framework/Crystal/src/LoadIsawPeaks.cpp
+++ b/Framework/Crystal/src/LoadIsawPeaks.cpp
@@ -525,8 +525,9 @@ void LoadIsawPeaks::appendFile(const PeaksWorkspace_sptr &outWS,
       double tof = peak.getTOF();
       Kernel::Units::Wavelength wl;
 
-      wl.initialize(peak.getL1(), peak.getL2(), peak.getScattering(), 0,
-                    peak.getInitialEnergy(), 0.0);
+      wl.initialize(peak.getL1(), 0,
+                    {{UnitParams::l2, peak.getL2()},
+                     {UnitParams::twoTheta, peak.getScattering()}});
 
       peak.setWavelength(wl.singleFromTOF(tof));
       // Add the peak to workspace
diff --git a/Framework/Crystal/src/LoadIsawSpectrum.cpp b/Framework/Crystal/src/LoadIsawSpectrum.cpp
index 5c9b438d279353051a08789051159926b6614d66..03720f3dc4fa9ff58b2d689a71a1b89abaace71a 100644
--- a/Framework/Crystal/src/LoadIsawSpectrum.cpp
+++ b/Framework/Crystal/src/LoadIsawSpectrum.cpp
@@ -160,7 +160,11 @@ void LoadIsawSpectrum::exec() {
 
     Mantid::Kernel::Unit_sptr unit =
         UnitFactory::Instance().create("Wavelength");
-    unit->toTOF(xdata, ydata, l1, l2, theta2, 0, 0.0, 0.0);
+    unit->toTOF(xdata, ydata, l1, 0,
+                {
+                    {UnitParams::l2, l2},
+                    {UnitParams::twoTheta, theta2},
+                });
     double one = xdata[0];
     double spect1 = spectrumCalc(one, iSpec, time, spectra, i);
 
diff --git a/Framework/Crystal/src/NormaliseVanadium.cpp b/Framework/Crystal/src/NormaliseVanadium.cpp
index 77871cba04ce1044b3292644a46abf8549b32b46..97ea0dcbbdb4d8d85a5347927f7e74152e8df287 100644
--- a/Framework/Crystal/src/NormaliseVanadium.cpp
+++ b/Framework/Crystal/src/NormaliseVanadium.cpp
@@ -86,17 +86,15 @@ void NormaliseVanadium::exec() {
     if (!spectrumInfo.hasDetectors(i))
       continue;
 
-    // This is the scattered beam direction
-    double L2 = spectrumInfo.l2(i);
-    // Two-theta = polar angle = scattering angle = between +Z vector and the
-    // scattered beam
-    double scattering = spectrumInfo.twoTheta(i);
-
     Mantid::Kernel::Units::Wavelength wl;
+    Mantid::Kernel::Units::TOF tof;
+    UnitParametersMap pmap{};
+    spectrumInfo.getDetectorValues(tof, wl, Kernel::DeltaEMode::Elastic, false,
+                                   i, pmap);
     auto timeflight = inSpec.points();
     if (unitStr == "TOF")
       wl.fromTOF(timeflight.mutableRawData(), timeflight.mutableRawData(), L1,
-                 L2, scattering, 0, 0, 0);
+                 0, pmap);
 
     // Loop through the bins in the current spectrum
     double lambp = 0;
diff --git a/Framework/Crystal/src/PeakHKLErrors.cpp b/Framework/Crystal/src/PeakHKLErrors.cpp
index 374410e132f7f59b303002d722c577bbb88bbe2a..c65b2a42db0c34af26d839f5dd3c43a0997119fa 100644
--- a/Framework/Crystal/src/PeakHKLErrors.cpp
+++ b/Framework/Crystal/src/PeakHKLErrors.cpp
@@ -662,8 +662,10 @@ Peak PeakHKLErrors::createNewPeak(const DataObjects::Peak &peak_old,
 
   Wavelength wl;
 
-  wl.initialize(L0, peak.getL2(), peak.getScattering(), 0,
-                peak_old.getInitialEnergy(), 0.0);
+  wl.initialize(L0, 0,
+                {{UnitParams::l2, peak.getL2()},
+                 {UnitParams::twoTheta, peak.getScattering()},
+                 {UnitParams::efixed, peak_old.getInitialEnergy()}});
 
   peak.setWavelength(wl.singleFromTOF(T));
   peak.setIntensity(peak_old.getIntensity());
diff --git a/Framework/Crystal/src/PeaksIntersection.cpp b/Framework/Crystal/src/PeaksIntersection.cpp
index a713553ed21c36bcad1f8f7085185792743fa6cd..dd91f53c31208e45151feec8b7f167ece2ef4372 100644
--- a/Framework/Crystal/src/PeaksIntersection.cpp
+++ b/Framework/Crystal/src/PeaksIntersection.cpp
@@ -16,6 +16,7 @@
 using namespace Mantid::API;
 using namespace Mantid::Geometry;
 using namespace Mantid::Kernel;
+using Mantid::DataObjects::Peak;
 using Mantid::DataObjects::PeaksWorkspace;
 using Mantid::DataObjects::PeaksWorkspace_sptr;
 
@@ -78,14 +79,14 @@ void PeaksIntersection::executePeaksIntersection(const bool checkPeakExtents) {
 
   m_peakRadius = this->getProperty("PeakRadius");
 
-  // Find the coordinate frame to use an set up boost function for this.
-  boost::function<V3D(IPeak *)> coordFrameFunc = &IPeak::getHKL;
+  boost::function<V3D(Peak *)> coordFrameFunc;
+  coordFrameFunc = &Peak::getHKL;
   if (coordinateFrame == detectorSpaceFrame()) {
-    coordFrameFunc = &IPeak::getDetectorPosition;
+    coordFrameFunc = &Peak::getDetectorPosition;
   } else if (coordinateFrame == qLabFrame()) {
-    coordFrameFunc = &IPeak::getQLabFrame;
+    coordFrameFunc = &Peak::getQLabFrame;
   } else if (coordinateFrame == qSampleFrame()) {
-    coordFrameFunc = &IPeak::getQSampleFrame;
+    coordFrameFunc = &Peak::getQSampleFrame;
   }
 
   // Create the faces.
@@ -131,7 +132,7 @@ void PeaksIntersection::executePeaksIntersection(const bool checkPeakExtents) {
   PARALLEL_FOR_IF(Kernel::threadSafe(*ws, *outputWorkspace))
   for (int i = 0; i < nPeaks; ++i) {
     PARALLEL_START_INTERUPT_REGION
-    IPeak *peak = ws->getPeakPtr(i);
+    Peak *peak = dynamic_cast<Peak *>(ws->getPeakPtr(i));
     V3D peakCenter = coordFrameFunc(peak);
 
     if (i % frequency == 0)
diff --git a/Framework/Crystal/src/SCDCalibratePanels.cpp b/Framework/Crystal/src/SCDCalibratePanels.cpp
index 9762c35f20a2411d6443604b68aa2409f2d94fe8..a878f1a59d0998b17f922410897941665ab4613c 100644
--- a/Framework/Crystal/src/SCDCalibratePanels.cpp
+++ b/Framework/Crystal/src/SCDCalibratePanels.cpp
@@ -379,8 +379,9 @@ void SCDCalibratePanels::findT0(
 
     Units::Wavelength wl;
 
-    wl.initialize(peak.getL1(), peak.getL2(), peak.getScattering(), 0,
-                  peak.getInitialEnergy(), 0.0);
+    wl.initialize(peak.getL1(), 0,
+                  {{UnitParams::l2, peak.getL2()},
+                   {UnitParams::twoTheta, peak.getScattering()}});
     peak.setWavelength(wl.singleFromTOF(peak.getTOF() + mT0));
   }
 }
diff --git a/Framework/Crystal/src/SCDCalibratePanels2.cpp b/Framework/Crystal/src/SCDCalibratePanels2.cpp
index 71b39d29fa93b1f9cdbbeb42d0ef850ec0f66f53..e54bd2694719f49c2fa2cea9d3699d1b1ac56264 100644
--- a/Framework/Crystal/src/SCDCalibratePanels2.cpp
+++ b/Framework/Crystal/src/SCDCalibratePanels2.cpp
@@ -685,8 +685,10 @@ void SCDCalibratePanels2::adjustT0(double dT0, IPeaksWorkspace_sptr &pws) {
   for (int i = 0; i < pws->getNumberPeaks(); ++i) {
     IPeak &pk = pws->getPeak(i);
     Units::Wavelength wl;
-    wl.initialize(pk.getL1(), pk.getL2(), pk.getScattering(), 0,
-                  pk.getInitialEnergy(), 0.0);
+    wl.initialize(pk.getL1(), 0,
+                  {{UnitParams::l2, pk.getL2()},
+                   {UnitParams::twoTheta, pk.getScattering()},
+                   {UnitParams::efixed, pk.getInitialEnergy()}});
     pk.setWavelength(wl.singleFromTOF(pk.getTOF() + dT0));
   }
 }
diff --git a/Framework/Crystal/src/SCDCalibratePanels2ObjFunc.cpp b/Framework/Crystal/src/SCDCalibratePanels2ObjFunc.cpp
index 64c88ab9e87f734e8988da45e599809c525fcbbc..cba4f70f11e2cfd71a74b7fbc81485ab4743bfb4 100644
--- a/Framework/Crystal/src/SCDCalibratePanels2ObjFunc.cpp
+++ b/Framework/Crystal/src/SCDCalibratePanels2ObjFunc.cpp
@@ -137,8 +137,10 @@ void SCDCalibratePanels2ObjFunc::function1D(double *out, const double *xValues,
     pk.setDetectorID(pk.getDetectorID());
     // calculate&set wavelength based on new instrument
     Units::Wavelength wl;
-    wl.initialize(pk.getL1(), pk.getL2(), pk.getScattering(), 0,
-                  pk.getInitialEnergy(), 0.0);
+    wl.initialize(pk.getL1(), 0,
+                  {{UnitParams::l2, pk.getL2()},
+                   {UnitParams::twoTheta, pk.getScattering()},
+                   {UnitParams::efixed, pk.getInitialEnergy()}});
     pk.setWavelength(wl.singleFromTOF(tof));
 
     V3D qv = pk.getQSampleFrame();
diff --git a/Framework/Crystal/src/SCDPanelErrors.cpp b/Framework/Crystal/src/SCDPanelErrors.cpp
index 6dbd04977f117af09ae71df9bd3d88461943c0e9..5a303969a0d35da3aa12f728336e059501be1c7f 100644
--- a/Framework/Crystal/src/SCDPanelErrors.cpp
+++ b/Framework/Crystal/src/SCDPanelErrors.cpp
@@ -213,8 +213,9 @@ void SCDPanelErrors::eval(double xshift, double yshift, double zshift,
                               hkl, peak.getGoniometerMatrix());
       Units::Wavelength wl;
 
-      wl.initialize(peak2.getL1(), peak2.getL2(), peak2.getScattering(), 0,
-                    peak2.getInitialEnergy(), 0.0);
+      wl.initialize(peak2.getL1(), 0,
+                    {{UnitParams::l2, peak2.getL2()},
+                     {UnitParams::twoTheta, peak2.getScattering()}});
       peak2.setWavelength(wl.singleFromTOF(peak.getTOF() + tShift));
       V3D Q3 = peak2.getQSampleFrame();
       out[i * 3] = Q3[0] - Q2[0];
diff --git a/Framework/Crystal/src/SaveHKL.cpp b/Framework/Crystal/src/SaveHKL.cpp
index 179739523c40d13efa27bfccff96bd1556a82dc1..0e7fd62c1d82a556919013a8fa47211debc10a47 100644
--- a/Framework/Crystal/src/SaveHKL.cpp
+++ b/Framework/Crystal/src/SaveHKL.cpp
@@ -435,7 +435,8 @@ void SaveHKL::exec() {
           double l2 = p.getL2();
           Mantid::Kernel::Unit_sptr unit =
               UnitFactory::Instance().create("Wavelength");
-          unit->toTOF(xdata, ydata, l1, l2, theta2, 0, 0.0, 0.0);
+          unit->toTOF(xdata, ydata, l1, 0,
+                      {{UnitParams::l2, l2}, {UnitParams::twoTheta, theta2}});
           double one = xdata[0];
           double spect1 = spectrumCalc(one, iSpec, time, spectra, bank);
           relSigSpect = std::sqrt((1.0 / spect) + (1.0 / spect1));
diff --git a/Framework/Crystal/src/SortPeaksWorkspace.cpp b/Framework/Crystal/src/SortPeaksWorkspace.cpp
index 830f9c5ecdae860c6bdf2f834240a65babdeb7ba..595975f6dba0d45ffbcacc697705794e7266a534 100644
--- a/Framework/Crystal/src/SortPeaksWorkspace.cpp
+++ b/Framework/Crystal/src/SortPeaksWorkspace.cpp
@@ -37,10 +37,10 @@ const std::string SortPeaksWorkspace::category() const {
 /** Initialize the algorithm's properties.
  */
 void SortPeaksWorkspace::init() {
-  declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                       "InputWorkspace", "", Direction::Input),
                   "An input workspace.");
-  declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                       "OutputWorkspace", "", Direction::Output),
                   "An output workspace.");
 
@@ -58,8 +58,8 @@ void SortPeaksWorkspace::init() {
 void SortPeaksWorkspace::exec() {
   const std::string columnToSortBy = getProperty("ColumnNameToSortBy");
   const bool sortAscending = getProperty("SortAscending");
-  PeaksWorkspace_sptr inputWS = getProperty("InputWorkspace");
-  PeaksWorkspace_sptr outputWS = getProperty("OutputWorkspace");
+  IPeaksWorkspace_sptr inputWS = getProperty("InputWorkspace");
+  IPeaksWorkspace_sptr outputWS = getProperty("OutputWorkspace");
 
   // Try to get the column. This will throw if the column does not exist.
   inputWS->getColumn(columnToSortBy);
@@ -68,7 +68,6 @@ void SortPeaksWorkspace::exec() {
     outputWS = inputWS->clone();
   }
 
-  // Perform the sorting.
   std::vector<PeaksWorkspace::ColumnAndDirection> sortCriteria;
   sortCriteria.emplace_back(columnToSortBy, sortAscending);
   outputWS->sort(sortCriteria);
diff --git a/Framework/Crystal/test/AddPeakHKLTest.h b/Framework/Crystal/test/AddPeakHKLTest.h
index 1caad2ef787b95faee520aa6592421d87424176c..fa719b2423965629607f25649f97cf0ff7f76633 100644
--- a/Framework/Crystal/test/AddPeakHKLTest.h
+++ b/Framework/Crystal/test/AddPeakHKLTest.h
@@ -96,8 +96,7 @@ public:
     IPeaksWorkspace_sptr ws_out = alg.getProperty("Workspace");
 
     // Get the peak just added.
-    const IPeak &peak = ws_out->getPeak(0);
-
+    auto peak = dynamic_cast<const Peak &>(ws_out->getPeak(0));
     /*
      Now we check we have made a self - consistent peak
      */
@@ -113,7 +112,7 @@ public:
     TSM_ASSERT_EQUALS("This detector id does not match what we expect from the "
                       "instrument definition",
                       1, detector->getID());
-    TSM_ASSERT_EQUALS("Thie detector position is wrong", detectorPos,
+    TSM_ASSERT_EQUALS("This detector position is wrong", detectorPos,
                       detector->getPos());
     TSM_ASSERT_EQUALS("Goniometer has not been set properly", goniometer.getR(),
                       peak.getGoniometerMatrix());
diff --git a/Framework/Crystal/test/FindSXPeaksHelperTest.h b/Framework/Crystal/test/FindSXPeaksHelperTest.h
index 95070d6b9284ae8b18840842f66abdaca4590972..eb1144aca50fd44d82a3b31f89efd28bb06b97a8 100644
--- a/Framework/Crystal/test/FindSXPeaksHelperTest.h
+++ b/Framework/Crystal/test/FindSXPeaksHelperTest.h
@@ -140,13 +140,13 @@ public:
     auto backgroundStrategy = std::make_unique<AbsoluteBackgroundStrategy>(3.);
     auto workspace =
         WorkspaceCreationHelper::create2DWorkspaceWithFullInstrument(
-            1 /*nhist*/, 15 /*nbins*/);
-    auto &mutableY = workspace->mutableY(0);
+            2 /*nhist*/, 15 /*nbins*/);
+    const int workspaceIndex = 1;
+    auto &mutableY = workspace->mutableY(workspaceIndex);
     doAddDoublePeakToData(mutableY);
 
-    const int workspaceIndex = 0;
-    const auto &x = workspace->x(0);
-    const auto &y = workspace->y(0);
+    const auto &x = workspace->x(workspaceIndex);
+    const auto &y = workspace->y(workspaceIndex);
     const auto &spectrumInfo = workspace->spectrumInfo();
 
     // WHEN
@@ -398,13 +398,13 @@ private:
     // GIVEN
     auto workspace =
         WorkspaceCreationHelper::create2DWorkspaceWithFullInstrument(
-            1 /*nhist*/, 15 /*nbins*/);
-    auto &mutableY = workspace->mutableY(0);
+            2 /*nhist*/, 15 /*nbins*/);
+    const int workspaceIndex = 1;
+    auto &mutableY = workspace->mutableY(workspaceIndex);
     doAddDoublePeakToData(mutableY);
 
-    const int workspaceIndex = 0;
-    const auto &x = workspace->x(0);
-    const auto &y = workspace->y(0);
+    const auto &x = workspace->x(workspaceIndex);
+    const auto &y = workspace->y(workspaceIndex);
     const auto &spectrumInfo = workspace->spectrumInfo();
 
     // WHEN
diff --git a/Framework/Crystal/test/IntegratePeakTimeSlicesTest.h b/Framework/Crystal/test/IntegratePeakTimeSlicesTest.h
index 239cd875bb4df32ed30ecc2a87eceda48adc8234..48f83a775038f1a280541460964fe1d27debb2e0 100644
--- a/Framework/Crystal/test/IntegratePeakTimeSlicesTest.h
+++ b/Framework/Crystal/test/IntegratePeakTimeSlicesTest.h
@@ -115,7 +115,9 @@ public:
     std::vector<double> x;
     x.emplace_back(PeakTime);
 
-    wl.fromTOF(x, x, L1, L2, ScatAng, 0, 0, 0);
+    wl.fromTOF(x, x, L1, 0,
+               {{Kernel::UnitParams::l2, L2},
+                {Kernel::UnitParams::twoTheta, ScatAng}});
     double wavelength = x[0];
 
     Peak peak(instP, pixelp->getID(), wavelength);
@@ -250,7 +252,9 @@ private:
     x.emplace_back(time);
     const auto ScatAng = detectorInfo.twoTheta(detInfoIndex) / 180 * M_PI;
 
-    Q.fromTOF(x, x, L1, L2, ScatAng, 0, 0, 0.0);
+    Q.fromTOF(x, x, L1, 0,
+              {{Kernel::UnitParams::l2, L2},
+               {Kernel::UnitParams::twoTheta, ScatAng}});
 
     return x[0] / 2 / M_PI;
   }
diff --git a/Framework/Crystal/test/SCDCalibratePanels2Test.h b/Framework/Crystal/test/SCDCalibratePanels2Test.h
index 3b8dbd3a2463ed8c318295dfef3a4ee62e8ae334..f5516fcfe30d21dc2c1f45f88156f5629cb5be2a 100644
--- a/Framework/Crystal/test/SCDCalibratePanels2Test.h
+++ b/Framework/Crystal/test/SCDCalibratePanels2Test.h
@@ -449,9 +449,10 @@ private:
     for (int i = 0; i < pws->getNumberPeaks(); ++i) {
       tof = pws->getPeak(i).getTOF();
       pws->getPeak(i).setInstrument(pws->getInstrument());
-      wl.initialize(pws->getPeak(i).getL1(), pws->getPeak(i).getL2(),
-                    pws->getPeak(i).getScattering(), 0,
-                    pws->getPeak(i).getInitialEnergy(), 0.0);
+      wl.initialize(pws->getPeak(i).getL1(), 0,
+                    {{UnitParams::l2, pws->getPeak(i).getL2()},
+                     {UnitParams::twoTheta, pws->getPeak(i).getScattering()},
+                     {UnitParams::efixed, pws->getPeak(i).getInitialEnergy()}});
       pws->getPeak(i).setDetectorID(pws->getPeak(i).getDetectorID());
       pws->getPeak(i).setWavelength(wl.singleFromTOF(tof));
     }
diff --git a/Framework/Crystal/test/SortPeaksWorkspaceTest.h b/Framework/Crystal/test/SortPeaksWorkspaceTest.h
index d61cdca4685990eb5d446a1b36d2e346e2db4ad2..d889eff22256dad0058cd005f259dbccb19d064c 100644
--- a/Framework/Crystal/test/SortPeaksWorkspaceTest.h
+++ b/Framework/Crystal/test/SortPeaksWorkspaceTest.h
@@ -8,6 +8,7 @@
 
 #include "MantidAPI/TableRow.h"
 #include "MantidCrystal/SortPeaksWorkspace.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidTestHelpers/WorkspaceCreationHelper.h"
 #include <algorithm>
@@ -44,8 +45,9 @@ private:
     TS_ASSERT_THROWS_NOTHING(alg.execute());
     TS_ASSERT(alg.isExecuted());
 
-    PeaksWorkspace_sptr outWS =
-        AnalysisDataService::Instance().retrieveWS<PeaksWorkspace>(outWSName);
+    IPeaksWorkspace_sptr tmp =
+        AnalysisDataService::Instance().retrieveWS<IPeaksWorkspace>(outWSName);
+    PeaksWorkspace_sptr outWS = std::dynamic_pointer_cast<PeaksWorkspace>(tmp);
 
     // Extract the sorted column values out into a containtainer.
     const size_t columnIndex = outWS->getColumnIndex(columnName);
@@ -187,7 +189,38 @@ public:
   }
 
   void test_modify_workspace_in_place() {
-    PeaksWorkspace_sptr inWS = WorkspaceCreationHelper::createPeaksWorkspace(2);
+    PeaksWorkspace_sptr tmp = WorkspaceCreationHelper::createPeaksWorkspace(2);
+    IPeaksWorkspace_sptr inWS = std::dynamic_pointer_cast<IPeaksWorkspace>(tmp);
+
+    SortPeaksWorkspace alg;
+    alg.setChild(true);
+    alg.setRethrows(true);
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspace", inWS));
+    alg.setPropertyValue("OutputWorkspace", "OutName");
+    TS_ASSERT_THROWS_NOTHING(alg.setProperty("OutputWorkspace", inWS));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("ColumnNameToSortBy", "h"));
+    TS_ASSERT_THROWS_NOTHING(alg.execute());
+    TS_ASSERT(alg.isExecuted());
+
+    IPeaksWorkspace_sptr outWS = alg.getProperty("OutputWorkspace");
+
+    TSM_ASSERT_EQUALS("Sorting should have happened in place. Output and input "
+                      "workspaces should be the same.",
+                      outWS, inWS);
+  }
+
+  void test_leanPeakWorkspace_sort_inplace() {
+    // generate a lean elastic peak workspace with two peaks
+    auto lpws = std::make_shared<LeanElasticPeaksWorkspace>();
+    // add peaks
+    LeanElasticPeak pk1(Mantid::Kernel::V3D(0.0, 0.0, 6.28319), 2.0);
+    LeanElasticPeak pk2(Mantid::Kernel::V3D(6.28319, 0.0, 6.28319), 1.0);
+    lpws->addPeak(pk1);
+    lpws->addPeak(pk2);
+    IPeaksWorkspace_sptr inWS =
+        std::dynamic_pointer_cast<IPeaksWorkspace>(lpws);
 
     SortPeaksWorkspace alg;
     alg.setChild(true);
@@ -201,7 +234,7 @@ public:
     TS_ASSERT_THROWS_NOTHING(alg.execute());
     TS_ASSERT(alg.isExecuted());
 
-    PeaksWorkspace_sptr outWS = alg.getProperty("OutputWorkspace");
+    IPeaksWorkspace_sptr outWS = alg.getProperty("OutputWorkspace");
 
     TSM_ASSERT_EQUALS("Sorting should have happened in place. Output and input "
                       "workspaces should be the same.",
diff --git a/Framework/CurveFitting/src/Algorithms/PawleyFit.cpp b/Framework/CurveFitting/src/Algorithms/PawleyFit.cpp
index 9ba22ff2e156ede21ef0a299cc9fbce372152985..e153fc4da3a3a9ff19629550ad233e457e484c10 100644
--- a/Framework/CurveFitting/src/Algorithms/PawleyFit.cpp
+++ b/Framework/CurveFitting/src/Algorithms/PawleyFit.cpp
@@ -51,8 +51,7 @@ double PawleyFit::getTransformedCenter(double d, const Unit_sptr &unit) const {
     return d;
   }
 
-  return UnitConversion::run(*m_dUnit, *unit, d, 0, 0, 0, DeltaEMode::Elastic,
-                             0);
+  return UnitConversion::run(*m_dUnit, *unit, d, 0, DeltaEMode::Elastic);
 }
 
 /**
diff --git a/Framework/CurveFitting/src/Functions/PawleyFunction.cpp b/Framework/CurveFitting/src/Functions/PawleyFunction.cpp
index 3d47ea64dab25bcba73ba9571dbe5b8a6f617687..a842433a1d9636fa233f8290b956061977ffac5b 100644
--- a/Framework/CurveFitting/src/Functions/PawleyFunction.cpp
+++ b/Framework/CurveFitting/src/Functions/PawleyFunction.cpp
@@ -398,8 +398,7 @@ void PawleyFunction::setUnitCell(const std::string &unitCellString) {
 /// Transform d value to workspace unit
 double PawleyFunction::getTransformedCenter(double d) const {
   if ((m_dUnit && m_wsUnit) && m_dUnit != m_wsUnit) {
-    return UnitConversion::run(*m_dUnit, *m_wsUnit, d, 0, 0, 0,
-                               DeltaEMode::Elastic, 0);
+    return UnitConversion::run(*m_dUnit, *m_wsUnit, d, 0, DeltaEMode::Elastic);
   }
 
   return d;
diff --git a/Framework/DataHandling/CMakeLists.txt b/Framework/DataHandling/CMakeLists.txt
index b0ce8cf67d0929c1b87b24f5d5cc1db4c583badb..8987e354d4688cd0b5f5c8e319618539ac094a4a 100644
--- a/Framework/DataHandling/CMakeLists.txt
+++ b/Framework/DataHandling/CMakeLists.txt
@@ -1,5 +1,6 @@
 set(SRC_FILES
     src/AppendGeometryToSNSNexus.cpp
+    src/ApplyDiffCal.cpp
     src/BankPulseTimes.cpp
     src/CheckMantidVersion.cpp
     src/CompressEvents.cpp
@@ -212,6 +213,7 @@ set(SRC_FILES
 
 set(INC_FILES
     inc/MantidDataHandling/AppendGeometryToSNSNexus.h
+    inc/MantidDataHandling/ApplyDiffCal.h
     inc/MantidDataHandling/BankPulseTimes.h
     inc/MantidDataHandling/CheckMantidVersion.h
     inc/MantidDataHandling/CompressEvents.h
@@ -427,6 +429,7 @@ set(INC_FILES
 
 set(TEST_FILES
     AppendGeometryToSNSNexusTest.h
+    ApplyDiffCalTest.h
     CheckMantidVersionTest.h
     CompressEventsTest.h
     CreateChunkingFromInstrumentTest.h
diff --git a/Framework/DataHandling/inc/MantidDataHandling/ApplyDiffCal.h b/Framework/DataHandling/inc/MantidDataHandling/ApplyDiffCal.h
new file mode 100644
index 0000000000000000000000000000000000000000..c5953cd4c0b6c6595da384bd8df57d3b48ed1cc1
--- /dev/null
+++ b/Framework/DataHandling/inc/MantidDataHandling/ApplyDiffCal.h
@@ -0,0 +1,44 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+//   NScD Oak Ridge National Laboratory, European Spallation Source,
+//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+// SPDX - License - Identifier: GPL - 3.0 +
+#pragma once
+
+#include "MantidAPI/Algorithm.h"
+#include "MantidAPI/ITableWorkspace_fwd.h"
+#include "MantidAPI/Workspace_fwd.h"
+#include "MantidGeometry/Instrument.h"
+#include "MantidKernel/System.h"
+
+namespace Mantid {
+namespace DataHandling {
+
+/** ApplyDiffCal :
+ */
+class DLLExport ApplyDiffCal : public API::Algorithm {
+public:
+  const std::string name() const override;
+  int version() const override;
+  const std::vector<std::string> seeAlso() const override {
+    return {"AlignDetectors", "ConvertDiffCal", "ConvertUnits", "LoadDiffCal"};
+  }
+  const std::string category() const override;
+  const std::string summary() const override;
+  /// Cross-check properties with each other @see IAlgorithm::validateInputs
+  std::map<std::string, std::string> validateInputs() override;
+
+private:
+  void init() override;
+  void exec() override;
+
+  void loadCalFile(const Mantid::API::Workspace_sptr &inputWS,
+                   const std::string &filename);
+  void getCalibrationWS(const Mantid::API::Workspace_sptr &inputWS);
+
+  Mantid::API::ITableWorkspace_sptr m_calibrationWS;
+};
+
+} // namespace DataHandling
+} // namespace Mantid
diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadGSS.h b/Framework/DataHandling/inc/MantidDataHandling/LoadGSS.h
index cefa59b10e4c8305ff0a36a3a76e287513ddc6d0..5d4d94eb89058abe94e476e40ec37f6b797d37a0 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/LoadGSS.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/LoadGSS.h
@@ -68,7 +68,8 @@ private:
                                 const double &primaryflightpath,
                                 const std::vector<int> &detectorids,
                                 const std::vector<double> &totalflightpaths,
-                                const std::vector<double> &twothetas);
+                                const std::vector<double> &twothetas,
+                                const std::vector<double> &difcs);
 };
 } // namespace DataHandling
 } // namespace Mantid
diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadLog.h b/Framework/DataHandling/inc/MantidDataHandling/LoadLog.h
index 7de2cb3ab667218a4fc352a3868a07bf419b34fc..87412001212e1885a5c8d037e844d371df68803a 100644
--- a/Framework/DataHandling/inc/MantidDataHandling/LoadLog.h
+++ b/Framework/DataHandling/inc/MantidDataHandling/LoadLog.h
@@ -100,10 +100,14 @@ private:
   /// Checks if the file is an ASCII file
   bool isAscii(const std::string &filename);
 
-  /// Check if first 19 characters of a string is data-time string according to
+  /// Check if first 19 characters of a string is date-time string according to
   /// yyyy-mm-ddThh:mm:ss
   bool isDateTimeString(const std::string &str) const;
 
+  /// Check whether the first 24 characters of a string are consistent with
+  /// the date-time format used in older unsupported log files.
+  bool isOldDateTimeFormat(std::ifstream &logFileStream) const;
+
   /// Checks if a log file name was provided (e.g. through setPropertyValue). If
   /// not it creates one based on provided path.
   std::string extractLogName(const std::vector<std::string> &logName);
diff --git a/Framework/DataHandling/src/ApplyDiffCal.cpp b/Framework/DataHandling/src/ApplyDiffCal.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a1986daa0aabaec588f5c46c18c6774c0acc6db1
--- /dev/null
+++ b/Framework/DataHandling/src/ApplyDiffCal.cpp
@@ -0,0 +1,227 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+//   NScD Oak Ridge National Laboratory, European Spallation Source,
+//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+// SPDX - License - Identifier: GPL - 3.0 +
+#include "MantidDataHandling/ApplyDiffCal.h"
+
+#include "MantidAPI/FileProperty.h"
+#include "MantidAPI/ITableWorkspace.h"
+#include "MantidAPI/Progress.h"
+#include "MantidAPI/Run.h"
+#include "MantidAPI/TableRow.h"
+#include "MantidAPI/Workspace.h"
+#include "MantidDataObjects/MaskWorkspace.h"
+#include "MantidDataObjects/OffsetsWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidKernel/EnabledWhenProperty.h"
+
+using namespace Mantid::API;
+using namespace Mantid::DataObjects;
+
+namespace Mantid {
+namespace DataHandling {
+
+using Mantid::Kernel::Direction;
+using Mantid::Kernel::PropertyWithValue;
+
+namespace {
+namespace PropertyNames {
+const std::string CAL_FILE("Filename");
+const std::string GROUP_FILE("GroupFilename");
+const std::string MAKE_CAL("MakeCalWorkspace");
+const std::string MAKE_GRP("MakeGroupingWorkspace");
+const std::string MAKE_MSK("MakeMaskWorkspace");
+} // namespace PropertyNames
+} // namespace
+
+// Register the algorithm into the AlgorithmFactory
+DECLARE_ALGORITHM(ApplyDiffCal)
+
+/// Algorithms name for identification. @see Algorithm::name
+const std::string ApplyDiffCal::name() const { return "ApplyDiffCal"; }
+
+/// Algorithm's version for identification. @see Algorithm::version
+int ApplyDiffCal::version() const { return 1; }
+
+/// Algorithm's category for identification. @see Algorithm::category
+const std::string ApplyDiffCal::category() const {
+  return "DataHandling\\Instrument;Diffraction\\DataHandling";
+}
+
+/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
+const std::string ApplyDiffCal::summary() const {
+  return "Applies a calibration to a workspace for powder diffraction";
+}
+
+/** Initialize the algorithm's properties.
+ */
+void ApplyDiffCal::init() {
+  declareProperty(std::make_unique<WorkspaceProperty<API::Workspace>>(
+                      "InstrumentWorkspace", "", Direction::InOut),
+                  "Set the workspace whose instrument should be updated");
+  const std::vector<std::string> exts{".h5", ".hd5", ".hdf", ".cal"};
+  declareProperty(
+      std::make_unique<FileProperty>("CalibrationFile", "",
+                                     FileProperty::OptionalLoad, exts),
+      "Optional: The .cal file containing the position correction factors. "
+      "Either this, CalibrationWorkspace or OffsetsWorkspace needs to be "
+      "specified.");
+  declareProperty(
+      std::make_unique<WorkspaceProperty<ITableWorkspace>>(
+          "CalibrationWorkspace", "", Direction::Input, PropertyMode::Optional),
+      "Optional: Set the Diffraction Calibration workspace");
+  declareProperty(
+      std::make_unique<WorkspaceProperty<OffsetsWorkspace>>(
+          "OffsetsWorkspace", "", Direction::Input, PropertyMode::Optional),
+      "Optional: A OffsetsWorkspace containing the calibration offsets. Either "
+      "this, CalibrationWorkspace or CalibrationFile needs to be specified.");
+  declareProperty("ClearCalibration", false,
+                  "Remove any existing calibration from the workspace");
+  setPropertySettings(
+      "CalibrationFile",
+      std::make_unique<Kernel::EnabledWhenProperty>(
+          "ClearCalibration", Kernel::ePropertyCriterion::IS_EQUAL_TO, "0"));
+  setPropertySettings(
+      "CalibrationWorkspace",
+      std::make_unique<Kernel::EnabledWhenProperty>(
+          "ClearCalibration", Kernel::ePropertyCriterion::IS_EQUAL_TO, "0"));
+  setPropertySettings(
+      "OffsetsWorkspace",
+      std::make_unique<Kernel::EnabledWhenProperty>(
+          "ClearCalibration", Kernel::ePropertyCriterion::IS_EQUAL_TO, "0"));
+}
+
+std::map<std::string, std::string> ApplyDiffCal::validateInputs() {
+  std::map<std::string, std::string> result;
+
+  // Check workspace type has ExperimentInfo fields
+  using API::ExperimentInfo_sptr;
+  using API::Workspace_sptr;
+  Workspace_sptr inputWS = getProperty("InstrumentWorkspace");
+  if (!std::dynamic_pointer_cast<ExperimentInfo>(inputWS)) {
+    result["InstrumentWorkspace"] = "InputWorkspace type invalid. "
+                                    "Expected MatrixWorkspace, "
+                                    "PeaksWorkspace.";
+  }
+
+  int numWays = 0;
+
+  const std::string calFileName = getProperty("CalibrationFile");
+  if (!calFileName.empty())
+    numWays += 1;
+
+  ITableWorkspace_const_sptr calibrationWS =
+      getProperty("CalibrationWorkspace");
+  if (bool(calibrationWS))
+    numWays += 1;
+
+  OffsetsWorkspace_const_sptr offsetsWS = getProperty("OffsetsWorkspace");
+  if (bool(offsetsWS))
+    numWays += 1;
+
+  bool clearCalibration = getProperty("ClearCalibration");
+  std::string message;
+  if ((clearCalibration) && (numWays > 0)) {
+    message =
+        "You cannot supply a calibration input when clearing the calibration.";
+  }
+  if (!clearCalibration) {
+    if (numWays == 0) {
+      message = "You must specify only one of CalibrationFile, "
+                "CalibrationWorkspace, OffsetsWorkspace.";
+    }
+    if (numWays > 1) {
+      message = "You must specify one of CalibrationFile, "
+                "CalibrationWorkspace, OffsetsWorkspace.";
+    }
+  }
+
+  if (!message.empty()) {
+    result["CalibrationFile"] = message;
+    result["CalibrationWorkspace"] = message;
+  }
+
+  return result;
+}
+
+void ApplyDiffCal::loadCalFile(const Workspace_sptr &inputWS,
+                               const std::string &filename) {
+  IAlgorithm_sptr alg = createChildAlgorithm("LoadDiffCal");
+  alg->setProperty("InputWorkspace", inputWS);
+  alg->setPropertyValue("Filename", filename);
+  alg->setProperty<bool>("MakeCalWorkspace", true);
+  alg->setProperty<bool>("MakeGroupingWorkspace", false);
+  alg->setProperty<bool>("MakeMaskWorkspace", false);
+  alg->setPropertyValue("WorkspaceName", "temp");
+  alg->executeAsChildAlg();
+
+  m_calibrationWS = alg->getProperty("OutputCalWorkspace");
+}
+
+void ApplyDiffCal::getCalibrationWS(const Workspace_sptr &inputWS) {
+  m_calibrationWS = getProperty("CalibrationWorkspace");
+  if (m_calibrationWS)
+    return; // nothing more to do
+
+  OffsetsWorkspace_sptr offsetsWS = getProperty("OffsetsWorkspace");
+  if (offsetsWS) {
+    auto alg = createChildAlgorithm("ConvertDiffCal");
+    alg->setProperty("OffsetsWorkspace", offsetsWS);
+    alg->executeAsChildAlg();
+    m_calibrationWS = alg->getProperty("OutputWorkspace");
+    m_calibrationWS->setTitle(offsetsWS->getTitle());
+    return;
+  }
+
+  const std::string calFileName = getPropertyValue("CalibrationFile");
+  if (!calFileName.empty()) {
+    progress(0.0, "Reading calibration file");
+    loadCalFile(std::move(inputWS), calFileName);
+    return;
+  }
+
+  throw std::runtime_error("Failed to determine calibration information");
+}
+
+//----------------------------------------------------------------------------------------------
+/** Execute the algorithm.
+ */
+void ApplyDiffCal::exec() {
+
+  Workspace_sptr InstrumentWorkspace = getProperty("InstrumentWorkspace");
+  // validateInputs guarantees this will be an ExperimentInfo object
+  auto experimentInfo =
+      std::dynamic_pointer_cast<API::ExperimentInfo>(InstrumentWorkspace);
+  auto instrument = experimentInfo->getInstrument();
+  auto &paramMap = experimentInfo->instrumentParameters();
+  bool clearCalibration = getProperty("ClearCalibration");
+  if (clearCalibration) {
+    paramMap.clearParametersByName("DIFC");
+    paramMap.clearParametersByName("DIFA");
+    paramMap.clearParametersByName("TZERO");
+  } else {
+    this->getCalibrationWS(InstrumentWorkspace);
+
+    Column_const_sptr detIdColumn = m_calibrationWS->getColumn("detid");
+    Column_const_sptr difcColumn = m_calibrationWS->getColumn("difc");
+    Column_const_sptr difaColumn = m_calibrationWS->getColumn("difa");
+    Column_const_sptr tzeroColumn = m_calibrationWS->getColumn("tzero");
+
+    for (size_t i = 0; i < m_calibrationWS->rowCount(); ++i) {
+      auto detid = static_cast<detid_t>((*detIdColumn)[i]);
+      double difc = (*difcColumn)[i];
+      double difa = (*difaColumn)[i];
+      double tzero = (*tzeroColumn)[i];
+
+      auto det = instrument->getDetector(detid);
+      paramMap.addDouble(det->getComponentID(), "DIFC", difc);
+      paramMap.addDouble(det->getComponentID(), "DIFA", difa);
+      paramMap.addDouble(det->getComponentID(), "TZERO", tzero);
+    }
+  }
+}
+
+} // namespace DataHandling
+} // namespace Mantid
diff --git a/Framework/DataHandling/src/LoadDiffCal.cpp b/Framework/DataHandling/src/LoadDiffCal.cpp
index b28e4399d40daf2556a1b481fc85c4b27187cd6f..d10d84a0bc90876f3edda6fcada3a14afdc43069 100644
--- a/Framework/DataHandling/src/LoadDiffCal.cpp
+++ b/Framework/DataHandling/src/LoadDiffCal.cpp
@@ -17,9 +17,9 @@
 #include "MantidDataObjects/MaskWorkspace.h"
 #include "MantidDataObjects/TableWorkspace.h"
 #include "MantidDataObjects/Workspace2D.h"
-#include "MantidKernel/Diffraction.h"
 #include "MantidKernel/Exception.h"
 #include "MantidKernel/OptionalBool.h"
+#include "MantidKernel/Unit.h"
 
 #include <H5Cpp.h>
 #include <cmath>
@@ -315,8 +315,9 @@ void LoadDiffCal::makeCalWorkspace(const std::vector<int32_t> &detids,
       newrow << offsets[i];
 
     // calculate tof range for information
+    Kernel::Units::dSpacing dspacingUnit;
     const double tofMinRow =
-        Kernel::Diffraction::calcTofMin(difc[i], difa[i], tzero[i], tofMin);
+        dspacingUnit.calcTofMin(difc[i], difa[i], tzero[i], tofMin);
     std::stringstream msg;
     if (tofMinRow != tofMin) {
       msg << "TofMin shifted from " << tofMin << " to " << tofMinRow << " ";
@@ -324,7 +325,7 @@ void LoadDiffCal::makeCalWorkspace(const std::vector<int32_t> &detids,
     newrow << tofMinRow;
     if (useTofMax) {
       const double tofMaxRow =
-          Kernel::Diffraction::calcTofMax(difc[i], difa[i], tzero[i], tofMax);
+          dspacingUnit.calcTofMax(difc[i], difa[i], tzero[i], tofMax);
       newrow << tofMaxRow;
 
       if (tofMaxRow != tofMax) {
diff --git a/Framework/DataHandling/src/LoadGSS.cpp b/Framework/DataHandling/src/LoadGSS.cpp
index 3c3ab417877438854a4f41dd2c4cdda117c8b04e..b4c8a2b97e952636a54bb16abe1220a0d4e66dc9 100644
--- a/Framework/DataHandling/src/LoadGSS.cpp
+++ b/Framework/DataHandling/src/LoadGSS.cpp
@@ -458,7 +458,7 @@ API::MatrixWorkspace_sptr LoadGSS::loadGSASFile(const std::string &filename,
 
   // build instrument geometry
   createInstrumentGeometry(outputWorkspace, instrumentname, primaryflightpath,
-                           detectorIDs, totalflightpaths, twothetas);
+                           detectorIDs, totalflightpaths, twothetas, difcs);
 
   return outputWorkspace;
 }
@@ -491,7 +491,7 @@ void LoadGSS::createInstrumentGeometry(
     const MatrixWorkspace_sptr &workspace, const std::string &instrumentname,
     const double &primaryflightpath, const std::vector<int> &detectorids,
     const std::vector<double> &totalflightpaths,
-    const std::vector<double> &twothetas) {
+    const std::vector<double> &twothetas, const std::vector<double> &difcs) {
   // Check Input
   if (detectorids.size() != totalflightpaths.size() ||
       totalflightpaths.size() != twothetas.size()) {
@@ -560,7 +560,13 @@ void LoadGSS::createInstrumentGeometry(
 
   } // ENDFOR (i: spectrum)
   workspace->setInstrument(instrument);
-}
 
+  auto &paramMap = workspace->instrumentParameters();
+  for (size_t i = 0; i < workspace->getNumberHistograms(); i++) {
+    auto detector = workspace->getDetector(i);
+    paramMap.addDouble(detector->getComponentID(), "DIFC", difcs[i]);
+  }
+}
 } // namespace DataHandling
+
 } // namespace Mantid
diff --git a/Framework/DataHandling/src/LoadLog.cpp b/Framework/DataHandling/src/LoadLog.cpp
index c771655cb7074ad72a821c71a55a2f2ba7b81172..df67c5718622f0026622264ee33f219cdc371237 100644
--- a/Framework/DataHandling/src/LoadLog.cpp
+++ b/Framework/DataHandling/src/LoadLog.cpp
@@ -27,6 +27,7 @@
 #include <Poco/Path.h>
 #include <boost/algorithm/string.hpp>
 #include <fstream> // used to get ifstream
+#include <regex>
 #include <sstream>
 #include <utility>
 
@@ -143,6 +144,13 @@ void LoadLog::exec() {
         "More than one log name provided. Invalid ISIS log file.");
   }
 
+  // If it's an old log file (pre-2007), then it is not currently supported.
+  if (isOldDateTimeFormat(logFileStream)) {
+    throw std::invalid_argument(
+        "File " + m_filename +
+        " cannot be read because it has an old unsupported format.");
+  }
+
   int colNum = static_cast<int>(getProperty("NumberOfColumns"));
 
   if (colNum == Mantid::EMPTY_INT()) {
@@ -181,7 +189,7 @@ void LoadLog::loadTwoColumnLogFile(std::ifstream &logFileStream,
   std::string aLine;
   if (Mantid::Kernel::Strings::extractToEOL(logFileStream, aLine)) {
     if (!isDateTimeString(aLine)) {
-      throw std::invalid_argument("File" + m_filename +
+      throw std::invalid_argument("File " + m_filename +
                                   " is not a standard ISIS log file. Expected "
                                   "to be a two column file.");
     }
@@ -239,7 +247,7 @@ void LoadLog::loadThreeColumnLogFile(std::ifstream &logFileStream,
 
   while (Mantid::Kernel::Strings::extractToEOL(logFileStream, str)) {
     if (!isDateTimeString(str) && !str.empty()) {
-      throw std::invalid_argument("File" + logFileName +
+      throw std::invalid_argument("File " + logFileName +
                                   " is not a standard ISIS log file. Expected "
                                   "to be a file starting with DateTime String "
                                   "format.");
@@ -267,7 +275,7 @@ void LoadLog::loadThreeColumnLogFile(std::ifstream &logFileStream,
 
     if (LoadLog::string != l_kind) {
       throw std::invalid_argument(
-          "ISIS log file contains unrecognised second column entries:" +
+          "ISIS log file contains unrecognised second column entries: " +
           logFileName);
     }
 
@@ -491,6 +499,27 @@ bool LoadLog::isDateTimeString(const std::string &str) const {
   return Types::Core::DateAndTimeHelpers::stringIsISO8601(str.substr(0, 19));
 }
 
+/**
+ * Check whether the string is consistent with the old log file
+ * date-time format, for example:
+ * Fri 31-JAN-2003 11:28:15
+ * Wed  9-FEB-2005 09:47:01
+ * @param logFileStream :: The file to test
+ * @return true if the format matches the old log file format.
+ */
+bool LoadLog::isOldDateTimeFormat(std::ifstream &logFileStream) const {
+  // extract first line of file
+  std::string firstLine;
+  Mantid::Kernel::Strings::extractToEOL(logFileStream, firstLine);
+  // reset file back to the beginning
+  logFileStream.seekg(0);
+
+  std::regex oldDateFormat(
+      R"([A-Z][a-z]{2} [ 1-3]\d-[A-Z]{3}-\d{4} \d{2}:\d{2}:\d{2})");
+
+  return std::regex_match(firstLine.substr(0, 24), oldDateFormat);
+}
+
 /**
  * Read a line of a SNS-style text file.
  * @param str :: The string to test
@@ -532,7 +561,7 @@ int LoadLog::countNumberColumns(std::ifstream &logFileStream,
   Mantid::Kernel::Strings::extractToEOL(logFileStream, str);
 
   if (!isDateTimeString(str)) {
-    throw std::invalid_argument("File" + logFileName +
+    throw std::invalid_argument("File " + logFileName +
                                 " is not a standard ISIS log file. Expected to "
                                 "be a file starting with DateTime String "
                                 "format.");
@@ -548,7 +577,7 @@ int LoadLog::countNumberColumns(std::ifstream &logFileStream,
 
   if (LoadLog::string != l_kind && LoadLog::number != l_kind) {
     throw std::invalid_argument(
-        "ISIS log file contains unrecognised second column entries:" +
+        "ISIS log file contains unrecognised second column entries: " +
         logFileName);
   }
 
diff --git a/Framework/DataHandling/src/SaveGDA.cpp b/Framework/DataHandling/src/SaveGDA.cpp
index f7230ebd141d08c739f1f5ce47d2ef924931698a..85efe73401507cc1eb32019b11c955f78c18354e 100644
--- a/Framework/DataHandling/src/SaveGDA.cpp
+++ b/Framework/DataHandling/src/SaveGDA.cpp
@@ -162,7 +162,7 @@ void SaveGDA::exec() {
     const auto ws = inputWS->getItem(i);
     const auto matrixWS = std::dynamic_pointer_cast<MatrixWorkspace>(ws);
 
-    const auto &d = matrixWS->x(0);
+    auto x = matrixWS->dataX(0);
     const size_t bankIndex(groupingScheme[i] - 1);
     if (bankIndex >= calibParams.size()) {
       throw Kernel::Exception::IndexError(bankIndex, calibParams.size(),
@@ -173,14 +173,15 @@ void SaveGDA::exec() {
     // For historic reasons, TOF is scaled by 32 in MAUD
     const static double tofScale = 32;
     std::vector<double> tofScaled;
-    tofScaled.reserve(d.size());
-    std::transform(d.begin(), d.end(), std::back_inserter(tofScaled),
-                   [&bankCalibParams](const double dVal) {
-                     return (dVal * bankCalibParams.difc +
-                             dVal * dVal * bankCalibParams.difa +
-                             bankCalibParams.tzero) *
-                            tofScale;
-                   });
+    tofScaled.reserve(x.size());
+    Kernel::Units::dSpacing dSpacingUnit;
+    std::vector<double> yunused;
+    dSpacingUnit.toTOF(x, yunused, 0., Kernel::DeltaEMode::Elastic,
+                       {{Kernel::UnitParams::difa, bankCalibParams.difa},
+                        {Kernel::UnitParams::difc, bankCalibParams.difc},
+                        {Kernel::UnitParams::tzero, bankCalibParams.tzero}});
+    std::transform(x.begin(), x.end(), std::back_inserter(tofScaled),
+                   [](const double tofVal) { return tofVal * tofScale; });
     const auto averageDeltaTByT = computeAverageDeltaTByT(tofScaled);
 
     const auto &intensity = matrixWS->y(0);
diff --git a/Framework/DataHandling/src/SaveGSS.cpp b/Framework/DataHandling/src/SaveGSS.cpp
index 05372a28b137c90e88869100422d63a072b0bb12..829162614267c65bc228197fd30b1c3e98b2d633 100644
--- a/Framework/DataHandling/src/SaveGSS.cpp
+++ b/Framework/DataHandling/src/SaveGSS.cpp
@@ -361,11 +361,10 @@ void SaveGSS::generateBankHeader(std::stringstream &out,
     const auto l1 = spectrumInfo.l1();
     const auto l2 = spectrumInfo.l2(specIndex);
     const auto twoTheta = spectrumInfo.twoTheta(specIndex);
-    const auto difc = (2.0 * PhysicalConstants::NeutronMass *
-                       sin(twoTheta * 0.5) * (l1 + l2)) /
-                      (PhysicalConstants::h * 1.e4);
+    auto diffConstants = spectrumInfo.diffractometerConstants(specIndex);
     out << "# Total flight path " << (l1 + l2) << "m, tth "
-        << (twoTheta * 180. / M_PI) << "deg, DIFC " << difc << "\n";
+        << (twoTheta * 180. / M_PI) << "deg, DIFC "
+        << diffConstants[Kernel::UnitParams::difc] << "\n";
   }
   out << "# Data for spectrum :" << specIndex << "\n";
 }
diff --git a/Framework/DataHandling/test/ApplyDiffCalTest.h b/Framework/DataHandling/test/ApplyDiffCalTest.h
new file mode 100644
index 0000000000000000000000000000000000000000..d1a7f7dd19dff7dcbe2f9bc7d4e069973c9d601a
--- /dev/null
+++ b/Framework/DataHandling/test/ApplyDiffCalTest.h
@@ -0,0 +1,131 @@
+// Mantid Repository : https://github.com/mantidproject/mantid
+//
+// Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+//   NScD Oak Ridge National Laboratory, European Spallation Source,
+//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+// SPDX - License - Identifier: GPL - 3.0 +
+#pragma once
+
+#include <cxxtest/TestSuite.h>
+
+#include "MantidAPI/AnalysisDataService.h"
+#include "MantidAPI/TableRow.h"
+#include "MantidDataHandling/ApplyDiffCal.h"
+#include "MantidDataObjects/TableWorkspace.h"
+#include "MantidDataObjects/Workspace2D.h"
+#include "MantidTestHelpers/ComponentCreationHelper.h"
+
+using namespace Mantid::API;
+using namespace Mantid::DataHandling;
+using namespace Mantid::DataObjects;
+
+namespace {
+const size_t NUM_BANK = 5;
+} // namespace
+
+class ApplyDiffCalTest : public CxxTest::TestSuite {
+public:
+  void testName() {
+    ApplyDiffCal appDiffCal;
+    TS_ASSERT_EQUALS(appDiffCal.name(), "ApplyDiffCal")
+  }
+
+  void testInit() {
+    ApplyDiffCal appDiffCal;
+    appDiffCal.initialize();
+    TS_ASSERT(appDiffCal.isInitialized())
+  }
+
+  void testExec() {
+    auto calWSIn = createCalibration(5 * 9); // nine components per bank
+
+    Mantid::Geometry::Instrument_sptr inst = createInstrument();
+
+    std::string testWorkspaceName = "TestApplyDiffCalWorkspace";
+    AnalysisDataService::Instance().add(testWorkspaceName,
+                                        std::make_shared<Workspace2D>());
+    auto instrumentWS =
+        AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
+            testWorkspaceName);
+    instrumentWS->setInstrument(inst);
+
+    ApplyDiffCal appDiffCal;
+    TS_ASSERT_THROWS_NOTHING(appDiffCal.initialize());
+    TS_ASSERT(appDiffCal.isInitialized());
+    TS_ASSERT_THROWS_NOTHING(
+        appDiffCal.setProperty("InstrumentWorkspace", testWorkspaceName));
+    TS_ASSERT_THROWS_NOTHING(
+        appDiffCal.setProperty("CalibrationWorkspace", calWSIn));
+    TS_ASSERT_THROWS_NOTHING(appDiffCal.execute(););
+    TS_ASSERT(appDiffCal.isExecuted());
+
+    auto instFromWS = instrumentWS->getInstrument();
+    auto det = instFromWS->getDetector(3);
+    auto pmap = instFromWS->getParameterMap();
+    auto par = pmap->getRecursive(det.get(), "DIFC");
+    double difc{0.};
+    if (par)
+      difc = par->value<double>();
+    TS_ASSERT_EQUALS(difc, 102);
+    par = pmap->getRecursive(det.get(), "DIFA");
+    double difa{0.};
+    if (par)
+      difa = par->value<double>();
+    TS_ASSERT_EQUALS(difa, 4);
+    par = pmap->getRecursive(det.get(), "TZERO");
+    double tzero{0.};
+    if (par)
+      tzero = par->value<double>();
+    TS_ASSERT_EQUALS(tzero, 2);
+
+    ApplyDiffCal appDiffCalClear;
+    pmap->addDouble(det->getComponentID(), "extraparam", 1.23);
+    TS_ASSERT_THROWS_NOTHING(appDiffCalClear.initialize());
+    TS_ASSERT(appDiffCalClear.isInitialized());
+    TS_ASSERT_THROWS_NOTHING(
+        appDiffCalClear.setProperty("InstrumentWorkspace", testWorkspaceName));
+    TS_ASSERT_THROWS_NOTHING(
+        appDiffCalClear.setProperty("ClearCalibration", true));
+    TS_ASSERT_THROWS_NOTHING(appDiffCalClear.execute(););
+    TS_ASSERT(appDiffCalClear.isExecuted());
+
+    instFromWS = instrumentWS->getInstrument();
+    det = instFromWS->getDetector(3);
+    pmap = instFromWS->getParameterMap();
+    par = pmap->getRecursive(det.get(), "DIFC");
+    TS_ASSERT(!par);
+    par = pmap->getRecursive(det.get(), "extraparam");
+    TS_ASSERT(par);
+  }
+
+  void TestClear() {}
+
+private:
+  Mantid::Geometry::Instrument_sptr createInstrument() {
+    auto instr =
+        ComponentCreationHelper::createTestInstrumentCylindrical(NUM_BANK);
+    auto pmap = std::make_shared<Mantid::Geometry::ParameterMap>();
+    instr = std::make_shared<Mantid::Geometry::Instrument>(instr, pmap);
+    return instr;
+  }
+
+  TableWorkspace_sptr createCalibration(const size_t numRows) {
+    TableWorkspace_sptr wksp = std::make_shared<TableWorkspace>();
+    wksp->addColumn("int", "detid");
+    wksp->addColumn("double", "difc");
+    wksp->addColumn("double", "difa");
+    wksp->addColumn("double", "tzero");
+    wksp->addColumn("double", "tofmin");
+
+    for (size_t i = 0; i < numRows; ++i) {
+      TableRow row = wksp->appendRow();
+
+      row << static_cast<int>(i + 1)      // detid
+          << static_cast<double>(100 + i) // difc
+          << static_cast<double>(i * i)   // difa
+          << static_cast<double>(i)       // tzero
+          << 0.;                          // tofmin
+    }
+    return wksp;
+  }
+};
diff --git a/Framework/DataHandling/test/LoadGSSTest.h b/Framework/DataHandling/test/LoadGSSTest.h
index f38231405fcb5a3be28e826b3360c4a2a93c00d9..7bdeb7771e5537bba34abce60b7bfe79afceb5d3 100644
--- a/Framework/DataHandling/test/LoadGSSTest.h
+++ b/Framework/DataHandling/test/LoadGSSTest.h
@@ -97,6 +97,8 @@ public:
     TS_ASSERT_DELTA(spectrumInfo.twoTheta(0) * 180. / M_PI, 58.308, 1e-4);
     TS_ASSERT_DELTA(spectrumInfo.l2(1), 2.060, 1e-4);
     TS_ASSERT_DELTA(spectrumInfo.twoTheta(1) * 180. / M_PI, 154.257, 1e-4);
+    auto diffConsts = spectrumInfo.diffractometerConstants(1);
+    TS_ASSERT_DELTA(diffConsts[Kernel::UnitParams::difc], 10398.8, 1e-4);
   }
 
   void test_load_gss_ExtendedHeader_gsa() {
diff --git a/Framework/DataHandling/test/LoadLogTest.h b/Framework/DataHandling/test/LoadLogTest.h
index 65d6fa273e906de8db2bcfb1c512ca6db594e1c8..9a1bb00857fc2274942e209bb1436017820f6702 100644
--- a/Framework/DataHandling/test/LoadLogTest.h
+++ b/Framework/DataHandling/test/LoadLogTest.h
@@ -142,6 +142,35 @@ public:
     AnalysisDataService::Instance().remove(outputSpace);
   }
 
+  void testOldLogFileWithSingleDigitDate() {
+    // Snippet from an old log file with unsupported format.
+    std::string oldLogText(
+        "Sat  8-FEB-2003 11:29:24 EXECUTING DOSANS\n"
+        "Sat  8-FEB-2003 11:29:25 LOAD INST.UPD\n"
+        "Sat  8-FEB-2003 11:29:25 CHANGE RUNTABLE "
+        "LOQ$DISK0:[LOQ]INST.UPD;15625\n"
+        "Sat  8-FEB-2003 11:29:27 CSET TRANS OUT\n"
+        "Sat  8-FEB-2003 11:29:27 COMND TRANS    WR 05     2 1 16 4\n");
+
+    TS_ASSERT(oldLogFileGivesCorrectWarning(oldLogText));
+  }
+
+  void testOldLogFileWithDoubleDigitDate() {
+    // Snippet from an old log file with unsupported format.
+    std::string oldLogText("Fri 31-JAN-2003 11:28:15 EXECUTING DOTRANS\n"
+                           "Fri 31-JAN-2003 11:28:41 STOPD A2       _LTA5007: "
+                           "     2.7000         2.7000\n"
+                           "Fri 31-JAN-2003 11:28:42 LOAD INST.UPD\n"
+                           "Fri 31-JAN-2003 11:28:42 CHANGE RUNTABLE "
+                           "LOQ$DISK0:[LOQ]INST.UPD;14965\n"
+                           "Fri 31-JAN-2003 11:28:42 DEBUG 1 ack 001 suc 000\n"
+                           "Fri 31-JAN-2003 11:28:42 DEBUG 1 res 001 suc 002  "
+                           "H000000F0  H00000006\n"
+                           "Fri 31-JAN-2003 11:28:43 CSET TRANS IN\n");
+
+    TS_ASSERT(oldLogFileGivesCorrectWarning(oldLogText));
+  }
+
   void test_log_file_has_error() {
     std::string logFileText("2007-11-16T13:25:48 i1 0 \n"
                             "2007-11-16T13:29:36 str1  a\n"
@@ -312,4 +341,33 @@ private:
   std::string inputFile;
   std::string outputSpace;
   std::string inputSpace;
+
+  bool oldLogFileGivesCorrectWarning(std::string &logFileText) {
+
+    ScopedFile oldLogFile(logFileText, "oldLogFile.log");
+
+    MatrixWorkspace_sptr ws =
+        WorkspaceFactory::Instance().create("Workspace2D", 1, 1, 1);
+
+    LoadLog loadAlg;
+    loadAlg.initialize();
+    loadAlg.setPropertyValue("Filename", oldLogFile.getFileName());
+    loadAlg.setProperty("Workspace", ws);
+    // We want to see what the exception message is.
+    loadAlg.setRethrows(true);
+
+    // LoadLog algorithm should throw exception with an error message that
+    // contains ""
+    try {
+      loadAlg.execute();
+    } catch (std::exception &ex) {
+      std::string errorMessage(ex.what());
+
+      if (errorMessage.find("old unsupported format") != std::string::npos) {
+        return true;
+      }
+    }
+
+    return false;
+  }
 };
diff --git a/Framework/DataObjects/inc/MantidDataObjects/BasePeak.h b/Framework/DataObjects/inc/MantidDataObjects/BasePeak.h
index 68e1167512b779540f114c87c24a041b4cb641c6..7cd806d36d33e9659143b1b1192621b0e7e6530e 100644
--- a/Framework/DataObjects/inc/MantidDataObjects/BasePeak.h
+++ b/Framework/DataObjects/inc/MantidDataObjects/BasePeak.h
@@ -75,6 +75,10 @@ public:
   void setIntHKL(const Kernel::V3D &HKL) override;
   void setIntMNP(const Mantid::Kernel::V3D &MNP) override;
 
+  Mantid::Kernel::V3D getSamplePos() const override;
+  void setSamplePos(double samX, double samY, double samZ) override;
+  void setSamplePos(const Mantid::Kernel::V3D &XYZ) override;
+
   double getIntensity() const override;
   double getSigmaIntensity() const override;
   double getIntensityOverSigma() const override;
@@ -121,6 +125,9 @@ protected:
   // ki-kf for Inelastic convention; kf-ki for Crystallography convention
   std::string convention;
 
+  /// Cached sample position
+  Mantid::Kernel::V3D m_samplePos;
+
 private:
   /// Name of the parent bank
   std::string m_bankName;
diff --git a/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeak.h b/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeak.h
index f05a958380e29cf361501a4bc54dfd76d1e38529..decb5449d98e1615c096c1e9cdb92856fa7c35e8 100644
--- a/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeak.h
+++ b/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeak.h
@@ -57,17 +57,11 @@ public:
   // Construct a peak from a reference to the interface
   explicit LeanElasticPeak(const Geometry::IPeak &ipeak);
 
-  Geometry::IDetector_const_sptr getDetector() const override;
   std::shared_ptr<const Geometry::ReferenceFrame>
   getReferenceFrame() const override;
 
-  void setSamplePos(double, double, double) override;
-  void setSamplePos(const Mantid::Kernel::V3D &) override;
-
   Mantid::Kernel::V3D getQLabFrame() const override;
   Mantid::Kernel::V3D getQSampleFrame() const override;
-  Mantid::Kernel::V3D getDetectorPosition() const override;
-  Mantid::Kernel::V3D getDetectorPositionNoCheck() const override;
 
   void setQSampleFrame(const Mantid::Kernel::V3D &QSampleFrame,
                        boost::optional<double> = boost::none) override;
@@ -89,8 +83,6 @@ public:
   void setInitialEnergy(double m_initialEnergy) override;
   void setFinalEnergy(double m_finalEnergy) override;
 
-  virtual Mantid::Kernel::V3D getDetPos() const override;
-  virtual Mantid::Kernel::V3D getSamplePos() const override;
   double getL1() const override;
   double getL2() const override;
 
diff --git a/Framework/DataObjects/inc/MantidDataObjects/Peak.h b/Framework/DataObjects/inc/MantidDataObjects/Peak.h
index cf6f0eb65368c7a2b8a1585653cea463159027c9..3b34bfed86480245b1ddbb4ed5a4ce7fd68da0f2 100644
--- a/Framework/DataObjects/inc/MantidDataObjects/Peak.h
+++ b/Framework/DataObjects/inc/MantidDataObjects/Peak.h
@@ -85,7 +85,7 @@ public:
   const std::set<int> &getContributingDetIDs() const;
 
   void setInstrument(const Geometry::Instrument_const_sptr &inst);
-  Geometry::IDetector_const_sptr getDetector() const override;
+  Geometry::IDetector_const_sptr getDetector() const;
   Geometry::Instrument_const_sptr getInstrument() const;
   std::shared_ptr<const Geometry::ReferenceFrame>
   getReferenceFrame() const override;
@@ -93,13 +93,10 @@ public:
   bool findDetector();
   bool findDetector(const Geometry::InstrumentRayTracer &tracer);
 
-  void setSamplePos(double samX, double samY, double samZ) override;
-  void setSamplePos(const Mantid::Kernel::V3D &XYZ) override;
-
   Mantid::Kernel::V3D getQLabFrame() const override;
   Mantid::Kernel::V3D getQSampleFrame() const override;
-  Mantid::Kernel::V3D getDetectorPosition() const override;
-  Mantid::Kernel::V3D getDetectorPositionNoCheck() const override;
+  Mantid::Kernel::V3D getDetectorPosition() const;
+  Mantid::Kernel::V3D getDetectorPositionNoCheck() const;
 
   void setQSampleFrame(
       const Mantid::Kernel::V3D &QSampleFrame,
@@ -121,8 +118,7 @@ public:
   void setInitialEnergy(double m_initialEnergy) override;
   void setFinalEnergy(double m_finalEnergy) override;
 
-  virtual Mantid::Kernel::V3D getDetPos() const override;
-  virtual Mantid::Kernel::V3D getSamplePos() const override;
+  virtual Mantid::Kernel::V3D getDetPos() const;
   double getL1() const override;
   double getL2() const override;
 
@@ -155,8 +151,7 @@ private:
 
   /// Cached source position
   Mantid::Kernel::V3D sourcePos;
-  /// Cached sample position
-  Mantid::Kernel::V3D samplePos;
+
   /// Cached detector position
   Mantid::Kernel::V3D detPos;
 
diff --git a/Framework/DataObjects/inc/MantidDataObjects/PeaksWorkspace.h b/Framework/DataObjects/inc/MantidDataObjects/PeaksWorkspace.h
index c277923d6afaa6b5cdabd0ce97fa5d694186264d..07c23c28c2ccb12cc263ae6d6021c28443cd7698 100644
--- a/Framework/DataObjects/inc/MantidDataObjects/PeaksWorkspace.h
+++ b/Framework/DataObjects/inc/MantidDataObjects/PeaksWorkspace.h
@@ -15,6 +15,7 @@
 #include "MantidKernel/SpecialCoordinateSystem.h"
 #include "MantidKernel/V3D.h"
 
+using Mantid::Geometry::IPeak_uptr;
 // IsamplePosition should be IsampleOrientation
 namespace Mantid {
 //----------------------------------------------------------------------
@@ -87,24 +88,22 @@ public:
   Peak &getPeak(int peakNum) override;
   const Peak &getPeak(int peakNum) const override;
 
-  std::unique_ptr<Geometry::IPeak> createPeak(
+  IPeak_uptr createPeak(
       const Kernel::V3D &QLabFrame,
       boost::optional<double> detectorDistance = boost::none) const override;
 
-  std::unique_ptr<Geometry::IPeak>
+  IPeak_uptr
   createPeak(const Kernel::V3D &Position,
              const Kernel::SpecialCoordinateSystem &frame) const override;
 
-  std::unique_ptr<Geometry::IPeak>
-  createPeakQSample(const Kernel::V3D &position) const override;
+  IPeak_uptr createPeakQSample(const Kernel::V3D &position) const override;
 
   std::vector<std::pair<std::string, std::string>>
   peakInfo(const Kernel::V3D &qFrame, bool labCoords) const override;
 
-  std::unique_ptr<Geometry::IPeak>
-  createPeakHKL(const Kernel::V3D &HKL) const override;
+  IPeak_uptr createPeakHKL(const Kernel::V3D &HKL) const override;
 
-  std::unique_ptr<Geometry::IPeak> createPeak() const override;
+  IPeak_uptr createPeak() const override;
 
   int peakInfoNumber(const Kernel::V3D &qFrame, bool labCoords) const override;
 
diff --git a/Framework/DataObjects/src/BasePeak.cpp b/Framework/DataObjects/src/BasePeak.cpp
index 8373a048780f227019c9cc757060b9b9f9b9a2dd..b3b4cdc98670eed99b06cd1f6954852d1479e6e2 100644
--- a/Framework/DataObjects/src/BasePeak.cpp
+++ b/Framework/DataObjects/src/BasePeak.cpp
@@ -31,8 +31,8 @@ namespace DataObjects {
 //----------------------------------------------------------------------------------------------
 /** Default constructor */
 BasePeak::BasePeak()
-    : m_H(0), m_K(0), m_L(0), m_intensity(0), m_sigmaIntensity(0),
-      m_binCount(0), m_absorptionWeightedPathLength(0),
+    : m_samplePos(V3D(0, 0, 0)), m_H(0), m_K(0), m_L(0), m_intensity(0),
+      m_sigmaIntensity(0), m_binCount(0), m_absorptionWeightedPathLength(0),
       m_GoniometerMatrix(3, 3, true), m_InverseGoniometerMatrix(3, 3, true),
       m_runNumber(0), m_monitorCount(0), m_row(-1), m_col(-1), m_peakNumber(0),
       m_intHKL(V3D(0, 0, 0)), m_intMNP(V3D(0, 0, 0)),
@@ -60,10 +60,10 @@ BasePeak::BasePeak(const Mantid::Kernel::Matrix<double> &goniometer)
 }
 
 BasePeak::BasePeak(const BasePeak &other)
-    : convention(other.convention), m_bankName(other.m_bankName),
-      m_H(other.m_H), m_K(other.m_K), m_L(other.m_L),
-      m_intensity(other.m_intensity), m_sigmaIntensity(other.m_sigmaIntensity),
-      m_binCount(other.m_binCount),
+    : convention(other.convention), m_samplePos(other.m_samplePos),
+      m_bankName(other.m_bankName), m_H(other.m_H), m_K(other.m_K),
+      m_L(other.m_L), m_intensity(other.m_intensity),
+      m_sigmaIntensity(other.m_sigmaIntensity), m_binCount(other.m_binCount),
       m_absorptionWeightedPathLength(other.m_absorptionWeightedPathLength),
       m_GoniometerMatrix(other.m_GoniometerMatrix),
       m_InverseGoniometerMatrix(other.m_InverseGoniometerMatrix),
@@ -192,6 +192,31 @@ void BasePeak::setIntMNP(const V3D &MNP) {
   m_intMNP = V3D(std::round(MNP[0]), std::round(MNP[1]), std::round(MNP[2]));
 }
 
+/** Return the sample position vector */
+Mantid::Kernel::V3D BasePeak::getSamplePos() const { return m_samplePos; }
+
+/** Set sample position
+ *
+ * @ doubles x,y,z-> m_samplePos(x), m_samplePos(y), m_samplePos(z)
+ */
+void BasePeak::setSamplePos(double samX, double samY, double samZ) {
+
+  this->m_samplePos[0] = samX;
+  this->m_samplePos[1] = samY;
+  this->m_samplePos[2] = samZ;
+}
+
+/** Set sample position
+ *
+ * @param XYZ :: vector x,y,z-> m_samplePos(x), m_samplePos(y), m_samplePos(z)
+ */
+void BasePeak::setSamplePos(const Mantid::Kernel::V3D &XYZ) {
+
+  this->m_samplePos[0] = XYZ[0];
+  this->m_samplePos[1] = XYZ[1];
+  this->m_samplePos[2] = XYZ[2];
+}
+
 //----------------------------------------------------------------------------------------------
 /** Return the # of counts in the bin at its peak*/
 double BasePeak::getBinCount() const { return m_binCount; }
diff --git a/Framework/DataObjects/src/LeanElasticPeak.cpp b/Framework/DataObjects/src/LeanElasticPeak.cpp
index 5c100f1b2d2172bee5213f863982fb36610ff03c..0864a4617f8de75cada6ee78991eff713acd6bdc 100644
--- a/Framework/DataObjects/src/LeanElasticPeak.cpp
+++ b/Framework/DataObjects/src/LeanElasticPeak.cpp
@@ -99,13 +99,6 @@ void LeanElasticPeak::setWavelength(double wavelength) {
   m_wavelength = wavelength;
 }
 
-//----------------------------------------------------------------------------------------------
-/** Return a shared ptr to the detector at center of peak. */
-Geometry::IDetector_const_sptr LeanElasticPeak::getDetector() const {
-  throw Exception::NotImplementedError(
-      "LeanElasticPeak::getDetector(): Has no detector ID");
-}
-
 /** Return a shared ptr to the reference frame for this peak. */
 std::shared_ptr<const Geometry::ReferenceFrame>
 LeanElasticPeak::getReferenceFrame() const {
@@ -241,18 +234,6 @@ double LeanElasticPeak::getInitialEnergy() const { return getFinalEnergy(); }
  * elastic so always 0 */
 double LeanElasticPeak::getEnergyTransfer() const { return 0.; }
 
-/** Set sample position */
-void LeanElasticPeak::setSamplePos(double, double, double) {
-  throw Exception::NotImplementedError(
-      "LeanElasticPeak has no sample information");
-}
-
-/** Set sample position  */
-void LeanElasticPeak::setSamplePos(const Mantid::Kernel::V3D &) {
-  throw Exception::NotImplementedError(
-      "LeanElasticPeak has no sample information");
-}
-
 /** Set the final energy */
 void LeanElasticPeak::setFinalEnergy(double) {
   throw Exception::NotImplementedError("Use LeanElasticPeak::setWavelength");
@@ -263,20 +244,6 @@ void LeanElasticPeak::setInitialEnergy(double) {
   throw Exception::NotImplementedError("Use LeanElasticPeak::setWavelength");
 }
 
-// -------------------------------------------------------------------------------------
-/** Return the detector position vector */
-Mantid::Kernel::V3D LeanElasticPeak::getDetPos() const {
-  throw Exception::NotImplementedError(
-      "LeanElasticPeak has no detector information");
-}
-
-// -------------------------------------------------------------------------------------
-/** Return the sample position vector */
-Mantid::Kernel::V3D LeanElasticPeak::getSamplePos() const {
-  throw Exception::NotImplementedError(
-      "LeanElasticPeak has no sample information");
-}
-
 // -------------------------------------------------------------------------------------
 /** Return the L1 flight path length (source to sample), in meters. */
 double LeanElasticPeak::getL1() const {
@@ -305,23 +272,6 @@ LeanElasticPeak &LeanElasticPeak::operator=(const LeanElasticPeak &other) {
   return *this;
 }
 
-/**
- Forwarding function. Exposes the detector position directly.
- */
-Mantid::Kernel::V3D LeanElasticPeak::getDetectorPositionNoCheck() const {
-  throw Exception::NotImplementedError(
-      "LeanElasticPeak has no detector information");
-}
-
-/**
- Forwarding function. Exposes the detector position directly, but checks that
- the detector is not null before accessing its position. Throws if null.
- */
-Mantid::Kernel::V3D LeanElasticPeak::getDetectorPosition() const {
-  throw Exception::NotImplementedError(
-      "LeanElasticPeak has no detector information");
-}
-
 Mantid::Kernel::Logger LeanElasticPeak::g_log("PeakLogger");
 
 } // namespace DataObjects
diff --git a/Framework/DataObjects/src/Peak.cpp b/Framework/DataObjects/src/Peak.cpp
index 0f6107e62b0dadce310d30b2b29e2ea99fbd67c2..c32270b7ce4099e3851f94083d82220e3934fd0a 100644
--- a/Framework/DataObjects/src/Peak.cpp
+++ b/Framework/DataObjects/src/Peak.cpp
@@ -155,13 +155,13 @@ Peak::Peak(const Peak &other)
     : BasePeak(other), m_inst(other.m_inst), m_det(other.m_det),
       m_detectorID(other.m_detectorID), m_initialEnergy(other.m_initialEnergy),
       m_finalEnergy(other.m_finalEnergy), sourcePos(other.sourcePos),
-      samplePos(other.samplePos), detPos(other.detPos),
-      m_detIDs(other.m_detIDs) {}
+      detPos(other.detPos), m_detIDs(other.m_detIDs) {}
 
 //----------------------------------------------------------------------------------------------
 /** Constructor making a Peak from IPeak interface
  *
- * @param ipeak :: const reference to an IPeak object
+ * @param ipeak :: const reference to an IPeak object though actually
+ * referencing a Peak object.
  * @return
  */
 Peak::Peak(const Geometry::IPeak &ipeak)
@@ -320,7 +320,7 @@ void Peak::setInstrument(const Geometry::Instrument_const_sptr &inst) {
                                                "instrument");
 
   sourcePos = sourceObj->getPos();
-  samplePos = sampleObj->getPos();
+  m_samplePos = sampleObj->getPos();
 }
 
 //----------------------------------------------------------------------------------------------
@@ -377,8 +377,8 @@ double Peak::getTOF() const {
 /** Calculate the scattering angle of the peak  */
 double Peak::getScattering() const {
   // The detector is at 2 theta scattering angle
-  V3D beamDir = samplePos - sourcePos;
-  V3D detDir = detPos - samplePos;
+  V3D beamDir = m_samplePos - sourcePos;
+  V3D detDir = detPos - m_samplePos;
 
   return detDir.angle(beamDir);
 }
@@ -387,7 +387,7 @@ double Peak::getScattering() const {
 /** Calculate the azimuthal angle of the peak  */
 double Peak::getAzimuthal() const {
   // The detector is at 2 theta scattering angle
-  V3D detDir = detPos - samplePos;
+  V3D detDir = detPos - m_samplePos;
 
   return atan2(detDir.Y(), detDir.X());
 }
@@ -396,8 +396,8 @@ double Peak::getAzimuthal() const {
 /** Calculate the d-spacing of the peak, in 1/Angstroms  */
 double Peak::getDSpacing() const {
   // The detector is at 2 theta scattering angle
-  V3D beamDir = samplePos - sourcePos;
-  V3D detDir = detPos - samplePos;
+  V3D beamDir = m_samplePos - sourcePos;
+  V3D detDir = detPos - m_samplePos;
 
   double two_theta;
   try {
@@ -423,10 +423,10 @@ double Peak::getDSpacing() const {
  * */
 Mantid::Kernel::V3D Peak::getQLabFrame() const {
   // Normalized beam direction
-  V3D beamDir = samplePos - sourcePos;
+  V3D beamDir = m_samplePos - sourcePos;
   beamDir /= beamDir.norm();
   // Normalized detector direction
-  V3D detDir = (detPos - samplePos);
+  V3D detDir = (detPos - m_samplePos);
   detDir /= detDir.norm();
 
   // Energy in J of the neutron
@@ -550,7 +550,7 @@ void Peak::setQLabFrame(const Mantid::Kernel::V3D &qLab,
 
   // Use the given detector distance to find the detector position.
   if (detectorDistance.is_initialized()) {
-    detPos = samplePos + detectorDir * detectorDistance.get();
+    detPos = m_samplePos + detectorDir * detectorDistance.get();
     // We do not-update the detector as by manually setting the distance the
     // client seems to know better.
   } else {
@@ -576,7 +576,7 @@ V3D Peak::getVirtualDetectorPosition(const V3D &detectorDir) const {
   }
   const auto object = std::dynamic_pointer_cast<const ObjComponent>(component);
   const auto distance =
-      object->shape()->distance(Geometry::Track(samplePos, detectorDir));
+      object->shape()->distance(Geometry::Track(m_samplePos, detectorDir));
   return detectorDir * distance;
 }
 
@@ -612,7 +612,7 @@ bool Peak::findDetector() {
  */
 bool Peak::findDetector(const InstrumentRayTracer &tracer) {
   // Scattered beam direction
-  const V3D beam = normalize(detPos - samplePos);
+  const V3D beam = normalize(detPos - m_samplePos);
 
   return findDetector(beam, tracer);
 }
@@ -680,28 +680,6 @@ double Peak::getEnergyTransfer() const {
   return getInitialEnergy() - getFinalEnergy();
 }
 
-/** Set sample position
- *
- * @ doubles x,y,z-> samplePos(x), samplePos(y), samplePos(z)
- */
-void Peak::setSamplePos(double samX, double samY, double samZ) {
-
-  this->samplePos[0] = samX;
-  this->samplePos[1] = samY;
-  this->samplePos[2] = samZ;
-}
-
-/** Set sample position
- *
- * @param XYZ :: vector x,y,z-> samplePos(x), samplePos(y), samplePos(z)
- */
-void Peak::setSamplePos(const Mantid::Kernel::V3D &XYZ) {
-
-  this->samplePos[0] = XYZ[0];
-  this->samplePos[1] = XYZ[1];
-  this->samplePos[2] = XYZ[2];
-}
-
 /** Set the final energy
  * @param m_finalEnergy :: final energy in meV   */
 void Peak::setFinalEnergy(double m_finalEnergy) {
@@ -718,17 +696,13 @@ void Peak::setInitialEnergy(double m_initialEnergy) {
 /** Return the detector position vector */
 Mantid::Kernel::V3D Peak::getDetPos() const { return detPos; }
 
-// -------------------------------------------------------------------------------------
-/** Return the sample position vector */
-Mantid::Kernel::V3D Peak::getSamplePos() const { return samplePos; }
-
 // -------------------------------------------------------------------------------------
 /** Return the L1 flight path length (source to sample), in meters. */
-double Peak::getL1() const { return (samplePos - sourcePos).norm(); }
+double Peak::getL1() const { return (m_samplePos - sourcePos).norm(); }
 
 // -------------------------------------------------------------------------------------
 /** Return the L2 flight path length (sample to detector), in meters. */
-double Peak::getL2() const { return (detPos - samplePos).norm(); }
+double Peak::getL2() const { return (detPos - m_samplePos).norm(); }
 
 /**
  * @brief Assignement operator overload
@@ -744,7 +718,7 @@ Peak &Peak::operator=(const Peak &other) {
     m_initialEnergy = other.m_initialEnergy;
     m_finalEnergy = other.m_finalEnergy;
     sourcePos = other.sourcePos;
-    samplePos = other.samplePos;
+    m_samplePos = other.m_samplePos;
     detPos = other.detPos;
     m_detIDs = other.m_detIDs;
   }
diff --git a/Framework/DataObjects/test/EventListTest.h b/Framework/DataObjects/test/EventListTest.h
index 697a6012932ea1bf4d4f6ea06515966dd7782ba0..468a1985c71160bc15a2ef1211d931341536f97f 100644
--- a/Framework/DataObjects/test/EventListTest.h
+++ b/Framework/DataObjects/test/EventListTest.h
@@ -1486,8 +1486,8 @@ public:
   void test_convertUnitsViaTof_allTypes() {
     DummyUnit1 fromUnit;
     DummyUnit2 toUnit;
-    fromUnit.initialize(1, 2, 3, 4, 5, 6);
-    toUnit.initialize(1, 2, 3, 4, 5, 6);
+    fromUnit.initialize(1, 2, {});
+    toUnit.initialize(1, 2, {});
     // Go through each possible EventType as the input
     for (int this_type = 0; this_type < 3; this_type++) {
       this->fake_uniform_data();
diff --git a/Framework/DataObjects/test/LeanElasticPeakTest.h b/Framework/DataObjects/test/LeanElasticPeakTest.h
index d1a50ae394c1b22594760ad3bc3828378a898efe..23c0e512d3642623ff45f56019d37dea21b92493 100644
--- a/Framework/DataObjects/test/LeanElasticPeakTest.h
+++ b/Framework/DataObjects/test/LeanElasticPeakTest.h
@@ -37,14 +37,7 @@ public:
     TS_ASSERT(std::isinf(p.getFinalEnergy()))
     TS_ASSERT_EQUALS(p.getQSampleFrame(), V3D(0, 0, 0))
     TS_ASSERT_EQUALS(p.getQLabFrame(), V3D())
-
-    TS_ASSERT_THROWS(p.getDetector(), const Exception::NotImplementedError &)
-    TS_ASSERT_THROWS(p.getDetectorPosition(),
-                     const Exception::NotImplementedError &)
-    TS_ASSERT_THROWS(p.getDetectorPositionNoCheck(),
-                     const Exception::NotImplementedError &)
-    TS_ASSERT_THROWS(p.getDetPos(), const Exception::NotImplementedError &)
-    TS_ASSERT_THROWS(p.getSamplePos(), const Exception::NotImplementedError &)
+    TS_ASSERT_EQUALS(p.getSamplePos(), V3D(0, 0, 0))
     TS_ASSERT_THROWS(p.getTOF(), const Exception::NotImplementedError &)
     TS_ASSERT_EQUALS(p.getScattering(), 0.)
     TS_ASSERT_EQUALS(p.getAzimuthal(), -M_PI)
@@ -330,8 +323,5 @@ public:
 
     TS_ASSERT_EQUALS(leanpeak.getBinCount(), peak.getBinCount());
     TS_ASSERT_EQUALS(leanpeak.getBinCount(), 90);
-
-    TS_ASSERT_THROWS(leanpeak.getDetector(),
-                     const Exception::NotImplementedError &)
   }
 };
diff --git a/Framework/DataObjects/test/PeaksWorkspaceTest.h b/Framework/DataObjects/test/PeaksWorkspaceTest.h
index 58c833c3a32325b6ab8fd5a98d8d360c100cc388..3d11510620abcfc0810d41ac9ae6f398fe37b03d 100644
--- a/Framework/DataObjects/test/PeaksWorkspaceTest.h
+++ b/Framework/DataObjects/test/PeaksWorkspaceTest.h
@@ -422,8 +422,8 @@ public:
     const auto params = makePeakParameters();
     auto ws = makeWorkspace(params);
     // Create the peak
-    auto peak = ws->createPeakHKL(params.hkl);
-
+    IPeak_uptr ipeak = ws->createPeakHKL(params.hkl);
+    Peak_uptr peak(static_cast<Peak *>(ipeak.release()));
     /*
      Now we check we have made a self - consistent peak
      */
diff --git a/Framework/Geometry/inc/MantidGeometry/Crystal/IPeak.h b/Framework/Geometry/inc/MantidGeometry/Crystal/IPeak.h
index d1ad3dce179c2594a2b784c8decd90b43639f344..341c3d9873a2cf2c788e3d9042579d29758ebe1e 100644
--- a/Framework/Geometry/inc/MantidGeometry/Crystal/IPeak.h
+++ b/Framework/Geometry/inc/MantidGeometry/Crystal/IPeak.h
@@ -26,7 +26,6 @@ class InstrumentRayTracer;
 class MANTID_GEOMETRY_DLL IPeak {
 public:
   virtual ~IPeak() = default;
-  virtual Geometry::IDetector_const_sptr getDetector() const = 0;
   virtual std::shared_ptr<const Geometry::ReferenceFrame>
   getReferenceFrame() const = 0;
 
@@ -51,8 +50,6 @@ public:
   virtual void setSamplePos(double samX, double samY, double samZ) = 0;
   virtual void setSamplePos(const Mantid::Kernel::V3D &XYZ) = 0;
   virtual Mantid::Kernel::V3D getSamplePos() const = 0;
-  virtual Mantid::Kernel::V3D getDetectorPosition() const = 0;
-  virtual Mantid::Kernel::V3D getDetectorPositionNoCheck() const = 0;
 
   virtual Mantid::Kernel::V3D getQLabFrame() const = 0;
   virtual Mantid::Kernel::V3D getQSampleFrame() const = 0;
@@ -97,7 +94,6 @@ public:
   virtual int getRow() const = 0;
   virtual int getCol() const = 0;
 
-  virtual Mantid::Kernel::V3D getDetPos() const = 0;
   virtual double getL1() const = 0;
   virtual double getL2() const = 0;
 
diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfo.h b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfo.h
index b4c07f743e3d0fddcdc376ee9a38956fdd330659..9c84b45c7d59ff28917e4a8eed7255cfcf4f4e1a 100644
--- a/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfo.h
+++ b/Framework/Geometry/inc/MantidGeometry/Instrument/DetectorInfo.h
@@ -76,6 +76,11 @@ public:
   double signedTwoTheta(const std::pair<size_t, size_t> &index) const;
   double azimuthal(const size_t index) const;
   double azimuthal(const std::pair<size_t, size_t> &index) const;
+  std::tuple<double, double, double>
+  diffractometerConstants(const size_t index,
+                          std::vector<detid_t> &calibratedDets,
+                          std::vector<detid_t> &uncalibratedDets) const;
+  double difcUncalibrated(const size_t index) const;
   std::pair<double, double> geographicalAngles(const size_t index) const;
   std::pair<double, double>
   geographicalAngles(const std::pair<size_t, size_t> &index) const;
@@ -125,6 +130,7 @@ private:
   const Geometry::IDetector &getDetector(const size_t index) const;
   std::shared_ptr<const Geometry::IDetector>
   getDetectorPtr(const size_t index) const;
+  void clearPositionDependentParameters(const size_t index);
 
   /// Pointer to the actual DetectorInfo object (non-wrapping part).
   std::unique_ptr<Beamline::DetectorInfo> m_detectorInfo;
diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/ParameterMap.h b/Framework/Geometry/inc/MantidGeometry/Instrument/ParameterMap.h
index 7fb23a1ee2a58b78525a34e7f10a99df196d1f86..56f998a6edc9d959a50f6bf1131369f7cd799700 100644
--- a/Framework/Geometry/inc/MantidGeometry/Instrument/ParameterMap.h
+++ b/Framework/Geometry/inc/MantidGeometry/Instrument/ParameterMap.h
@@ -82,8 +82,10 @@ public:
   static const std::string &pQuat();
   static const std::string &scale();
 
-  const std::string diff(const ParameterMap &rhs,
-                         const bool &firstDiffOnly = false) const;
+  const std::string
+  diff(const ParameterMap &rhs, const bool &firstDiffOnly = false,
+       const bool relative = false,
+       const double doubleTolerance = Kernel::Tolerance) const;
 
   /// Inquality comparison operator
   bool operator!=(const ParameterMap &rhs) const;
@@ -346,6 +348,8 @@ private:
   /// the parameter map
   component_map_cit positionOf(const IComponent *comp, const char *name,
                                const char *type) const;
+  /// calculate relative error for use in diff
+  bool relErr(double x1, double x2, double errorVal) const;
 
   /// internal list of parameter files loaded
   std::vector<std::string> m_parameterFileNames;
diff --git a/Framework/Geometry/src/Instrument.cpp b/Framework/Geometry/src/Instrument.cpp
index edc2af5db14cdab499025a96e950815031801e98..3b2694bcd2c9f9db9a8d4fa9d75c451422b37a82 100644
--- a/Framework/Geometry/src/Instrument.cpp
+++ b/Framework/Geometry/src/Instrument.cpp
@@ -20,6 +20,7 @@
 #include "MantidKernel/Exception.h"
 #include "MantidKernel/Logger.h"
 #include "MantidKernel/PhysicalConstants.h"
+#include "MantidKernel/Unit.h"
 
 #include <memory>
 #include <nexus/NeXusFile.hpp>
@@ -858,9 +859,6 @@ void Instrument::appendPlottable(
   }
 }
 
-const double CONSTANT = (PhysicalConstants::h * 1e10) /
-                        (2.0 * PhysicalConstants::NeutronMass * 1e6);
-
 //------------------------------------------------------------------------------------------------
 /** Get several instrument parameters used in tof to D-space conversion
  *
@@ -1365,50 +1363,9 @@ namespace Conversion {
  */
 double tofToDSpacingFactor(const double l1, const double l2,
                            const double twoTheta, const double offset) {
-  if (offset <=
-      -1.) // not physically possible, means result is negative d-spacing
-  {
-    std::stringstream msg;
-    msg << "Encountered offset of " << offset
-        << " which converts data to negative d-spacing\n";
-    throw std::logic_error(msg.str());
-  }
-
-  auto sinTheta = std::sin(twoTheta / 2);
-
-  const double numerator = (1.0 + offset);
-  sinTheta *= (l1 + l2);
-
-  return (numerator * CONSTANT) / sinTheta;
+  return Kernel::Units::tofToDSpacingFactor(l1, l2, twoTheta, offset);
 }
 
-/** Calculate the conversion factor from tof -> d-spacing
- * for a LIST of detector ids assigned to a single spectrum.
- * @brief tofToDSpacingFactor
- * @param l1
- * @param l2
- * @param twoTheta scattering angle
- * @param detectors
- * @param offsets
- * @return
- */
-double tofToDSpacingFactor(const double l1, const double l2,
-                           const double twoTheta,
-                           const std::vector<detid_t> &detectors,
-                           const std::map<detid_t, double> &offsets) {
-  double factor = 0.;
-  double offset;
-  for (auto detector : detectors) {
-    auto off_iter = offsets.find(detector);
-    if (off_iter != offsets.cend()) {
-      offset = offsets.find(detector)->second;
-    } else {
-      offset = 0.;
-    }
-    factor += tofToDSpacingFactor(l1, l2, twoTheta, offset);
-  }
-  return factor / static_cast<double>(detectors.size());
-}
 } // namespace Conversion
 } // namespace Geometry
 } // Namespace Mantid
diff --git a/Framework/Geometry/src/Instrument/DetectorInfo.cpp b/Framework/Geometry/src/Instrument/DetectorInfo.cpp
index bda1593cc5337214038980dd845685ba4038dda0..3ce2a14be114a8e950ce07c1de3db782342d1273 100644
--- a/Framework/Geometry/src/Instrument/DetectorInfo.cpp
+++ b/Framework/Geometry/src/Instrument/DetectorInfo.cpp
@@ -15,6 +15,7 @@
 #include "MantidKernel/EigenConversionHelpers.h"
 #include "MantidKernel/Exception.h"
 #include "MantidKernel/MultiThreaded.h"
+#include "MantidKernel/Unit.h"
 
 namespace Mantid {
 namespace Geometry {
@@ -308,6 +309,37 @@ double DetectorInfo::azimuthal(const std::pair<size_t, size_t> &index) const {
   return atan2(dotVertical, dotHorizontal);
 }
 
+std::tuple<double, double, double> DetectorInfo::diffractometerConstants(
+    const size_t index, std::vector<detid_t> &calibratedDets,
+    std::vector<detid_t> &uncalibratedDets) const {
+  auto det = m_instrument->getDetector((*m_detectorIDs)[index]);
+  auto pmap = m_instrument->getParameterMap();
+  auto par = pmap->get(det.get(), "DIFC");
+  if (par) {
+    double difc = par->value<double>();
+    calibratedDets.push_back((*m_detectorIDs)[index]);
+    double difa = 0., tzero = 0.;
+    par = pmap->get(det.get(), "DIFA");
+    if (par)
+      difa = par->value<double>();
+    par = pmap->get(det.get(), "TZERO");
+    if (par)
+      tzero = par->value<double>();
+    return {difa, difc, tzero};
+  } else {
+    // if calibrated difc not available, revert to uncalibrated difc with other
+    // two constants=0
+    uncalibratedDets.push_back((*m_detectorIDs)[index]);
+    double difc = difcUncalibrated(index);
+    return {0., difc, 0.};
+  }
+}
+
+double DetectorInfo::difcUncalibrated(const size_t index) const {
+  return 1. / Kernel::Units::tofToDSpacingFactor(l1(), l2(index),
+                                                 twoTheta(index), 0.);
+}
+
 std::pair<double, double>
 DetectorInfo::geographicalAngles(const size_t index) const {
   const auto samplePos = samplePosition();
@@ -383,15 +415,28 @@ void DetectorInfo::clearMaskFlags() {
 /// Set the absolute position of the detector with given index. Not thread safe.
 void DetectorInfo::setPosition(const size_t index,
                                const Kernel::V3D &position) {
+
+  clearPositionDependentParameters(index);
   m_detectorInfo->setPosition(index, Kernel::toVector3d(position));
 }
 
 /// Set the absolute position of the detector with given index. Not thread safe.
 void DetectorInfo::setPosition(const std::pair<size_t, size_t> &index,
                                const Kernel::V3D &position) {
+  clearPositionDependentParameters(index.first);
   m_detectorInfo->setPosition(index, Kernel::toVector3d(position));
 }
 
+// Clear any parameters whose value is only valid for specific positions
+// Currently diffractometer constants
+void DetectorInfo::clearPositionDependentParameters(const size_t index) {
+  auto det = m_instrument->getDetector((*m_detectorIDs)[index]);
+  auto pmap = m_instrument->getParameterMap();
+  pmap->clearParametersByName("DIFA", det.get());
+  pmap->clearParametersByName("DIFC", det.get());
+  pmap->clearParametersByName("TZERO", det.get());
+}
+
 /// Set the absolute rotation of the detector with given index. Not thread safe.
 void DetectorInfo::setRotation(const size_t index,
                                const Kernel::Quat &rotation) {
diff --git a/Framework/Geometry/src/Instrument/ParameterMap.cpp b/Framework/Geometry/src/Instrument/ParameterMap.cpp
index 05b2d805e4184250a3493d3a58e5165e3c63c7de..4b6ff507b1822e2670b6cc0456d6790071efe6ab 100644
--- a/Framework/Geometry/src/Instrument/ParameterMap.cpp
+++ b/Framework/Geometry/src/Instrument/ParameterMap.cpp
@@ -138,36 +138,7 @@ bool ParameterMap::operator!=(const ParameterMap &rhs) const {
  * @return true if the objects are considered equal, false otherwise
  */
 bool ParameterMap::operator==(const ParameterMap &rhs) const {
-  if (this == &rhs)
-    return true; // True for the same object
-
-  // Quick size check
-  if (this->size() != rhs.size())
-    return false;
-
-  // The map is unordered and the key is only valid at runtime. The
-  // asString method turns the ComponentIDs to full-qualified name identifiers
-  // so we will use the same approach to compare them
-
-  auto thisEnd = this->m_map.cend();
-  auto rhsEnd = rhs.m_map.cend();
-  for (auto thisIt = this->m_map.begin(); thisIt != thisEnd; ++thisIt) {
-    const IComponent *comp = static_cast<IComponent *>(thisIt->first);
-    const std::string fullName = comp->getFullName();
-    const auto &param = thisIt->second;
-    bool match(false);
-    for (auto rhsIt = rhs.m_map.cbegin(); rhsIt != rhsEnd; ++rhsIt) {
-      const IComponent *rhsComp = static_cast<IComponent *>(rhsIt->first);
-      const std::string rhsFullName = rhsComp->getFullName();
-      if (fullName == rhsFullName && (*param) == (*rhsIt->second)) {
-        match = true;
-        break;
-      }
-    }
-    if (!match)
-      return false;
-  }
-  return true;
+  return diff(rhs, true, false, 0.).empty();
 }
 
 /** Get the component description by name
@@ -217,6 +188,31 @@ ParameterMap::getShortDescription(const std::string &compName,
   }
   return result;
 }
+
+//------------------------------------------------------------------------------------------------
+/** Function which calculates relative error between two values and analyses if
+this error is within the limits
+* requested. When the absolute value of the difference is smaller then the value
+of the error requested,
+* absolute error is used instead of relative error.
+
+@param x1       -- first value to check difference
+@param x2       -- second value to check difference
+@param errorVal -- the value of the error, to check against. Should  be large
+then 0
+
+@returns true if error or false if the value is within the limits requested
+*/
+bool ParameterMap::relErr(double x1, double x2, double errorVal) const {
+  double num = std::fabs(x1 - x2);
+  // how to treat x1<0 and x2 > 0 ?  probably this way
+  double den = 0.5 * (std::fabs(x1) + std::fabs(x2));
+  if (den < errorVal)
+    return (num > errorVal);
+
+  return (num / den > errorVal);
+}
+
 /**
  * Output information that helps understanding the mismatch between two
  * parameter maps.
@@ -226,10 +222,15 @@ ParameterMap::getShortDescription(const std::string &compName,
  * true
  * @param rhs A reference to a ParameterMap object to compare it to
  * @param firstDiffOnly If true return only first difference found
+ * @param relative Indicates whether to treat the error as relative or absolute
+ * @param doubleTolerance The tolerance to use when comparing parameter values
+ * of type double
  * @return diff as a string
  */
 const std::string ParameterMap::diff(const ParameterMap &rhs,
-                                     const bool &firstDiffOnly) const {
+                                     const bool &firstDiffOnly,
+                                     const bool relative,
+                                     const double doubleTolerance) const {
   if (this == &rhs)
     return std::string(""); // True for the same object
 
@@ -240,25 +241,41 @@ const std::string ParameterMap::diff(const ParameterMap &rhs,
            std::to_string(rhs.size());
   }
 
-  // Run this same loops as in operator==
   // The map is unordered and the key is only valid at runtime. The
   // asString method turns the ComponentIDs to full-qualified name identifiers
   // so we will use the same approach to compare them
 
+  std::unordered_multimap<std::string, Parameter_sptr> thisMap, rhsMap;
+  for (auto &mappair : this->m_map) {
+    thisMap.emplace(mappair.first->getFullName(), mappair.second);
+  }
+  for (auto &mappair : rhs.m_map) {
+    rhsMap.emplace(mappair.first->getFullName(), mappair.second);
+  }
+
   std::stringstream strOutput;
-  auto thisEnd = this->m_map.cend();
-  auto rhsEnd = rhs.m_map.cend();
-  for (auto thisIt = this->m_map.cbegin(); thisIt != thisEnd; ++thisIt) {
-    const IComponent *comp = static_cast<IComponent *>(thisIt->first);
-    const std::string fullName = comp->getFullName();
+  for (auto thisIt = thisMap.cbegin(); thisIt != thisMap.cend(); ++thisIt) {
+    const std::string fullName = thisIt->first;
     const auto &param = thisIt->second;
     bool match(false);
-    for (auto rhsIt = rhs.m_map.cbegin(); rhsIt != rhsEnd; ++rhsIt) {
-      const IComponent *rhsComp = static_cast<IComponent *>(rhsIt->first);
-      const std::string rhsFullName = rhsComp->getFullName();
-      if (fullName == rhsFullName && (*param) == (*rhsIt->second)) {
-        match = true;
-        break;
+    for (auto rhsIt = rhsMap.cbegin(); rhsIt != rhsMap.cend(); ++rhsIt) {
+      const std::string rhsFullName = rhsIt->first;
+      const auto &rhsParam = rhsIt->second;
+      if ((fullName == rhsFullName) && (param->name() == (rhsParam->name()))) {
+        if ((param->type() == rhsParam->type()) &&
+            (rhsParam->type() == "double")) {
+          if (relative) {
+            if (!relErr(param->value<double>(), rhsParam->value<double>(),
+                        doubleTolerance))
+              match = true;
+          } else if (std::abs(param->value<double>() -
+                              rhsParam->value<double>()) <= doubleTolerance)
+            match = true;
+        } else if (param->asString() == rhsParam->asString()) {
+          match = true;
+        }
+        if (match)
+          break;
       }
     }
 
@@ -270,9 +287,8 @@ const std::string ParameterMap::diff(const ParameterMap &rhs,
                 << " and value: " << (*param).asString() << '\n';
       bool componentWithSameNameRHS = false;
       bool parameterWithSameNameRHS = false;
-      for (auto rhsIt = rhs.m_map.cbegin(); rhsIt != rhsEnd; ++rhsIt) {
-        const IComponent *rhsComp = static_cast<IComponent *>(rhsIt->first);
-        const std::string rhsFullName = rhsComp->getFullName();
+      for (auto rhsIt = rhsMap.cbegin(); rhsIt != rhsMap.cend(); ++rhsIt) {
+        const std::string rhsFullName = rhsIt->first;
         if (fullName == rhsFullName) {
           componentWithSameNameRHS = true;
           if ((*param).name() == (*rhsIt->second).name()) {
diff --git a/Framework/Geometry/test/MockObjects.h b/Framework/Geometry/test/MockObjects.h
index 16aa90434cf64837203baae19d7c57a92f2378f6..33924160b206af682c3b3e10a07d0aa3dc84847b 100644
--- a/Framework/Geometry/test/MockObjects.h
+++ b/Framework/Geometry/test/MockObjects.h
@@ -64,7 +64,6 @@ Mock IPeak
 ------------------------------------------------------------*/
 class MockIPeak : public Mantid::Geometry::IPeak {
 public:
-  MOCK_CONST_METHOD0(getDetector, Geometry::IDetector_const_sptr());
   MOCK_CONST_METHOD0(getReferenceFrame,
                      std::shared_ptr<const Geometry::ReferenceFrame>());
   MOCK_CONST_METHOD0(getRunNumber, int());
@@ -123,11 +122,8 @@ public:
   MOCK_CONST_METHOD0(getBankName, std::string());
   MOCK_CONST_METHOD0(getRow, int());
   MOCK_CONST_METHOD0(getCol, int());
-  MOCK_CONST_METHOD0(getDetPos, Mantid::Kernel::V3D());
   MOCK_CONST_METHOD0(getL1, double());
   MOCK_CONST_METHOD0(getL2, double());
-  MOCK_CONST_METHOD0(getDetectorPosition, Mantid::Kernel::V3D());
-  MOCK_CONST_METHOD0(getDetectorPositionNoCheck, Mantid::Kernel::V3D());
   MOCK_CONST_METHOD0(getPeakShape, const Mantid::Geometry::PeakShape &());
   MOCK_METHOD1(setPeakShape, void(Mantid::Geometry::PeakShape *shape));
   MOCK_METHOD1(setPeakShape,
diff --git a/Framework/Kernel/CMakeLists.txt b/Framework/Kernel/CMakeLists.txt
index fc5097d760f22c9ea93d187de70bcb52d0c847fb..483280d511355d8dea2950fc4798dc96a95c8b57 100644
--- a/Framework/Kernel/CMakeLists.txt
+++ b/Framework/Kernel/CMakeLists.txt
@@ -23,7 +23,6 @@ set(SRC_FILES
     src/DateTimeValidator.cpp
     src/DateValidator.cpp
     src/DeltaEMode.cpp
-    src/Diffraction.cpp
     src/DirectoryValidator.cpp
     src/DiskBuffer.cpp
     src/DllOpen.cpp
@@ -173,7 +172,6 @@ set(INC_FILES
     inc/MantidKernel/DateTimeValidator.h
     inc/MantidKernel/DateValidator.h
     inc/MantidKernel/DeltaEMode.h
-    inc/MantidKernel/Diffraction.h
     inc/MantidKernel/DirectoryValidator.h
     inc/MantidKernel/DiskBuffer.h
     inc/MantidKernel/DllConfig.h
@@ -343,7 +341,6 @@ set(TEST_FILES
     DateTimeValidatorTest.h
     DateValidatorTest.h
     DeltaEModeTest.h
-    DiffractionTest.h
     DirectoryValidatorTest.h
     DiskBufferISaveableTest.h
     DiskBufferTest.h
diff --git a/Framework/Kernel/inc/MantidKernel/Diffraction.h b/Framework/Kernel/inc/MantidKernel/Diffraction.h
deleted file mode 100644
index a75c1f0e910896d0bb35e1fe91d0b513bcfd3e04..0000000000000000000000000000000000000000
--- a/Framework/Kernel/inc/MantidKernel/Diffraction.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Mantid Repository : https://github.com/mantidproject/mantid
-//
-// Copyright &copy; 2017 ISIS Rutherford Appleton Laboratory UKRI,
-//   NScD Oak Ridge National Laboratory, European Spallation Source,
-//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
-// SPDX - License - Identifier: GPL - 3.0 +
-#pragma once
-
-#include "MantidKernel/DllConfig.h"
-#include <functional>
-
-namespace Mantid {
-namespace Kernel {
-namespace Diffraction {
-
-/** Diffraction : Collection of functions useful in diffraction scattering
- */
-
-MANTID_KERNEL_DLL double calcTofMin(const double difc, const double difa,
-                                    const double tzero,
-                                    const double tofmin = 0.);
-
-MANTID_KERNEL_DLL double calcTofMax(const double difc, const double difa,
-                                    const double tzero, const double tofmax);
-
-MANTID_KERNEL_DLL std::function<double(double)>
-getTofToDConversionFunc(const double difc, const double difa,
-                        const double tzero);
-
-MANTID_KERNEL_DLL std::function<double(double)>
-getDToTofConversionFunc(const double difc, const double difa,
-                        const double tzero);
-
-} // namespace Diffraction
-} // namespace Kernel
-} // namespace Mantid
diff --git a/Framework/Kernel/inc/MantidKernel/Unit.h b/Framework/Kernel/inc/MantidKernel/Unit.h
index 3e6cee072f9471861147d6f1d5ffed68d2c7bac4..8537b8b815aede803274b7ef2e4f9b5df7a6553f 100644
--- a/Framework/Kernel/inc/MantidKernel/Unit.h
+++ b/Framework/Kernel/inc/MantidKernel/Unit.h
@@ -12,6 +12,7 @@
 #include "MantidKernel/UnitLabel.h"
 #include <utility>
 
+#include <unordered_map>
 #include <vector>
 #ifndef Q_MOC_RUN
 #include <memory>
@@ -22,6 +23,12 @@
 namespace Mantid {
 namespace Kernel {
 
+enum class UnitParams { l2, twoTheta, efixed, delta, difa, difc, tzero };
+
+// list of parameter names and values - some code may relay on map behaviour
+// where [] creates element with value 0 if param name not present
+using UnitParametersMap = std::unordered_map<UnitParams, double>;
+
 /** The base units (abstract) class. All concrete units should inherit from
     this class and provide implementations of the caption(), label(),
     toTOF() and fromTOF() methods. They also need to declare (but NOT define)
@@ -75,87 +82,90 @@ public:
    *  @param ydata ::    Not currently used (ConvertUnits passes an empty
    * vector)
    *  @param _l1 ::       The source-sample distance (in metres)
-   *  @param _l2 ::       The sample-detector distance (in metres)
-   *  @param _twoTheta :: The scattering angle (in radians)
    *  @param _emode ::    The energy mode (0=elastic, 1=direct geometry,
    * 2=indirect geometry)
-   *  @param _efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2)
-   * (in
-   * meV)
-   *  @param _delta ::    Not currently used
+   *  @param params ::  Map containing optional parameters eg
+   *                    The sample-detector distance (in metres)
+   *                    The scattering angle (in radians)
+   *                    Fixed energy: EI (emode=1) or EF (emode=2)(in meV)
+   *                    Delta (not currently used)
    */
   void toTOF(std::vector<double> &xdata, std::vector<double> &ydata,
-             const double &_l1, const double &_l2, const double &_twoTheta,
-             const int &_emode, const double &_efixed, const double &_delta);
+             const double &_l1, const int &_emode,
+             std::initializer_list<std::pair<const UnitParams, double>> params);
+  void toTOF(std::vector<double> &xdata, std::vector<double> &ydata,
+             const double &_l1, const int &_emode,
+             const UnitParametersMap &params);
 
   /** Convert from the concrete unit to time-of-flight. TOF is in microseconds.
    *  @param xvalue ::   A single X-value to convert
    *  @param l1 ::       The source-sample distance (in metres)
-   *  @param l2 ::       The sample-detector distance (in metres)
-   *  @param twoTheta :: The scattering angle (in radians)
    *  @param emode ::    The energy mode (0=elastic, 1=direct geometry,
    * 2=indirect geometry)
-   *  @param efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2) (in
-   * meV)
-   *  @param delta ::    Not currently used
+   *  @param params ::  Map containing optional parameters eg
+   *                    The sample-detector distance (in metres)
+   *                    The scattering angle (in radians)
+   *                    Fixed energy: EI (emode=1) or EF (emode=2)(in meV)
+   *                    Delta (not currently used)
    *  @return the value in TOF units.
    */
   double convertSingleToTOF(const double xvalue, const double &l1,
-                            const double &l2, const double &twoTheta,
-                            const int &emode, const double &efixed,
-                            const double &delta);
+                            const int &emode, const UnitParametersMap &params);
 
   /** Convert from time-of-flight to the concrete unit. TOF is in microseconds.
    *  @param xdata ::    The array of X data to be converted
    *  @param ydata ::    Not currently used (ConvertUnits passes an empty
    * vector)
    *  @param _l1 ::       The source-sample distance (in metres)
-   *  @param _l2 ::       The sample-detector distance (in metres)
-   *  @param _twoTheta :: The scattering angle (in radians)
    *  @param _emode ::    The energy mode (0=elastic, 1=direct geometry,
    * 2=indirect geometry)
-   *  @param _efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2)
-   * (in
-   * meV)
-   *  @param _delta ::    Not currently used
+   *  @param params ::  Map containing optional parameters eg
+   *                    The sample-detector distance (in metres)
+   *                    The scattering angle (in radians)
+   *                    Fixed energy: EI (emode=1) or EF (emode=2)(in meV)
+   *                    Delta (not currently used)
    */
+  void
+  fromTOF(std::vector<double> &xdata, std::vector<double> &ydata,
+          const double &_l1, const int &_emode,
+          std::initializer_list<std::pair<const UnitParams, double>> params);
+
   void fromTOF(std::vector<double> &xdata, std::vector<double> &ydata,
-               const double &_l1, const double &_l2, const double &_twoTheta,
-               const int &_emode, const double &_efixed, const double &_delta);
+               const double &_l1, const int &_emode,
+               const UnitParametersMap &params);
 
   /** Convert from the time-of-flight to the concrete unit. TOF is in
    * microseconds.
    *  @param xvalue ::   A single X-value to convert
    *  @param l1 ::       The source-sample distance (in metres)
-   *  @param l2 ::       The sample-detector distance (in metres)
-   *  @param twoTheta :: The scattering angle (in radians)
    *  @param emode ::    The energy mode (0=elastic, 1=direct geometry,
    * 2=indirect geometry)
-   *  @param efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2) (in
-   * meV)
-   *  @param delta ::    Not currently used
+   *  @param params ::  Map containing optional parameters eg
+   *                    The sample-detector distance (in metres)
+   *                    The scattering angle (in radians)
+   *                    Fixed energy: EI (emode=1) or EF (emode=2)(in meV)
+   *                    Delta (not currently used)
    *  @return the value in these units.
    */
   double convertSingleFromTOF(const double xvalue, const double &l1,
-                              const double &l2, const double &twoTheta,
-                              const int &emode, const double &efixed,
-                              const double &delta);
+                              const int &emode,
+                              const UnitParametersMap &params);
 
   /** Initialize the unit to perform conversion using singleToTof() and
    *singleFromTof()
    *
    *  @param _l1 ::       The source-sample distance (in metres)
-   *  @param _l2 ::       The sample-detector distance (in metres)
-   *  @param _twoTheta :: The scattering angle (in radians)
    *  @param _emode ::    The energy mode (0=elastic, 1=direct geometry,
    *2=indirect geometry)
-   *  @param _efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2)
-   *(in meV)
-   *  @param _delta ::    Not currently used
+   *  @param params ::    map containing other optional parameters:
+   *                      The sample-detector distance (in metres)
+   *                      The scattering angle (in radians)
+   *                      Fixed energy: EI (emode=1) or EF (emode=2) (in meV)
+   *                      Diffractometer constants (DIFA, DIFC, TZERO)
+   *                      Delta: unused
    */
-  void initialize(const double &_l1, const double &_l2, const double &_twoTheta,
-                  const int &_emode, const double &_efixed,
-                  const double &_delta);
+  void initialize(const double &_l1, const int &_emode,
+                  const UnitParametersMap &params);
 
   /** Finalize the initialization. This will be overridden by subclasses as
    * needed. */
@@ -194,21 +204,25 @@ protected:
   void addConversion(std::string to, const double &factor,
                      const double &power = 1.0) const;
 
+  // validate the contents of the unit parameters map. Throw
+  // std::invalid_argument if it's a global error or std::runtime_error if it's
+  // a detector specific error
+  virtual void validateUnitParams(const int emode,
+                                  const UnitParametersMap &params);
+
   /// The unit values have been initialized
   bool initialized;
   /// l1 ::       The source-sample distance (in metres)
   double l1;
-  /// l2 ::       The sample-detector distance (in metres)
-  double l2;
-  /// twoTheta :: The scattering angle (in radians)
-  double twoTheta;
   /// emode ::    The energy mode (0=elastic, 1=direct geometry, 2=indirect
   /// geometry)
   int emode;
+  /// additional parameters
+  /// l2 :: distance from sample to detector (in metres)
+  /// twoTheta :: scattering angle in radians
   /// efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2) (in meV)
-  double efixed;
-  /// delta ::    Not currently used
-  double delta;
+  /// difc :: diffractometer constant DIFC
+  const UnitParametersMap *m_params;
 
 private:
   /// A 'quick conversion' requires the constant by which to multiply the input
@@ -318,6 +332,9 @@ public:
   Wavelength();
 
 protected:
+  void validateUnitParams(const int emode,
+                          const UnitParametersMap &params) override;
+  double efixed;
   double sfpTo;      ///< Extra correction factor in to conversion
   double factorTo;   ///< Constant factor for to conversion
   double sfpFrom;    ///< Extra correction factor in from conversion
@@ -368,10 +385,16 @@ public:
   Energy_inWavenumber();
 
 protected:
+  void validateUnitParams(const int emode,
+                          const UnitParametersMap &params) override;
   double factorTo;   ///< Constant factor for to conversion
   double factorFrom; ///< Constant factor for from conversion
 };
 
+MANTID_KERNEL_DLL double tofToDSpacingFactor(const double l1, const double l2,
+                                             const double twoTheta,
+                                             const double offset);
+
 //=================================================================================================
 /// d-Spacing in Angstrom
 class MANTID_KERNEL_DLL dSpacing : public Unit {
@@ -379,20 +402,27 @@ public:
   const std::string unitID() const override; ///< "dSpacing"
   const std::string caption() const override { return "d-Spacing"; }
   const UnitLabel label() const override;
-
   double singleToTOF(const double x) const override;
   double singleFromTOF(const double tof) const override;
   void init() override;
   Unit *clone() const override;
   double conversionTOFMin() const override;
   double conversionTOFMax() const override;
+  double calcTofMin(const double difc, const double difa, const double tzero,
+                    const double tofmin = 0.);
+  double calcTofMax(const double difc, const double difa, const double tzero,
+                    const double tofmax = 0.);
 
   /// Constructor
   dSpacing();
 
 protected:
-  double factorTo;   ///< Constant factor for to conversion
-  double factorFrom; ///< Constant factor for from conversion
+  void validateUnitParams(const int emode,
+                          const UnitParametersMap &params) override;
+  std::string toDSpacingError;
+  double difa;
+  double difc;
+  double tzero;
 };
 
 //=================================================================================================
@@ -416,6 +446,9 @@ public:
   dSpacingPerpendicular();
 
 protected:
+  void validateUnitParams(const int emode,
+                          const UnitParametersMap &params) override;
+  double twoTheta;
   double factorTo;   ///< Constant factor for to conversion
   double sfpTo;      ///< Extra correction factor in to conversion
   double factorFrom; ///< Constant factor for from conversion
@@ -440,13 +473,14 @@ public:
   MomentumTransfer();
 
 protected:
-  double factorTo;   ///< Constant factor for to conversion
-  double factorFrom; ///< Constant factor for from conversion
+  void validateUnitParams(const int emode,
+                          const UnitParametersMap &params) override;
+  double difc;
 };
 
 //=================================================================================================
 /// Momentum transfer squared in Angstrom^-2
-class MANTID_KERNEL_DLL QSquared : public Unit {
+class MANTID_KERNEL_DLL QSquared : public MomentumTransfer {
 public:
   const std::string unitID() const override; ///< "QSquared"
   const std::string caption() const override { return "Q2"; }
@@ -454,17 +488,12 @@ public:
 
   double singleToTOF(const double x) const override;
   double singleFromTOF(const double tof) const override;
-  void init() override;
   Unit *clone() const override;
   double conversionTOFMin() const override;
   double conversionTOFMax() const override;
 
   /// Constructor
   QSquared();
-
-protected:
-  double factorTo;   ///< Constant factor for to conversion
-  double factorFrom; ///< Constant factor for from conversion
 };
 
 //=================================================================================================
@@ -487,6 +516,9 @@ public:
   DeltaE();
 
 protected:
+  void validateUnitParams(const int emode,
+                          const UnitParametersMap &params) override;
+  double efixed;
   double factorTo;    ///< Constant factor for to conversion
   double factorFrom;  ///< Constant factor for from conversion
   double t_other;     ///< Energy mode dependent factor in to conversion
@@ -545,6 +577,9 @@ public:
   Momentum();
 
 protected:
+  void validateUnitParams(const int emode,
+                          const UnitParametersMap &params) override;
+  double efixed;
   double sfpTo;      ///< Extra correction factor in to conversion
   double factorTo;   ///< Constant factor for to conversion
   double sfpFrom;    ///< Extra correction factor in from conversion
@@ -569,6 +604,9 @@ public:
 
   /// Constructor
   SpinEchoLength();
+
+private:
+  double efixed;
 };
 
 //=================================================================================================
@@ -588,6 +626,9 @@ public:
 
   /// Constructor
   SpinEchoTime();
+
+private:
+  double efixed;
 };
 
 //=================================================================================================
diff --git a/Framework/Kernel/inc/MantidKernel/UnitConversion.h b/Framework/Kernel/inc/MantidKernel/UnitConversion.h
index 327c4f8bdb53af2dfb6fe7cf83c5f42fefa18b13..dc11da623d116314b49a73370ef9a4668641eab9 100644
--- a/Framework/Kernel/inc/MantidKernel/UnitConversion.h
+++ b/Framework/Kernel/inc/MantidKernel/UnitConversion.h
@@ -8,6 +8,7 @@
 
 #include "MantidKernel/DeltaEMode.h"
 #include "MantidKernel/DllConfig.h"
+#include "MantidKernel/Unit.h"
 #include <string>
 
 namespace Mantid {
@@ -27,10 +28,14 @@ public:
                     const double srcValue, const double l1, const double l2,
                     const double theta, const DeltaEMode::Type emode,
                     const double efixed);
+  static double run(const std::string &src, const std::string &dest,
+                    const double srcValue, const double l1,
+                    const DeltaEMode::Type emode,
+                    const UnitParametersMap &params = {});
   /// Convert a single value between the given units
   static double run(Unit &srcUnit, Unit &destUnit, const double srcValue,
-                    const double l1, const double l2, const double theta,
-                    const DeltaEMode::Type emode, const double efixed);
+                    const double l1, const DeltaEMode::Type emode,
+                    const UnitParametersMap &params = {});
 
   /// Convert to ElasticQ from Energy
   static double convertToElasticQ(const double theta, const double efixed);
@@ -42,9 +47,8 @@ private:
   /// Convert through TOF
   static double convertViaTOF(Unit &srcUnit, Unit &destUnit,
                               const double srcValue, const double l1,
-                              const double l2, const double theta,
                               const DeltaEMode::Type emode,
-                              const double efixed);
+                              const UnitParametersMap &params);
 };
 
 } // namespace Kernel
diff --git a/Framework/Kernel/src/Diffraction.cpp b/Framework/Kernel/src/Diffraction.cpp
deleted file mode 100644
index 1c9e4d64d88f5ece2e7ed09f879887d8db9bf1cd..0000000000000000000000000000000000000000
--- a/Framework/Kernel/src/Diffraction.cpp
+++ /dev/null
@@ -1,173 +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 & CSNS, Institute of High Energy Physics, CAS
-// SPDX - License - Identifier: GPL - 3.0 +
-#include "MantidKernel/Diffraction.h"
-#include <algorithm>
-#include <cmath>
-
-namespace Mantid {
-namespace Kernel {
-namespace Diffraction {
-
-double calcTofMin(const double difc, const double difa, const double tzero,
-                  const double tofmin) {
-  if (difa == 0.) {
-    if (tzero != 0.) {
-      // check for negative d-spacing
-      return std::max<double>(tzero, tofmin);
-    }
-  } else if (difa > 0.) {
-    // check for imaginary part in quadratic equation
-    return std::max<double>(tzero - .25 * difc * difc / difa, tofmin);
-  }
-
-  // everything else is fine so just return supplied tofmin
-  return tofmin;
-}
-
-/**
- * Returns the maximum TOF that can be used or tofmax. Whichever is smaller. In
- * the case when this is a negative number, just return 0.
- */
-double calcTofMax(const double difc, const double difa, const double tzero,
-                  const double tofmax) {
-  if (difa < 0.) {
-    // check for imaginary part in quadratic equation
-    if (tzero > 0.) {
-      // rather than calling abs multiply difa by -1
-      return std::min<double>(tzero + .25 * difc * difc / difa, tofmax);
-    } else {
-      return 0.;
-    }
-  }
-
-  // everything else is fine so just return supplied tofmax
-  return tofmax;
-}
-
-// ----------------------------------------------------------------------------
-// convert from d-spacing to time-of-flight
-namespace { // anonymous namespace
-/// Applies the equation d=(TOF-tzero)/difc
-struct tof_to_d_difc_only {
-  explicit tof_to_d_difc_only(const double difc) : factor(1. / difc) {}
-
-  double operator()(const double tof) const { return factor * tof; }
-
-  /// 1./difc
-  double factor;
-};
-
-/// Applies the equation d=(TOF-tzero)/difc
-struct tof_to_d_difc_and_tzero {
-  explicit tof_to_d_difc_and_tzero(const double difc, const double tzero)
-      : factor(1. / difc), offset(-1. * tzero / difc) {}
-
-  double operator()(const double tof) const { return factor * tof + offset; }
-
-  /// 1./difc
-  double factor;
-  /// -tzero/difc
-  double offset;
-};
-
-struct tof_to_d {
-  explicit tof_to_d(const double difc, const double difa, const double tzero) {
-    factor1 = -0.5 * difc / difa;
-    factor2 = 1. / difa;
-    factor3 = (factor1 * factor1) - (tzero / difa);
-  }
-
-  double operator()(const double tof) const {
-    double second = std::sqrt((tof * factor2) + factor3);
-    if (second < factor1)
-      return factor1 - second;
-    else {
-      return factor1 + second;
-    }
-  }
-
-  /// -0.5*difc/difa
-  double factor1;
-  /// 1/difa
-  double factor2;
-  /// (0.5*difc/difa)^2 - (tzero/difa)
-  double factor3;
-};
-} // anonymous namespace
-
-std::function<double(double)> getTofToDConversionFunc(const double difc,
-                                                      const double difa,
-                                                      const double tzero) {
-  if (difa == 0.) {
-    if (tzero == 0.) {
-      return tof_to_d_difc_only(difc);
-    } else {
-      return tof_to_d_difc_and_tzero(difc, tzero);
-    }
-  } else { // difa != 0.
-    return tof_to_d(difc, difa, tzero);
-  }
-}
-
-// ----------------------------------------------------------------------------
-// convert from d-spacing to time-of-flight
-namespace { // anonymous namespace
-struct d_to_tof_difc_only {
-  explicit d_to_tof_difc_only(const double difc) { this->difc = difc; }
-
-  double operator()(const double dspacing) const { return difc * dspacing; }
-
-  double difc;
-};
-
-struct d_to_tof_difc_and_tzero {
-  explicit d_to_tof_difc_and_tzero(const double difc, const double tzero) {
-    this->difc = difc;
-    this->tzero = tzero;
-  }
-
-  double operator()(const double dspacing) const {
-    return difc * dspacing + tzero;
-  }
-
-  double difc;
-  double tzero;
-};
-
-struct d_to_tof {
-  explicit d_to_tof(const double difc, const double difa, const double tzero) {
-    this->difc = difc;
-    this->difa = difa;
-    this->tzero = tzero;
-  }
-
-  double operator()(const double dspacing) const {
-    return difc * dspacing + difa * dspacing * dspacing + tzero;
-  }
-
-  double difc;
-  double difa;
-  double tzero;
-};
-} // anonymous namespace
-
-std::function<double(double)> getDToTofConversionFunc(const double difc,
-                                                      const double difa,
-                                                      const double tzero) {
-  if (difa == 0.) {
-    if (tzero == 0.)
-      return d_to_tof_difc_only(difc);
-    else
-      return d_to_tof_difc_and_tzero(difc, tzero);
-  } else {
-    return d_to_tof(difc, difa, tzero);
-  }
-}
-
-} // namespace Diffraction
-} // namespace Kernel
-} // namespace Mantid
diff --git a/Framework/Kernel/src/Unit.cpp b/Framework/Kernel/src/Unit.cpp
index b74bedb91b19f2fd20cfc04efc09c58d8ed36e5f..5c831c7b9b1bf4a4f284c06bc472f4f4614b1e51 100644
--- a/Framework/Kernel/src/Unit.cpp
+++ b/Framework/Kernel/src/Unit.cpp
@@ -8,21 +8,41 @@
 // Includes
 //----------------------------------------------------------------------
 #include "MantidKernel/Unit.h"
+#include "MantidKernel/Logger.h"
 #include "MantidKernel/PhysicalConstants.h"
 #include "MantidKernel/UnitFactory.h"
 #include "MantidKernel/UnitLabelTypes.h"
 #include <cfloat>
+#include <sstream>
 
 namespace Mantid {
 namespace Kernel {
 
+namespace {
+// static logger object
+Logger g_log("Unit");
+
+bool ParamPresent(const UnitParametersMap &params, UnitParams param) {
+  return params.find(param) != params.end();
+}
+
+bool ParamPresentAndSet(const UnitParametersMap *params, UnitParams param,
+                        double &var) {
+  auto it = params->find(param);
+  if (it != params->end()) {
+    var = it->second;
+    return true;
+  } else {
+    return false;
+  }
+}
+} // namespace
+
 /**
  * Default constructor
  * Gives the unit an empty UnitLabel
  */
-Unit::Unit()
-    : initialized(false), l1(0), l2(0), twoTheta(0), emode(0), efixed(0),
-      delta(0) {}
+Unit::Unit() : initialized(false), l1(0), emode(0) {}
 
 bool Unit::operator==(const Unit &u) const { return unitID() == u.unitID(); }
 
@@ -108,36 +128,43 @@ void Unit::addConversion(std::string to, const double &factor,
  *singleFromTof()
  *
  *  @param _l1 ::       The source-sample distance (in metres)
- *  @param _l2 ::       The sample-detector distance (in metres)
- *  @param _twoTheta :: The scattering angle (in radians)
  *  @param _emode ::    The energy mode (0=elastic, 1=direct geometry,
  *2=indirect geometry)
- *  @param _efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2) (in
- *meV)
- *  @param _delta ::    Not currently used
+ *  @param params ::  Map containing optional parameters eg
+ *                    The sample-detector distance (in metres)
+ *                    The scattering angle (in radians)
+ *                    Fixed energy: EI (emode=1) or EF (emode=2)(in meV)
+ *                    Delta (not currently used)
  */
-void Unit::initialize(const double &_l1, const double &_l2,
-                      const double &_twoTheta, const int &_emode,
-                      const double &_efixed, const double &_delta) {
+void Unit::initialize(const double &_l1, const int &_emode,
+                      const UnitParametersMap &params) {
   l1 = _l1;
-  l2 = _l2;
-  twoTheta = _twoTheta;
+  validateUnitParams(_emode, params);
   emode = _emode;
-  efixed = _efixed;
-  delta = _delta;
+  m_params = &params;
   initialized = true;
   this->init();
 }
 
+void Unit::validateUnitParams(const int, const UnitParametersMap &) {}
+
 //---------------------------------------------------------------------------------------
 /** Perform the conversion to TOF on a vector of data
  */
+
+void Unit::toTOF(
+    std::vector<double> &xdata, std::vector<double> &ydata, const double &_l1,
+    const int &_emode,
+    std::initializer_list<std::pair<const UnitParams, double>> params) {
+  UnitParametersMap paramsMap(params);
+  toTOF(xdata, ydata, _l1, _emode, paramsMap);
+}
+
 void Unit::toTOF(std::vector<double> &xdata, std::vector<double> &ydata,
-                 const double &_l1, const double &_l2, const double &_twoTheta,
-                 const int &_emode, const double &_efixed,
-                 const double &_delta) {
+                 const double &_l1, const int &_emode,
+                 const UnitParametersMap &params) {
   UNUSED_ARG(ydata);
-  this->initialize(_l1, _l2, _twoTheta, _emode, _efixed, _delta);
+  this->initialize(_l1, _emode, params);
   size_t numX = xdata.size();
   for (size_t i = 0; i < numX; i++)
     xdata[i] = this->singleToTOF(xdata[i]);
@@ -146,29 +173,32 @@ void Unit::toTOF(std::vector<double> &xdata, std::vector<double> &ydata,
 /** Convert a single value to TOF
 @param xvalue
 @param l1
-@param l2
-@param twoTheta
 @param emode
-@param efixed
-@param delta
+@param params (eg efixed or delta)
 */
 double Unit::convertSingleToTOF(const double xvalue, const double &l1,
-                                const double &l2, const double &twoTheta,
-                                const int &emode, const double &efixed,
-                                const double &delta) {
-  this->initialize(l1, l2, twoTheta, emode, efixed, delta);
+                                const int &emode,
+                                const UnitParametersMap &params) {
+  this->initialize(l1, emode, params);
   return this->singleToTOF(xvalue);
 }
 
 //---------------------------------------------------------------------------------------
 /** Perform the conversion to TOF on a vector of data
  */
+void Unit::fromTOF(
+    std::vector<double> &xdata, std::vector<double> &ydata, const double &_l1,
+    const int &_emode,
+    std::initializer_list<std::pair<const UnitParams, double>> params) {
+  UnitParametersMap paramsMap(params);
+  fromTOF(xdata, ydata, _l1, _emode, paramsMap);
+}
+
 void Unit::fromTOF(std::vector<double> &xdata, std::vector<double> &ydata,
-                   const double &_l1, const double &_l2,
-                   const double &_twoTheta, const int &_emode,
-                   const double &_efixed, const double &_delta) {
+                   const double &_l1, const int &_emode,
+                   const UnitParametersMap &params) {
   UNUSED_ARG(ydata);
-  this->initialize(_l1, _l2, _twoTheta, _emode, _efixed, _delta);
+  this->initialize(_l1, _emode, params);
   size_t numX = xdata.size();
   for (size_t i = 0; i < numX; i++)
     xdata[i] = this->singleFromTOF(xdata[i]);
@@ -177,17 +207,13 @@ void Unit::fromTOF(std::vector<double> &xdata, std::vector<double> &ydata,
 /** Convert a single value from TOF
 @param xvalue
 @param l1
-@param l2
-@param twoTheta
 @param emode
-@param efixed
-@param delta
+@param params (eg efixed or delta)
 */
 double Unit::convertSingleFromTOF(const double xvalue, const double &l1,
-                                  const double &l2, const double &twoTheta,
-                                  const int &emode, const double &efixed,
-                                  const double &delta) {
-  this->initialize(l1, l2, twoTheta, emode, efixed, delta);
+                                  const int &emode,
+                                  const UnitParametersMap &params) {
+  this->initialize(l1, emode, params);
   return this->singleFromTOF(xvalue);
 }
 
@@ -302,7 +328,7 @@ double TOF::conversionTOFMax() const { return DBL_MAX; }
 DECLARE_UNIT(Wavelength)
 
 Wavelength::Wavelength()
-    : Unit(), sfpTo(DBL_MIN), factorTo(DBL_MIN), sfpFrom(DBL_MIN),
+    : Unit(), efixed(0.), sfpTo(DBL_MIN), factorTo(DBL_MIN), sfpFrom(DBL_MIN),
       factorFrom(DBL_MIN), do_sfpFrom(false) {
   const double AngstromsSquared = 1e20;
   const double factor =
@@ -316,13 +342,31 @@ Wavelength::Wavelength()
 
 const UnitLabel Wavelength::label() const { return Symbol::Angstrom; }
 
+void Wavelength::validateUnitParams(const int emode,
+                                    const UnitParametersMap &params) {
+  if (!ParamPresent(params, UnitParams::l2)) {
+    throw std::runtime_error("An l2 value must be supplied in the extra "
+                             "parameters when initialising " +
+                             this->unitID() + " for conversion via TOF");
+  }
+  if ((emode != 0) && (!ParamPresent(params, UnitParams::efixed))) {
+    throw std::runtime_error("An efixed value must be supplied in the extra "
+                             "parameters when initialising " +
+                             this->unitID() + " for conversion via TOF");
+  }
+}
+
 void Wavelength::init() {
   // ------------ Factors to convert TO TOF ---------------------
+  double l2 = 0.0;
   double ltot = 0.0;
   double TOFisinMicroseconds = 1e6;
   double toAngstroms = 1e10;
   sfpTo = 0.0;
 
+  ParamPresentAndSet(m_params, UnitParams::efixed, efixed);
+  ParamPresentAndSet(m_params, UnitParams::l2, l2);
+
   if (emode == 1) {
     ltot = l2;
     sfpTo =
@@ -432,6 +476,8 @@ Energy::Energy() : Unit(), factorTo(DBL_MIN), factorFrom(DBL_MIN) {
 }
 
 void Energy::init() {
+  double l2 = 0.0;
+  ParamPresentAndSet(m_params, UnitParams::l2, l2);
   {
     const double TOFinMicroseconds = 1e6;
     factorTo =
@@ -490,7 +536,18 @@ Energy_inWavenumber::Energy_inWavenumber()
   addConversion("Momentum", 2 * M_PI / factor, 0.5);
 }
 
+void Energy_inWavenumber::validateUnitParams(const int,
+                                             const UnitParametersMap &params) {
+  if (!ParamPresent(params, UnitParams::l2)) {
+    throw std::runtime_error("An l2 value must be supplied in the extra "
+                             "parameters when initialising " +
+                             this->unitID() + " for conversion via TOF");
+  }
+}
+
 void Energy_inWavenumber::init() {
+  double l2 = 0.0;
+  ParamPresentAndSet(m_params, UnitParams::l2, l2);
   {
     const double TOFinMicroseconds = 1e6;
     factorTo = sqrt(PhysicalConstants::NeutronMass *
@@ -541,39 +598,188 @@ Unit *Energy_inWavenumber::clone() const {
  *
  * Conversion uses Bragg's Law: 2d sin(theta) = n * lambda
  */
-DECLARE_UNIT(dSpacing)
 
-const UnitLabel dSpacing::label() const { return Symbol::Angstrom; }
+const double CONSTANT = (PhysicalConstants::h * 1e10) /
+                        (2.0 * PhysicalConstants::NeutronMass * 1e6);
+
+/**
+ * Calculate and return conversion factor from tof to d-spacing.
+ * @param l1
+ * @param l2
+ * @param twoTheta scattering angle
+ * @param offset
+ * @return
+ */
+double tofToDSpacingFactor(const double l1, const double l2,
+                           const double twoTheta, const double offset) {
+  if (offset <=
+      -1.) // not physically possible, means result is negative d-spacing
+  {
+    std::stringstream msg;
+    msg << "Encountered offset of " << offset
+        << " which converts data to negative d-spacing\n";
+    throw std::logic_error(msg.str());
+  }
+
+  auto sinTheta = std::sin(twoTheta / 2);
+
+  const double numerator = (1.0 + offset);
+  sinTheta *= (l1 + l2);
+
+  return (numerator * CONSTANT) / sinTheta;
+}
+
+DECLARE_UNIT(dSpacing)
 
-dSpacing::dSpacing() : Unit(), factorTo(DBL_MIN), factorFrom(DBL_MIN) {
+dSpacing::dSpacing()
+    : Unit(), toDSpacingError(""), difa(0), difc(DBL_MIN), tzero(0) {
   const double factor = 2.0 * M_PI;
   addConversion("MomentumTransfer", factor, -1.0);
   addConversion("QSquared", (factor * factor), -2.0);
 }
 
+const UnitLabel dSpacing::label() const { return Symbol::Angstrom; }
+
+Unit *dSpacing::clone() const { return new dSpacing(*this); }
+
+void dSpacing::validateUnitParams(const int, const UnitParametersMap &params) {
+  double difc = 0.;
+  if (!ParamPresentAndSet(&params, UnitParams::difc, difc)) {
+    if (!ParamPresent(params, UnitParams::twoTheta) ||
+        (!ParamPresent(params, UnitParams::l2)))
+      throw std::runtime_error("A difc value or L2/two theta must be supplied "
+                               "in the extra parameters when initialising " +
+                               this->unitID() + " for conversion via TOF");
+  } else {
+    // check validations only applicable to fromTOF
+    toDSpacingError = "";
+    double difa = 0.;
+    ParamPresentAndSet(&params, UnitParams::difa, difa);
+    if ((difa == 0) && (difc == 0)) {
+      toDSpacingError = "Cannot convert to d spacing with DIFA=0 and DIFC=0";
+    };
+    // singleFromTOF currently assuming difc not negative
+    if (difc < 0.) {
+      toDSpacingError =
+          "A positive difc value must be supplied in the extra parameters when "
+          "initialising " +
+          this->unitID() + " for conversion via TOF";
+    }
+  }
+}
+
 void dSpacing::init() {
   // First the crux of the conversion
-  factorTo =
-      (2.0 * PhysicalConstants::NeutronMass * sin(twoTheta / 2.0) * (l1 + l2)) /
-      PhysicalConstants::h;
+  difa = 0.;
+  difc = 0.;
+  tzero = 0.;
+  ParamPresentAndSet(m_params, UnitParams::difa, difa);
+  ParamPresentAndSet(m_params, UnitParams::tzero, tzero);
+
+  if (!ParamPresentAndSet(m_params, UnitParams::difc, difc)) {
+    // also support inputs as L2, two theta
+    double l2;
+    if (ParamPresentAndSet(m_params, UnitParams::l2, l2)) {
+      double twoTheta;
+      if (ParamPresentAndSet(m_params, UnitParams::twoTheta, twoTheta)) {
+        if (difa != 0.) {
+          g_log.warning("Supplied difa ignored");
+          difa = 0.;
+        }
+        difc = 1. / tofToDSpacingFactor(l1, l2, twoTheta, 0.);
+        if (tzero != 0.) {
+          g_log.warning("Supplied tzero ignored");
+          tzero = 0.;
+        }
+      }
+    }
+  }
+}
 
-  // Now adjustments for the scale of units used
-  const double TOFisinMicroseconds = 1e6;
-  const double toAngstroms = 1e10;
-  factorTo *= TOFisinMicroseconds / toAngstroms;
-  factorFrom = factorTo;
-  if (factorFrom == 0.0)
-    factorFrom = DBL_MIN; // Protect against divide by zero
+double dSpacing::singleToTOF(const double x) const {
+  if (!isInitialized())
+    throw std::runtime_error("dSpacingBase::singleToTOF called before object "
+                             "has been initialized.");
+  return difa * x * x + difc * x + tzero;
 }
 
-double dSpacing::singleToTOF(const double x) const { return x * factorTo; }
 double dSpacing::singleFromTOF(const double tof) const {
-  return tof / factorFrom;
+  if (!isInitialized())
+    throw std::runtime_error("dSpacingBase::singleFromTOF called before object "
+                             "has been initialized.");
+  if (!toDSpacingError.empty())
+    throw std::runtime_error(toDSpacingError);
+  // handle special cases first...
+  if (tof == tzero) {
+    if (difa != 0)
+      return -difc / difa;
+  }
+  if ((difc * difc - 4 * difa * (tzero - tof)) < 0) {
+    throw std::runtime_error(
+        "Cannot convert to d spacing. Quadratic doesn't have real roots");
+  }
+  if ((difa > 0) && ((tzero - tof) > 0)) {
+    throw std::runtime_error("Cannot convert to d spacing. Quadratic doesn't "
+                             "have a positive root");
+  }
+  // and then solve quadratic using Muller formula
+  double sqrtTerm;
+  if (difa == 0) {
+    // avoid costly sqrt even though formula reduces to this
+    sqrtTerm = difc;
+  } else {
+    sqrtTerm = sqrt(difc * difc - 4 * difa * (tzero - tof));
+  }
+  if (sqrtTerm < difc)
+    return (tof - tzero) / (0.5 * (difc - sqrtTerm));
+  else
+    return (tof - tzero) / (0.5 * (difc + sqrtTerm));
+}
+double dSpacing::conversionTOFMin() const {
+  // quadratic only has a min if difa is positive
+  if (difa > 0) {
+    // min of the quadratic is at d=-difc/(2*difa)
+    return std::max(0., tzero - difc * difc / (4 * difa));
+  } else {
+    // no min so just pick value closest to zero that works
+    double TOFmin = singleToTOF(0.);
+    if (TOFmin < std::numeric_limits<double>::min()) {
+      TOFmin = 0.;
+    }
+    return TOFmin;
+  }
+}
+double dSpacing::conversionTOFMax() const {
+  // quadratic only has a max if difa is negative
+  if (difa < 0) {
+    return std::min(DBL_MAX, tzero - difc * difc / (4 * difa));
+  } else {
+    // no max so just pick value closest to DBL_MAX that works
+    double TOFmax = singleToTOF(DBL_MAX);
+    if (std::isinf(TOFmax)) {
+      TOFmax = DBL_MAX;
+    }
+    return TOFmax;
+  }
 }
-double dSpacing::conversionTOFMin() const { return 0; }
-double dSpacing::conversionTOFMax() const { return DBL_MAX / factorTo; }
 
-Unit *dSpacing::clone() const { return new dSpacing(*this); }
+double dSpacing::calcTofMin(const double difc, const double difa,
+                            const double tzero, const double tofmin) {
+  Kernel::UnitParametersMap params{{Kernel::UnitParams::difa, difa},
+                                   {Kernel::UnitParams::difc, difc},
+                                   {Kernel::UnitParams::tzero, tzero}};
+  initialize(-1., 0, params);
+  return std::max(conversionTOFMin(), tofmin);
+}
+
+double dSpacing::calcTofMax(const double difc, const double difa,
+                            const double tzero, const double tofmax) {
+  Kernel::UnitParametersMap params{{Kernel::UnitParams::difa, difa},
+                                   {Kernel::UnitParams::difc, difc},
+                                   {Kernel::UnitParams::tzero, tzero}};
+  initialize(-1, 0, params);
+  return std::min(conversionTOFMax(), tofmax);
+}
 
 // ==================================================================================================
 /* D-SPACING Perpendicular
@@ -590,7 +796,26 @@ const UnitLabel dSpacingPerpendicular::label() const {
 dSpacingPerpendicular::dSpacingPerpendicular()
     : Unit(), factorTo(DBL_MIN), factorFrom(DBL_MIN) {}
 
+void dSpacingPerpendicular::validateUnitParams(
+    const int, const UnitParametersMap &params) {
+  if (!ParamPresent(params, UnitParams::l2)) {
+    throw std::runtime_error(
+        "A l2 value must be supplied in the extra parameters when "
+        "initialising " +
+        this->unitID() + " for conversion via TOF");
+  }
+  if (!ParamPresent(params, UnitParams::twoTheta)) {
+    throw std::runtime_error(
+        "A two theta value must be supplied in the extra parameters when "
+        "initialising " +
+        this->unitID() + " for conversion via TOF");
+  }
+}
+
 void dSpacingPerpendicular::init() {
+  double l2 = 0.0;
+  ParamPresentAndSet(m_params, UnitParams::l2, l2);
+  ParamPresentAndSet(m_params, UnitParams::twoTheta, twoTheta);
   factorTo =
       (PhysicalConstants::NeutronMass * (l1 + l2)) / PhysicalConstants::h;
 
@@ -642,47 +867,50 @@ const UnitLabel MomentumTransfer::label() const {
   return Symbol::InverseAngstrom;
 }
 
-MomentumTransfer::MomentumTransfer()
-    : Unit(), factorTo(DBL_MIN), factorFrom(DBL_MIN) {
+MomentumTransfer::MomentumTransfer() : Unit() {
   addConversion("QSquared", 1.0, 2.0);
   const double factor = 2.0 * M_PI;
   addConversion("dSpacing", factor, -1.0);
 }
 
+void MomentumTransfer::validateUnitParams(const int,
+                                          const UnitParametersMap &params) {
+  double difc = 0.;
+  if (!ParamPresentAndSet(&params, UnitParams::difc, difc)) {
+    if (!ParamPresent(params, UnitParams::twoTheta) ||
+        (!ParamPresent(params, UnitParams::l2)))
+      throw std::runtime_error("A difc value or L2/two theta must be supplied "
+                               "in the extra parameters when initialising " +
+                               this->unitID() + " for conversion via TOF");
+  };
+}
+
 void MomentumTransfer::init() {
   // First the crux of the conversion
-  factorTo = (4.0 * M_PI * PhysicalConstants::NeutronMass * (l1 + l2) *
-              sin(twoTheta / 2.0)) /
-             PhysicalConstants::h;
-  // Now adjustments for the scale of units used
-  const double TOFisinMicroseconds = 1e6;
-  const double toAngstroms = 1e10;
-  factorTo *= TOFisinMicroseconds / toAngstroms;
-  // First the crux of the conversion
-  factorFrom = (4.0 * M_PI * PhysicalConstants::NeutronMass * (l1 + l2) *
-                sin(twoTheta / 2.0)) /
-               PhysicalConstants::h;
-
-  // Now adjustments for the scale of units used
-  factorFrom *= TOFisinMicroseconds / toAngstroms;
+  difc = 0.;
+
+  if (!ParamPresentAndSet(m_params, UnitParams::difc, difc)) {
+    // also support inputs as L2, two theta
+    double l2;
+    if (ParamPresentAndSet(m_params, UnitParams::l2, l2)) {
+      double twoTheta;
+      if (ParamPresentAndSet(m_params, UnitParams::twoTheta, twoTheta)) {
+        difc = 1. / tofToDSpacingFactor(l1, l2, twoTheta, 0.);
+      }
+    }
+  }
 }
 
 double MomentumTransfer::singleToTOF(const double x) const {
-  double temp = x;
-  if (temp == 0.0)
-    temp = DBL_MIN; // Protect against divide by zero
-  return factorTo / temp;
+  return 2. * M_PI * difc / x;
 }
 //
 double MomentumTransfer::singleFromTOF(const double tof) const {
-  double temp = tof;
-  if (temp == 0.0)
-    temp = DBL_MIN; // Protect against divide by zero
-  return factorFrom / temp;
+  return 2. * M_PI * difc / tof;
 }
 
 double MomentumTransfer::conversionTOFMin() const {
-  return factorFrom / DBL_MAX;
+  return 2. * M_PI * difc / DBL_MAX;
 }
 double MomentumTransfer::conversionTOFMax() const { return DBL_MAX; }
 
@@ -696,55 +924,27 @@ DECLARE_UNIT(QSquared)
 
 const UnitLabel QSquared::label() const { return Symbol::InverseAngstromSq; }
 
-QSquared::QSquared() : Unit(), factorTo(DBL_MIN), factorFrom(DBL_MIN) {
+QSquared::QSquared() : MomentumTransfer() {
   addConversion("MomentumTransfer", 1.0, 0.5);
   const double factor = 2.0 * M_PI;
   addConversion("dSpacing", factor, -0.5);
 }
 
-void QSquared::init() {
-  // First the crux of the conversion
-  factorTo = (4.0 * M_PI * PhysicalConstants::NeutronMass * (l1 + l2) *
-              sin(twoTheta / 2.0)) /
-             PhysicalConstants::h;
-  // Now adjustments for the scale of units used
-  const double TOFisinMicroseconds = 1e6;
-  const double toAngstroms = 1e10;
-  factorTo *= TOFisinMicroseconds / toAngstroms;
-
-  // First the crux of the conversion
-  factorFrom = (4.0 * M_PI * PhysicalConstants::NeutronMass * (l1 + l2) *
-                sin(twoTheta / 2.0)) /
-               PhysicalConstants::h;
-  // Now adjustments for the scale of units used
-  factorFrom *= TOFisinMicroseconds / toAngstroms;
-  factorFrom = factorFrom * factorFrom;
-}
-
 double QSquared::singleToTOF(const double x) const {
-  double temp = x;
-  if (temp == 0.0)
-    temp = DBL_MIN; // Protect against divide by zero
-  return factorTo / sqrt(temp);
+  return MomentumTransfer::singleToTOF(sqrt(x));
 }
 double QSquared::singleFromTOF(const double tof) const {
-  double temp = tof;
-  if (temp == 0.0)
-    temp = DBL_MIN; // Protect against divide by zero
-  return factorFrom / (temp * temp);
+  return pow(MomentumTransfer::singleFromTOF(tof), 2);
 }
 
 double QSquared::conversionTOFMin() const {
-  if (factorTo > 0)
-    return factorTo / sqrt(DBL_MAX);
-  else
-    return -sqrt(DBL_MAX);
+  return 2 * M_PI * difc / sqrt(DBL_MAX);
 }
 double QSquared::conversionTOFMax() const {
-  if (factorTo > 0)
-    return sqrt(DBL_MAX);
-  else
-    return factorTo / sqrt(DBL_MAX);
+  double tofmax = 2 * M_PI * difc / sqrt(DBL_MIN);
+  if (std::isinf(tofmax))
+    tofmax = DBL_MAX;
+  return tofmax;
 }
 
 Unit *QSquared::clone() const { return new QSquared(*this); }
@@ -764,11 +964,38 @@ DeltaE::DeltaE()
   addConversion("DeltaE_inFrequency", PhysicalConstants::meVtoFrequency, 1.);
 }
 
-void DeltaE::init() {
-  // Efixed must be set to something
-  if (efixed == 0.0)
+void DeltaE::validateUnitParams(const int emode,
+                                const UnitParametersMap &params) {
+  if (emode != 1 && emode != 2) {
     throw std::invalid_argument(
-        "efixed must be set for energy transfer calculation");
+        "emode must be equal to 1 or 2 for energy transfer calculation");
+  }
+  // Efixed must be set to something
+  double efixed;
+  if (!ParamPresentAndSet(&params, UnitParams::efixed, efixed)) {
+    if (emode == 1) { // direct, efixed=ei
+      throw std::invalid_argument(
+          "efixed must be set for energy transfer calculation");
+    } else {
+      throw std::runtime_error(
+          "efixed must be set for energy transfer calculation");
+    }
+  }
+  if (efixed <= 0) {
+    throw std::runtime_error("efixed must be greater than zero");
+  }
+  if (!ParamPresent(params, UnitParams::l2)) {
+    throw std::runtime_error(
+        "A l2 value must be supplied in the extra parameters when "
+        "initialising " +
+        this->unitID() + " for conversion via TOF");
+  }
+}
+
+void DeltaE::init() {
+  double l2 = 0.0;
+  ParamPresentAndSet(m_params, UnitParams::l2, l2);
+  ParamPresentAndSet(m_params, UnitParams::efixed, efixed);
   const double TOFinMicroseconds = 1e6;
   factorTo =
       sqrt(PhysicalConstants::NeutronMass / (2.0 * PhysicalConstants::meV)) *
@@ -781,9 +1008,6 @@ void DeltaE::init() {
     // t_other is t2
     t_other = (factorTo * l2) / sqrt(efixed);
     factorTo *= l1;
-  } else {
-    throw std::invalid_argument(
-        "emode must be equal to 1 or 2 for energy transfer calculation");
   }
 
   //------------ from conversion ------------------
@@ -876,8 +1100,8 @@ Unit *DeltaE::clone() const { return new DeltaE(*this); }
 /* Energy Transfer in units of wavenumber
  * =====================================================================================================
  *
- * This is identical to the above (Energy Transfer in meV) with one division by
- *meVtoWavenumber.
+ * This is identical to the above (Energy Transfer in meV) with one division
+ *by meVtoWavenumber.
  */
 DECLARE_UNIT(DeltaE_inWavenumber)
 
@@ -948,7 +1172,7 @@ DECLARE_UNIT(Momentum)
 const UnitLabel Momentum::label() const { return Symbol::InverseAngstrom; }
 
 Momentum::Momentum()
-    : Unit(), sfpTo(DBL_MIN), factorTo(DBL_MIN), sfpFrom(DBL_MIN),
+    : Unit(), efixed(0.), sfpTo(DBL_MIN), factorTo(DBL_MIN), sfpFrom(DBL_MIN),
       factorFrom(DBL_MIN), do_sfpFrom(false) {
 
   const double AngstromsSquared = 1e20;
@@ -964,13 +1188,31 @@ Momentum::Momentum()
   //
 }
 
+void Momentum::validateUnitParams(const int emode,
+                                  const UnitParametersMap &params) {
+  if (!ParamPresent(params, UnitParams::l2)) {
+    throw std::runtime_error(
+        "An l2 value must be supplied in the extra parameters when "
+        "initialising momentum for conversion via TOF");
+  }
+  if ((emode != 0) && (!ParamPresent(params, UnitParams::efixed))) {
+    throw std::runtime_error(
+        "An efixed value must be supplied in the extra parameters when "
+        "initialising momentum for conversion via TOF");
+  }
+}
+
 void Momentum::init() {
   // ------------ Factors to convert TO TOF ---------------------
+  double l2 = 0.0;
   double ltot = 0.0;
   double TOFisinMicroseconds = 1e6;
   double toAngstroms = 1e10;
   sfpTo = 0.0;
 
+  ParamPresentAndSet(m_params, UnitParams::l2, l2);
+  ParamPresentAndSet(m_params, UnitParams::efixed, efixed);
+
   if (emode == 1) {
     ltot = l2;
     sfpTo =
@@ -1065,6 +1307,7 @@ const UnitLabel SpinEchoLength::label() const { return Symbol::Nanometre; }
 SpinEchoLength::SpinEchoLength() : Wavelength() {}
 
 void SpinEchoLength::init() {
+  ParamPresentAndSet(m_params, UnitParams::efixed, efixed);
   // Efixed must be set to something
   if (efixed == 0.0)
     throw std::invalid_argument(
@@ -1113,9 +1356,10 @@ DECLARE_UNIT(SpinEchoTime)
 
 const UnitLabel SpinEchoTime::label() const { return Symbol::Nanosecond; }
 
-SpinEchoTime::SpinEchoTime() : Wavelength() {}
+SpinEchoTime::SpinEchoTime() : Wavelength(), efixed(0.) {}
 
 void SpinEchoTime::init() {
+  ParamPresentAndSet(m_params, UnitParams::efixed, efixed);
   // Efixed must be set to something
   if (efixed == 0.0)
     throw std::invalid_argument(
diff --git a/Framework/Kernel/src/UnitConversion.cpp b/Framework/Kernel/src/UnitConversion.cpp
index 3f9a4694fabd87f43d0d6748ac09a8dc07cb8314..727fd58efa4934424e2ddec38a0a832747942fe8 100644
--- a/Framework/Kernel/src/UnitConversion.cpp
+++ b/Framework/Kernel/src/UnitConversion.cpp
@@ -32,8 +32,23 @@ double UnitConversion::run(const std::string &src, const std::string &dest,
                            const DeltaEMode::Type emode, const double efixed) {
   Unit_sptr srcUnit = UnitFactory::Instance().create(src);
   Unit_sptr destUnit = UnitFactory::Instance().create(dest);
-  return UnitConversion::run(*srcUnit, *destUnit, srcValue, l1, l2, theta,
-                             emode, efixed);
+  if ((srcUnit->unitID() == "dSpacing") || (destUnit->unitID() == "dSpacing")) {
+    throw std::runtime_error(
+        "This signature is deprecated for d Spacing unit conversions");
+  }
+  UnitParametersMap params{{UnitParams::l2, l2},
+                           {UnitParams::twoTheta, theta},
+                           {UnitParams::efixed, efixed}};
+  return UnitConversion::run(*srcUnit, *destUnit, srcValue, l1, emode, params);
+} // namespace Kernel
+
+double UnitConversion::run(const std::string &src, const std::string &dest,
+                           const double srcValue, const double l1,
+                           const DeltaEMode::Type emode,
+                           const UnitParametersMap &params) {
+  Unit_sptr srcUnit = UnitFactory::Instance().create(src);
+  Unit_sptr destUnit = UnitFactory::Instance().create(dest);
+  return UnitConversion::run(*srcUnit, *destUnit, srcValue, l1, emode, params);
 }
 
 /**
@@ -42,22 +57,22 @@ double UnitConversion::run(const std::string &src, const std::string &dest,
  * @param destUnit :: The destination unit
  * @param srcValue :: The value to convert
  * @param l1 ::       The source-sample distance (in metres)
- * @param l2 ::       The sample-detector distance (in metres)
- * @param theta :: The scattering angle (in radians)
  * @param emode ::    The energy mode enumeration
- * @param efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2) (in
- * meV)
+ * @param params ::  Map containing optional parameters eg
+ *                   The sample-detector distance (in metres)
+ *                   The scattering angle (in radians)
+ *                   Fixed energy: EI (emode=1) or EF (emode=2)(in meV)
+ *                   Delta (not currently used)
  * @return The value converted to the destination unit
  */
 double UnitConversion::run(Unit &srcUnit, Unit &destUnit, const double srcValue,
-                           const double l1, const double l2, const double theta,
-                           const DeltaEMode::Type emode, const double efixed) {
+                           const double l1, const DeltaEMode::Type emode,
+                           const UnitParametersMap &params) {
   double factor(0.0), power(0.0);
   if (srcUnit.quickConversion(destUnit, factor, power)) {
     return convertQuickly(srcValue, factor, power);
   } else {
-    return convertViaTOF(srcUnit, destUnit, srcValue, l1, l2, theta, emode,
-                         efixed);
+    return convertViaTOF(srcUnit, destUnit, srcValue, l1, emode, params);
   }
 }
 
@@ -83,18 +98,18 @@ double UnitConversion::convertQuickly(const double srcValue,
  * @param destUnit :: The destination unit
  * @param srcValue :: The value to convert
  * @param l1 ::       The source-sample distance (in metres)
- * @param l2 ::       The sample-detector distance (in metres)
- * @param theta :: The scattering angle (in radians)
  * @param emode ::    The energy mode enumeration
- * @param efixed ::   Value of fixed energy: EI (emode=1) or EF (emode=2) (in
- * meV)
+ * @param params ::  Map containing optional parameters eg
+ *                   The sample-detector distance (in metres)
+ *                   The scattering angle (in radians)
+ *                   Fixed energy: EI (emode=1) or EF (emode=2)(in meV)
+ *                   Delta (not currently used)
  * @return The value converted to the destination unit
  */
 double UnitConversion::convertViaTOF(Unit &srcUnit, Unit &destUnit,
                                      const double srcValue, const double l1,
-                                     const double l2, const double theta,
                                      const DeltaEMode::Type emode,
-                                     const double efixed) {
+                                     const UnitParametersMap &params) {
   // Translate the emode to the int formulation
   int emodeAsInt(0);
   switch (emode) {
@@ -113,11 +128,9 @@ double UnitConversion::convertViaTOF(Unit &srcUnit, Unit &destUnit,
         std::to_string(emode));
   };
 
-  const double unused(0.0);
-  const double tof = srcUnit.convertSingleToTOF(srcValue, l1, l2, theta,
-                                                emodeAsInt, efixed, unused);
-  return destUnit.convertSingleFromTOF(tof, l1, l2, theta, emodeAsInt, efixed,
-                                       unused);
+  const double tof =
+      srcUnit.convertSingleToTOF(srcValue, l1, emodeAsInt, params);
+  return destUnit.convertSingleFromTOF(tof, l1, emodeAsInt, params);
 }
 
 /**
diff --git a/Framework/Kernel/test/UnitTest.h b/Framework/Kernel/test/UnitTest.h
index a0bf000969f0186294ad8ee7ed8839dc1e87fb69..1adabc83dcad0287836e17ded84486a2fcfed829 100644
--- a/Framework/Kernel/test/UnitTest.h
+++ b/Framework/Kernel/test/UnitTest.h
@@ -100,6 +100,17 @@ std::string convert_units_check_range(const Unit &aUnit,
   return error_mess;
 }
 
+namespace {                // anonymous
+const double DIFC = 2100.; // sensible value
+const double TZERO = 10.;
+// intentionally goofy - reduces tzero by 1
+const double DIFA1 = .25 * DIFC * DIFC;
+// intentionally goofy - reduces tzero by .01
+const double DIFA2 = 25 * DIFC * DIFC;
+// intentionally goofy
+const double DIFA3 = -.25 * DIFC * DIFC;
+} // namespace
+
 class UnitTest : public CxxTest::TestSuite {
 
   class UnitTester : public Unit {
@@ -253,7 +264,11 @@ public:
 
   void test_copy_constructor_on_concrete_type() {
     Units::TOF first;
-    first.initialize(1.0, 1.0, 1.0, 2, 1.0, 1.0);
+    first.initialize(1.0, 2,
+                     {{UnitParams::l2, 1.0},
+                      {UnitParams::twoTheta, 1.0},
+                      {UnitParams::efixed, 1.0},
+                      {UnitParams::delta, 1.0}});
     Units::TOF second(first);
     TS_ASSERT_EQUALS(first.isInitialized(), second.isInitialized());
     TS_ASSERT_EQUALS(first.unitID(), second.unitID())
@@ -264,7 +279,11 @@ public:
 
   void test_copy_assignment_operator_on_concrete_type() {
     Units::TOF first;
-    first.initialize(1.0, 1.0, 1.0, 2, 1.0, 1.0);
+    first.initialize(1.0, 2,
+                     {{UnitParams::l2, 1.0},
+                      {UnitParams::twoTheta, 1.0},
+                      {UnitParams::efixed, 1.0},
+                      {UnitParams::delta, 1.0}});
     Units::TOF second;
     second = first;
     TS_ASSERT_EQUALS(first.isInitialized(), second.isInitialized());
@@ -291,7 +310,7 @@ public:
     std::vector<double> x(20, 9.9), y(20, 8.8);
     std::vector<double> xx = x;
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(tof.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(tof.toTOF(x, y, 1.0, 1, {}))
     // Check vectors are unchanged
     TS_ASSERT(xx == x)
     TS_ASSERT(yy == y)
@@ -301,7 +320,7 @@ public:
     std::vector<double> x(20, 9.9), y(20, 8.8);
     std::vector<double> xx = x;
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(tof.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(tof.fromTOF(x, y, 1.0, 1, {}))
     // Check vectors are unchanged
     TS_ASSERT(xx == x)
     TS_ASSERT(yy == y)
@@ -342,23 +361,28 @@ public:
   void testWavelength_toTOF() {
     std::vector<double> x(1, 1.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(lambda.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(lambda.toTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 1.0}}))
     TS_ASSERT_DELTA(x[0], 2665.4390, 0.0001) //  758.3352
     TS_ASSERT(yy == y)
 
-    TS_ASSERT_DELTA(lambda.convertSingleToTOF(1.5, 1.0, 1.0, 1.0, 1, 1.0, 1.0),
-                    2665.4390, 0.0001);
+    TS_ASSERT_DELTA(
+        lambda.convertSingleToTOF(
+            1.5, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 1.0}}),
+        2665.4390, 0.0001);
   }
 
   void testWavelength_fromTOF() {
     std::vector<double> x(1, 1000.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(lambda.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(lambda.fromTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 1.0}}))
     TS_ASSERT_DELTA(x[0], -5.0865, 0.0001) // 1.979006
     TS_ASSERT(yy == y)
 
     TS_ASSERT_DELTA(
-        lambda.convertSingleFromTOF(1000.5, 1.0, 1.0, 1.0, 1, 1.0, 1.0),
+        lambda.convertSingleFromTOF(
+            1000.5, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 1.0}}),
         -5.0865, 0.0001);
   }
 
@@ -369,8 +393,9 @@ public:
     double input = 1.1;
     double result = factor * std::pow(input, power);
     std::vector<double> x(1, input);
-    lambda.toTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    energy.fromTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    lambda.toTOF(x, x, 99.0, 99,
+                 {{UnitParams::l2, 99.0}, {UnitParams::efixed, 1.0}});
+    energy.fromTOF(x, x, 99.0, 99, {{UnitParams::l2, 99.0}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-10)
 
     TS_ASSERT(lambda.quickConversion(energyk, factor, power))
@@ -378,8 +403,10 @@ public:
     TS_ASSERT_EQUALS(result2 / result,
                      Mantid::PhysicalConstants::meVtoWavenumber)
     std::vector<double> x2(1, input);
-    lambda.toTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    energyk.fromTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    lambda.toTOF(x2, x2, 99.0, 99,
+                 {{UnitParams::l2, 99.0}, {UnitParams::efixed, 99.0}});
+
+    energyk.fromTOF(x2, x2, 99.0, 99, {{UnitParams::l2, 99.0}});
     TS_ASSERT_DELTA(x2[0], result2, 1.0e-10)
   }
 
@@ -417,7 +444,8 @@ public:
   void testEnergy_toTOF() {
     std::vector<double> x(1, 4.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(energy.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(
+        energy.toTOF(x, y, 1.0, 1, {{UnitParams::l2, 1.0}}))
     TS_ASSERT_DELTA(x[0], 2286.271, 0.001)
     TS_ASSERT(yy == y)
   }
@@ -425,7 +453,8 @@ public:
   void testEnergy_fromTOF() {
     std::vector<double> x(1, 4.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(energy.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(
+        energy.fromTOF(x, y, 1.0, 1, {{UnitParams::l2, 1.0}}))
     TS_ASSERT_DELTA(x[0], 1306759.0, 1.0)
     TS_ASSERT(yy == y)
   }
@@ -438,15 +467,16 @@ public:
     double result = factor * std::pow(input, power);
     TS_ASSERT_EQUALS(result / input, Mantid::PhysicalConstants::meVtoWavenumber)
     std::vector<double> x(1, input);
-    energy.toTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    energyk.fromTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    energy.toTOF(x, x, 99.0, 99, {{UnitParams::l2, 99.0}});
+    energyk.fromTOF(x, x, 99.0, 99, {{UnitParams::l2, 99.0}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-12)
 
     TS_ASSERT(energy.quickConversion(lambda, factor, power))
     result = factor * std::pow(input, power);
     std::vector<double> x2(1, input);
-    energy.toTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    lambda.fromTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    energy.toTOF(x2, x2, 99.0, 99, {{UnitParams::l2, 99.0}});
+    lambda.fromTOF(x2, x2, 99.0, 99,
+                   {{UnitParams::l2, 99.0}, {UnitParams::efixed, 99.0}});
     TS_ASSERT_DELTA(x2[0], result, 1.0e-15)
   }
   void testEnergyRange() {
@@ -492,7 +522,8 @@ public:
   void testEnergy_inWavenumber_toTOF() {
     std::vector<double> x(1, 4.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(energyk.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(
+        energyk.toTOF(x, y, 1.0, 1, {{UnitParams::l2, 1.0}}))
     TS_ASSERT_DELTA(x[0], 6492.989, 0.001)
     TS_ASSERT(yy == y)
   }
@@ -500,7 +531,8 @@ public:
   void testEnergy_inWavenumber_fromTOF() {
     std::vector<double> x(1, 4.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(energyk.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(
+        energyk.fromTOF(x, y, 1.0, 1, {{UnitParams::l2, 1.0}}))
     TS_ASSERT_DELTA(x[0], 10539725, 1.0)
     TS_ASSERT(yy == y)
   }
@@ -513,15 +545,16 @@ public:
     double result = factor * std::pow(input, power);
     TS_ASSERT_EQUALS(input / result, Mantid::PhysicalConstants::meVtoWavenumber)
     std::vector<double> x(1, input);
-    energyk.toTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    energy.fromTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    energyk.toTOF(x, x, 99.0, 99, {{UnitParams::l2, 99.0}});
+    energy.fromTOF(x, x, 99.0, 99, {{UnitParams::l2, 99.0}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-14)
 
     TS_ASSERT(energyk.quickConversion(lambda, factor, power))
     result = factor * std::pow(input, power);
     std::vector<double> x2(1, input);
-    energyk.toTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    lambda.fromTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    energyk.toTOF(x2, x2, 99.0, 99, {{UnitParams::l2, 99.0}});
+    lambda.fromTOF(x2, x2, 99.0, 99,
+                   {{UnitParams::l2, 99.0}, {UnitParams::efixed, 99.0}});
     TS_ASSERT_DELTA(x2[0], result, 1.0e-15)
   }
 
@@ -547,19 +580,62 @@ public:
   void testdSpacing_toTOF() {
     std::vector<double> x(1, 1.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(d.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(0.5) *
+                  (1.0 + 1.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    TS_ASSERT_THROWS_NOTHING(d.toTOF(x, y, 1.0, 1, {{UnitParams::difc, difc}}))
+    TS_ASSERT_DELTA(x[0], 484.7537, 0.0001)
+    TS_ASSERT(yy == y)
+  }
+
+  void testdSpacing_toTOFWithL2TwoTheta() {
+    std::vector<double> x(1, 1.0), y(1, 1.0);
+    std::vector<double> yy = y;
+    TS_ASSERT_THROWS_NOTHING(d.toTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::twoTheta, 1.0}}))
     TS_ASSERT_DELTA(x[0], 484.7537, 0.0001)
     TS_ASSERT(yy == y)
   }
 
+  void testdSpacing_toTOFWithDIFATZERO() {
+    std::vector<double> x(1, 2.0), y(1, 1.0);
+    std::vector<double> yy = y;
+    TS_ASSERT_THROWS_NOTHING(d.toTOF(x, y, 1.0, 1,
+                                     {{UnitParams::difc, 3.0},
+                                      {UnitParams::difa, 2.0},
+                                      {UnitParams::tzero, 1.0}}))
+    TS_ASSERT_DELTA(x[0], 6.0 + 8.0 + 1.0, 0.0001)
+    TS_ASSERT(yy == y)
+  }
+
   void testdSpacing_fromTOF() {
     std::vector<double> x(1, 1001.1), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(d.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(0.5) *
+                  (1.0 + 1.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    TS_ASSERT_THROWS_NOTHING(
+        d.fromTOF(x, y, 1.0, 1, {{UnitParams::difc, difc}}))
     TS_ASSERT_DELTA(x[0], 2.065172, 0.000001)
     TS_ASSERT(yy == y)
   }
 
+  void testdSpacing_fromTOFWithDIFATZERO() {
+    std::vector<double> x(1, 2.0), y(1, 1.0);
+    std::vector<double> yy = y;
+    TS_ASSERT_THROWS_NOTHING(d.fromTOF(x, y, 1.0, 1,
+                                       {{UnitParams::difc, 2.0},
+                                        {UnitParams::difa, 3.0},
+                                        {UnitParams::tzero, 1.0}}))
+    TS_ASSERT_DELTA(x[0], 1.0 / 3.0, 0.0001)
+    TS_ASSERT(yy == y)
+    x[0] = 1.0;
+    TS_ASSERT_THROWS_NOTHING(d.fromTOF(x, y, 1.0, 1,
+                                       {{UnitParams::difc, 3.0},
+                                        {UnitParams::difa, -2.0},
+                                        {UnitParams::tzero, 1.0}}))
+    TS_ASSERT_DELTA(x[0], 1.5, 0.0001)
+    TS_ASSERT(yy == y)
+  }
+
   void testdSpacing_quickConversions() {
     // Test it gives the same answer as going 'the long way'
     // To MomentumTransfer
@@ -568,8 +644,10 @@ public:
     double input = 1.1;
     double result = factor * std::pow(input, power);
     std::vector<double> x(1, input);
-    d.toTOF(x, x, 99.0, 99.0, 1.0, 0, 99.0, 99.0);
-    q.fromTOF(x, x, 99.0, 99.0, 1.0, 0, 99.0, 99.0);
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(0.5) *
+                  (99.0 + 99.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    d.toTOF(x, x, 99.0, 0, {{UnitParams::difc, difc}});
+    q.fromTOF(x, x, 99.0, 0, {{UnitParams::difc, difc}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-12)
 
     // To QSquared
@@ -577,13 +655,17 @@ public:
     input = 1.1;
     result = factor * std::pow(input, power);
     x[0] = input;
-    d.toTOF(x, x, 99.0, 99.0, 1.0, 0, 99.0, 99.0);
-    q2.fromTOF(x, x, 99.0, 99.0, 1.0, 0, 99.0, 99.0);
+    d.toTOF(x, x, 99.0, 0, {{UnitParams::difc, difc}});
+    q2.fromTOF(x, x, 99.0, 0, {{UnitParams::difc, difc}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-12)
   }
   void testdSpacingRange() {
     std::vector<double> sample, rezult;
 
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass *
+                  sin(0.5 * M_PI / 180) * (99.0 + 99.0) * 1e-4 /
+                  Mantid::PhysicalConstants::h;
+    d.initialize(99.0, 0, {{UnitParams::difc, difc}});
     std::string err_mess = convert_units_check_range(d, sample, rezult);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
 
@@ -600,6 +682,52 @@ public:
     }
   }
 
+  void test_calcTofMin() {
+    const double TMIN = 300.;
+
+    // just difc
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, 0., 0.), 0.);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, 0., 0., TMIN), TMIN);
+    // difc + tzero
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, 0., TZERO, 0.), TZERO);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, 0., TZERO, TMIN), TMIN);
+
+    // difc + difa + tzero
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA1, 0., 0.), 0.);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA1, 0., TMIN), TMIN);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA1, TZERO, 0.), TZERO - 1.);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA1, TZERO, TMIN), TMIN);
+
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA2, 0., 0.), 0.);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA2, 0., TMIN), TMIN);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA2, TZERO, 0.), TZERO - .01);
+    TS_ASSERT_EQUALS(d.calcTofMin(DIFC, DIFA2, TZERO, TMIN), TMIN);
+  }
+
+  void test_calcTofMax() {
+    const double TMAX = 16666.7;
+    const double TSUPERMAX = std::numeric_limits<double>::max();
+
+    // just difc
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, 0., 0., TMAX), TMAX);
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, 0., 0., TSUPERMAX), TSUPERMAX);
+    // difc + tzero
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, 0., TZERO, TMAX), TMAX);
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, 0., TZERO, TSUPERMAX), TSUPERMAX);
+
+    // difc + difa + tzero
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, DIFA1, 0., TMAX), TMAX);
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, DIFA1, 0., TSUPERMAX), TSUPERMAX);
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, DIFA1, TZERO, TMAX), TMAX);
+    TS_ASSERT_EQUALS(d.calcTofMax(DIFC, DIFA1, TZERO, TSUPERMAX), TSUPERMAX);
+
+    TS_ASSERT_DELTA(d.calcTofMax(DIFC, DIFA3, 0., TMAX), 1., 1E-10);
+    TS_ASSERT_DELTA(d.calcTofMax(DIFC, DIFA3, 0., TSUPERMAX), 1., 1E-10);
+    TS_ASSERT_DELTA(d.calcTofMax(DIFC, DIFA3, TZERO, TMAX), TZERO + 1., 1E-10);
+    TS_ASSERT_DELTA(d.calcTofMax(DIFC, DIFA3, TZERO, TSUPERMAX), TZERO + 1.,
+                    1E-10);
+  }
+
   //----------------------------------------------------------------------
   // d-SpacingPerpebdicular tests
   //----------------------------------------------------------------------
@@ -626,7 +754,8 @@ public:
   void testdSpacingPerpendicular_toTOF() {
     std::vector<double> x(1, 1.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dp.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(dp.toTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::twoTheta, 1.0}}))
     TS_ASSERT_DELTA(x[0], 434.5529, 0.0001)
     TS_ASSERT(yy == y)
   }
@@ -634,7 +763,8 @@ public:
   void testdSpacingPerpendicular_fromTOF() {
     std::vector<double> x(1, 1001.1), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dp.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(dp.fromTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::twoTheta, 1.0}}))
     TS_ASSERT_DELTA(x[0], 2.045075, 0.000001)
     TS_ASSERT(yy == y)
   }
@@ -684,7 +814,9 @@ public:
   void testQTransfer_toTOF() {
     std::vector<double> x(1, 1.1), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(q.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(0.5) *
+                  (1.0 + 1.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    TS_ASSERT_THROWS_NOTHING(q.toTOF(x, y, 1.0, 1, {{UnitParams::difc, difc}}))
     TS_ASSERT_DELTA(x[0], 2768.9067, 0.0001)
     TS_ASSERT(yy == y)
   }
@@ -692,7 +824,10 @@ public:
   void testQTransfer_fromTOF() {
     std::vector<double> x(1, 1.1), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(q.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(0.5) *
+                  (1.0 + 1.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    TS_ASSERT_THROWS_NOTHING(
+        q.fromTOF(x, y, 1.0, 1, {{UnitParams::difc, difc}}))
     TS_ASSERT_DELTA(x[0], 2768.9067, 0.0001)
     TS_ASSERT(yy == y)
   }
@@ -705,8 +840,10 @@ public:
     double input = 1.1;
     double result = factor * std::pow(input, power);
     std::vector<double> x(1, input);
-    q.toTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    q2.fromTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(1.0 / 2) *
+                  (99.0 + 99.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    q.toTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
+    q2.fromTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-30)
 
     // To dSpacing
@@ -714,8 +851,8 @@ public:
     input = 1.1;
     result = factor * std::pow(input, power);
     x[0] = input;
-    q.toTOF(x, x, 99.0, 99.0, 1.0, 99, 99.0, 99.0);
-    d.fromTOF(x, x, 99.0, 99.0, 1.0, 99, 99.0, 99.0);
+    q.toTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
+    d.fromTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-12)
   }
   void testMomentumTransferRange() {
@@ -759,7 +896,9 @@ public:
   void testQ2_toTOF() {
     std::vector<double> x(1, 4.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(q2.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(1.0 / 2) *
+                  (1.0 + 1.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    TS_ASSERT_THROWS_NOTHING(q2.toTOF(x, y, 1.0, 1, {{UnitParams::difc, difc}}))
     TS_ASSERT_DELTA(x[0], 1522.899, 0.001)
     TS_ASSERT(yy == y)
   }
@@ -767,7 +906,10 @@ public:
   void testQ2_fromTOF() {
     std::vector<double> x(1, 200.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(q2.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(1.0 / 2) *
+                  (1.0 + 1.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    TS_ASSERT_THROWS_NOTHING(
+        q2.fromTOF(x, y, 1.0, 1, {{UnitParams::difc, difc}}))
     TS_ASSERT_DELTA(x[0], 231.9220, 0.0001)
     TS_ASSERT(yy == y)
   }
@@ -780,8 +922,10 @@ public:
     double input = 1.1;
     double result = factor * std::pow(input, power);
     std::vector<double> x(1, input);
-    q2.toTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    q.fromTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(1.0 / 2) *
+                  (99.0 + 99.0) * 1e-4 / Mantid::PhysicalConstants::h;
+    q2.toTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
+    q.fromTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-30)
 
     // To dSpacing
@@ -789,16 +933,19 @@ public:
     input = 1.1;
     result = factor * std::pow(input, power);
     x[0] = input;
-    q2.toTOF(x, x, 99.0, 99.0, 1.0, 99, 99.0, 99.0);
-    d.fromTOF(x, x, 99.0, 99.0, 1.0, 99, 99.0, 99.0);
+    q2.toTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
+    d.fromTOF(x, x, 99.0, 99, {{UnitParams::difc, difc}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-15)
   }
   void testQ2Range() {
     std::vector<double> sample, rezult;
 
-    q2.initialize(1.1, 1.1, 99.0, 0, 99.0, 0);
-    std::string err_mess =
-        convert_units_check_range(q2, sample, rezult, -DBL_EPSILON);
+    double difc = 2.0 * Mantid::PhysicalConstants::NeutronMass * sin(1.0 / 2) *
+                  (1.1 + 1.1) * 1e-4 / Mantid::PhysicalConstants::h;
+    q2.initialize(1.1, 0,
+                  {{UnitParams::difc, difc}, {UnitParams::efixed, 99.0}});
+
+    std::string err_mess = convert_units_check_range(q2, sample, rezult);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
 
     for (size_t i = 0; i < sample.size(); i++) {
@@ -838,40 +985,49 @@ public:
   void testDeltaE_toTOF() {
     std::vector<double> x(1, 1.1), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dE.toTOF(x, y, 1.5, 2.5, 0.0, 1, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dE.toTOF(
+        x, y, 1.5, 1, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 5071.066, 0.001)
     TS_ASSERT(yy == y)
 
     x[0] = 1.1;
-    TS_ASSERT_THROWS_NOTHING(dE.toTOF(x, y, 1.5, 2.5, 0.0, 2, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dE.toTOF(
+        x, y, 1.5, 2, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 4376.406, 0.001)
     TS_ASSERT(yy == y)
 
     // emode = 0
-    TS_ASSERT_THROWS(dE.toTOF(x, y, 1.5, 2.5, 0.0, 0, 4.0, 0.0),
-                     const std::invalid_argument &)
+    TS_ASSERT_THROWS(
+        dE.toTOF(x, y, 1.5, 0,
+                 {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}),
+        const std::invalid_argument &)
   }
 
   void testDeltaE_fromTOF() {
     std::vector<double> x(1, 2001.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dE.fromTOF(x, y, 1.5, 2.5, 0.0, 1, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dE.fromTOF(
+        x, y, 1.5, 1, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], -394.5692, 0.0001)
     TS_ASSERT(yy == y)
 
     x[0] = 3001.0;
-    TS_ASSERT_THROWS_NOTHING(dE.fromTOF(x, y, 1.5, 2.5, 0.0, 2, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dE.fromTOF(
+        x, y, 1.5, 2, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 569.8397, 0.0001)
     TS_ASSERT(yy == y)
 
     // emode = 0
-    TS_ASSERT_THROWS(dE.fromTOF(x, y, 1.5, 2.5, 0.0, 0, 4.0, 0.0),
-                     const std::invalid_argument &)
+    TS_ASSERT_THROWS(
+        dE.fromTOF(x, y, 1.5, 0,
+                   {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}),
+        const std::invalid_argument &)
   }
   void testDERange() {
     std::vector<double> sample, rezult;
     // Direct
-    dE.initialize(2001.0, 1.0, 1.5, 1, 10., 0.0);
+    dE.initialize(2001.0, 1,
+                  {{UnitParams::l2, 1.0}, {UnitParams::efixed, 10.0}});
 
     std::string err_mess =
         convert_units_check_range(dE, sample, rezult, DBL_EPSILON);
@@ -891,7 +1047,8 @@ public:
         sample[3], rezult[3], 10 * FLT_EPSILON);
 
     // Indirect
-    dE.initialize(2001.0, 1.0, 1.5, 2, 10., 0.0);
+    dE.initialize(2001.0, 2,
+                  {{UnitParams::l2, 1.0}, {UnitParams::efixed, 10.0}});
 
     err_mess = convert_units_check_range(dE, sample, rezult);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
@@ -936,40 +1093,49 @@ public:
   void testDeltaEk_toTOF() {
     std::vector<double> x(1, 1.1), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dEk.toTOF(x, y, 1.5, 2.5, 0.0, 1, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEk.toTOF(
+        x, y, 1.5, 1, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 4622.5452, 0.01)
     TS_ASSERT(yy == y)
 
     x[0] = 1.1;
-    TS_ASSERT_THROWS_NOTHING(dEk.toTOF(x, y, 1.5, 2.5, 0.0, 2, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEk.toTOF(
+        x, y, 1.5, 2, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 4544.0378, 0.001)
     TS_ASSERT(yy == y)
 
     // emode = 0
-    TS_ASSERT_THROWS(dEk.toTOF(x, y, 1.5, 2.5, 0.0, 0, 4.0, 0.0),
-                     const std::invalid_argument &)
+    TS_ASSERT_THROWS(
+        dEk.toTOF(x, y, 1.5, 0,
+                  {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}),
+        const std::invalid_argument &)
   }
 
   void testDeltaEk_fromTOF() {
     std::vector<double> x(1, 2001.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dEk.fromTOF(x, y, 1.5, 2.5, 0.0, 1, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEk.fromTOF(
+        x, y, 1.5, 1, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], -3182.416, 0.001)
     TS_ASSERT(yy == y)
 
     x[0] = 3001.0;
-    TS_ASSERT_THROWS_NOTHING(dEk.fromTOF(x, y, 1.5, 2.5, 0.0, 2, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEk.fromTOF(
+        x, y, 1.5, 2, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 4596.068, 0.001)
     TS_ASSERT(yy == y)
 
     // emode = 0
-    TS_ASSERT_THROWS(dEk.fromTOF(x, y, 1.5, 2.5, 0.0, 0, 4.0, 0.0),
-                     const std::invalid_argument &)
+    TS_ASSERT_THROWS(
+        dEk.fromTOF(x, y, 1.5, 0,
+                    {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}),
+        const std::invalid_argument &)
   }
   void testDE_kRange() {
     std::vector<double> sample, rezult;
     // Direct
-    dEk.initialize(2001.0, 1.0, 1.5, 1, 10., 0.0);
+    dEk.initialize(2001.0, 1,
+                   {{UnitParams::l2, 1.0}, {UnitParams::efixed, 10.0}});
 
     std::string err_mess = convert_units_check_range(dEk, sample, rezult);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
@@ -988,7 +1154,8 @@ public:
         sample[3], rezult[3], 10 * FLT_EPSILON);
 
     // Indirect
-    dEk.initialize(2001.0, 1.0, 1.5, 2, 10., 0.0);
+    dEk.initialize(2001.0, 2,
+                   {{UnitParams::l2, 1.0}, {UnitParams::efixed, 10.0}});
 
     err_mess = convert_units_check_range(dEk, sample, rezult);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
@@ -1034,41 +1201,50 @@ public:
     std::vector<double> x(1, 0.26597881882),
         y(1, 1.0); // 1.1meV = h*0.26597881882Ghz
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dEf.toTOF(x, y, 1.5, 2.5, 0.0, 1, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEf.toTOF(
+        x, y, 1.5, 1, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 5071.066, 0.001)
     TS_ASSERT(yy == y)
 
     x[0] = 0.26597881882;
-    TS_ASSERT_THROWS_NOTHING(dEf.toTOF(x, y, 1.5, 2.5, 0.0, 2, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEf.toTOF(
+        x, y, 1.5, 2, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 4376.406, 0.001)
     TS_ASSERT(yy == y)
 
     // emode = 0
-    TS_ASSERT_THROWS(dEf.toTOF(x, y, 1.5, 2.5, 0.0, 0, 4.0, 0.0),
-                     const std::invalid_argument &)
+    TS_ASSERT_THROWS(
+        dEf.toTOF(x, y, 1.5, 0,
+                  {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}),
+        const std::invalid_argument &)
   }
 
   void testDeltaEf_fromTOF() {
     std::vector<double> x(1, 2001.0), y(1, 1.0);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(dEf.fromTOF(x, y, 1.5, 2.5, 0.0, 1, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEf.fromTOF(
+        x, y, 1.5, 1, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], -95.4064, 0.0001)
     TS_ASSERT(yy == y)
 
     x[0] = 3001.0;
-    TS_ASSERT_THROWS_NOTHING(dEf.fromTOF(x, y, 1.5, 2.5, 0.0, 2, 4.0, 0.0))
+    TS_ASSERT_THROWS_NOTHING(dEf.fromTOF(
+        x, y, 1.5, 2, {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}))
     TS_ASSERT_DELTA(x[0], 137.7866, 0.0001)
     TS_ASSERT(yy == y)
 
     // emode = 0
-    TS_ASSERT_THROWS(dEf.fromTOF(x, y, 1.5, 2.5, 0.0, 0, 4.0, 0.0),
-                     const std::invalid_argument &)
+    TS_ASSERT_THROWS(
+        dEf.fromTOF(x, y, 1.5, 0,
+                    {{UnitParams::l2, 2.5}, {UnitParams::efixed, 4.0}}),
+        const std::invalid_argument &)
   }
 
   void testDE_fRange() {
     std::vector<double> sample, rezult;
     // Direct
-    dEf.initialize(2001.0, 1.0, 1.5, 1, 10., 0.0);
+    dEf.initialize(2001.0, 1,
+                   {{UnitParams::l2, 1.0}, {UnitParams::efixed, 10.0}});
 
     std::string err_mess =
         convert_units_check_range(dEf, sample, rezult, DBL_EPSILON);
@@ -1088,7 +1264,8 @@ public:
         sample[3], rezult[3], 10 * FLT_EPSILON);
 
     // Indirect
-    dEf.initialize(2001.0, 1.0, 1.5, 2, 10., 0.0);
+    dEf.initialize(2001.0, 2,
+                   {{UnitParams::l2, 1.0}, {UnitParams::efixed, 10.0}});
 
     err_mess = convert_units_check_range(dEf, sample, rezult);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
@@ -1129,7 +1306,8 @@ public:
   void testMomentum_toTOF() {
     std::vector<double> x(1, 2 * M_PI / 1.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(k_i.toTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(k_i.toTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 1.0}}))
     // TS_ASSERT_DELTA( x[0], 2665.4390, 0.0001 ) // -- wavelength to TOF;
     TS_ASSERT_DELTA(x[0], 2665.4390, 0.0001) //
     TS_ASSERT(yy == y)
@@ -1138,7 +1316,8 @@ public:
   void testMomentum_fromTOF() {
     std::vector<double> x(1, 1000.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(k_i.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 1.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(k_i.fromTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 1.0}}))
     //    TS_ASSERT_DELTA( x[0], -5.0865, 0.0001 ) // wavelength from TOF
     TS_ASSERT_DELTA(x[0], 2 * M_PI / (-5.0865), 0.0001) // 1.979006
     TS_ASSERT(yy == y)
@@ -1151,8 +1330,10 @@ public:
     double input = 1.1;
     double result = factor * std::pow(input, power);
     std::vector<double> x(1, input);
-    k_i.toTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    energy.fromTOF(x, x, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    k_i.toTOF(x, x, 99.0, 99,
+              {{UnitParams::l2, 99.0}, {UnitParams::efixed, 99.0}});
+    energy.fromTOF(x, x, 99.0, 99,
+                   {{UnitParams::l2, 99.0}, {UnitParams::efixed, 99.0}});
     TS_ASSERT_DELTA(x[0], result, 1.0e-10)
 
     TS_ASSERT(k_i.quickConversion(energyk, factor, power))
@@ -1160,8 +1341,10 @@ public:
     TS_ASSERT_EQUALS(result2 / result,
                      Mantid::PhysicalConstants::meVtoWavenumber)
     std::vector<double> x2(1, input);
-    k_i.toTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
-    energyk.fromTOF(x2, x2, 99.0, 99.0, 99.0, 99, 99.0, 99.0);
+    k_i.toTOF(x2, x2, 99.0, 99,
+              {{UnitParams::l2, 99.0}, {UnitParams::efixed, 99.0}});
+    energyk.fromTOF(x2, x2, 99.0, 99,
+                    {{UnitParams::l2, 99.0}, {UnitParams::efixed, 99.0}});
     TS_ASSERT_DELTA(x2[0], result2, 1.0e-10);
 
     TS_ASSERT(k_i.quickConversion(lambda, factor, power));
@@ -1173,7 +1356,7 @@ public:
   }
   void testK_iRange() {
     std::vector<double> sample, rezult;
-    k_i.initialize(1.1, 1.1, 99.0, 0, 99.0, 99);
+    k_i.initialize(1.1, 0, {{UnitParams::l2, 1.1}});
 
     std::string err_mess =
         convert_units_check_range(k_i, sample, rezult, DBL_EPSILON);
@@ -1193,7 +1376,8 @@ public:
       }
     }
 
-    k_i.initialize(10000, 11, 99.0, 2, 99.0, 99);
+    k_i.initialize(10000, 2,
+                   {{UnitParams::l2, 11}, {UnitParams::efixed, 99.0}});
 
     err_mess = convert_units_check_range(k_i, sample, rezult, DBL_EPSILON);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
@@ -1212,7 +1396,7 @@ public:
       }
     }
 
-    k_i.initialize(1, 1.1, 99.0, 1, 99.0, 99);
+    k_i.initialize(1, 1, {{UnitParams::l2, 1.1}, {UnitParams::efixed, 99.0}});
 
     err_mess = convert_units_check_range(k_i, sample, rezult, DBL_EPSILON);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
@@ -1258,30 +1442,36 @@ public:
   void testSpinEchoLength_toTOF() {
     std::vector<double> x(1, 4.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(delta.toTOF(x, y, 1.0, 1.0, 1.0, 0, 2.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(delta.toTOF(
+        x, y, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}))
     TS_ASSERT_DELTA(x[0], 758.3352, 0.0001)
     TS_ASSERT(yy == y)
 
-    TS_ASSERT_DELTA(delta.convertSingleToTOF(4.5, 1.0, 1.0, 1.0, 0, 2.0, 1.0),
-                    758.3352, 0.0001);
+    TS_ASSERT_DELTA(
+        delta.convertSingleToTOF(
+            4.5, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}),
+        758.3352, 0.0001);
   }
 
   void testSpinEchoLength_fromTOF() {
     std::vector<double> x(1, 1000.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(delta.fromTOF(x, y, 1.0, 1.0, 1.0, 0, 2.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(delta.fromTOF(
+        x, y, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}))
     TS_ASSERT_DELTA(x[0], 7.8329, 0.0001)
     TS_ASSERT(yy == y)
 
     TS_ASSERT_DELTA(
-        delta.convertSingleFromTOF(1000.5, 1.0, 1.0, 1.0, 0, 2.0, 1.0), 7.8329,
-        0.0001);
+        delta.convertSingleFromTOF(
+            1000.5, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}),
+        7.8329, 0.0001);
   }
 
   void testSpinEchoLength_invalidfromTOF() {
     std::vector<double> x(1, 1000.5), y(1, 1.5);
     // emode must = 0
-    TS_ASSERT_THROWS_ANYTHING(delta.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 2.0, 1.0))
+    TS_ASSERT_THROWS_ANYTHING(delta.fromTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}))
   }
 
   void testSpinEchoLength_quickConversions() {
@@ -1292,7 +1482,8 @@ public:
   }
   void testSpinEchoRange() {
     std::vector<double> sample, rezult;
-    delta.initialize(10, 1.1, 99.0, 0, 99.0, 99);
+    delta.initialize(10, 0,
+                     {{UnitParams::l2, 1.1}, {UnitParams::efixed, 99.0}});
 
     std::string err_mess = convert_units_check_range(delta, sample, rezult);
     TSM_ASSERT(" ERROR:" + err_mess, err_mess.size() == 0);
@@ -1336,30 +1527,36 @@ public:
   void testSpinEchoTime_toTOF() {
     std::vector<double> x(1, 4.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(tau.toTOF(x, y, 1.0, 1.0, 1.0, 0, 2.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(tau.toTOF(
+        x, y, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}))
     TS_ASSERT_DELTA(x[0], 662.4668, 0.0001)
     TS_ASSERT(yy == y)
 
-    TS_ASSERT_DELTA(tau.convertSingleToTOF(4.5, 1.0, 1.0, 1.0, 0, 2.0, 1.0),
-                    662.4668, 0.0001);
+    TS_ASSERT_DELTA(
+        tau.convertSingleToTOF(
+            4.5, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}),
+        662.4668, 0.0001);
   }
 
   void testSpinEchoTime_fromTOF() {
     std::vector<double> x(1, 1000.5), y(1, 1.5);
     std::vector<double> yy = y;
-    TS_ASSERT_THROWS_NOTHING(tau.fromTOF(x, y, 1.0, 1.0, 1.0, 0, 2.0, 1.0))
+    TS_ASSERT_THROWS_NOTHING(tau.fromTOF(
+        x, y, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}))
     TS_ASSERT_DELTA(x[0], 15.5014, 0.0001)
     TS_ASSERT(yy == y)
 
     TS_ASSERT_DELTA(
-        tau.convertSingleFromTOF(1000.5, 1.0, 1.0, 1.0, 0, 2.0, 1.0), 15.5014,
-        0.0001);
+        tau.convertSingleFromTOF(
+            1000.5, 1.0, 0, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}),
+        15.5014, 0.0001);
   }
 
   void testSpinEchoTime_invalidfromTOF() {
     std::vector<double> x(1, 1000.5), y(1, 1.5);
     // emode must = 0
-    TS_ASSERT_THROWS_ANYTHING(tau.fromTOF(x, y, 1.0, 1.0, 1.0, 1, 2.0, 1.0))
+    TS_ASSERT_THROWS_ANYTHING(tau.fromTOF(
+        x, y, 1.0, 1, {{UnitParams::l2, 1.0}, {UnitParams::efixed, 2.0}}))
   }
 
   void testSpinEchoTime_quickConversions() {
@@ -1370,7 +1567,7 @@ public:
   }
   void testSpinEchoTimeRange() {
     std::vector<double> sample, rezult;
-    tau.initialize(100, 11, 1.0, 0, 1.0, 1);
+    tau.initialize(100, 0, {{UnitParams::l2, 11}});
 
     std::string err_mess =
         convert_units_check_range(tau, sample, rezult, DBL_EPSILON);
diff --git a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/FindPeaksMD.h b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/FindPeaksMD.h
index f152fb820814381fa219ee40cd7cb4942b241e9c..64dc5bb72c26030d3c3a0a0bb1536cf1990daeb4 100644
--- a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/FindPeaksMD.h
+++ b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/FindPeaksMD.h
@@ -9,7 +9,9 @@
 #include "MantidAPI/Algorithm.h"
 #include "MantidAPI/ExperimentInfo.h"
 #include "MantidAPI/IMDEventWorkspace_fwd.h"
+#include "MantidAPI/IPeaksWorkspace.h"
 #include "MantidAPI/Progress.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/MDEventWorkspace.h"
 #include "MantidDataObjects/MDHistoWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
@@ -58,18 +60,29 @@ private:
   void exec() override;
 
   /// Read member variables from experiment info
-  void readExperimentInfo(const Mantid::API::ExperimentInfo_sptr &ei,
-                          const Mantid::API::IMDWorkspace_sptr &ws);
+  void readExperimentInfo(const Mantid::API::ExperimentInfo_sptr &ei);
+  void checkWorkspaceDims(const Mantid::API::IMDWorkspace_sptr &ws);
+  void determineOutputType(const std::string peakType,
+                           const uint16_t numExperimentInfo);
 
   /// Adds a peak based on Q, bin count & a set of detector IDs
   void addPeak(const Mantid::Kernel::V3D &Q, const double binCount,
                const Geometry::InstrumentRayTracer &tracer);
 
+  /// Adds a peak based on Q, bin count
+  void addLeanElasticPeak(const Mantid::Kernel::V3D &Q, const double binCount,
+                          const bool useGoniometer = false);
+
   /// Adds a peak based on Q, bin count
   std::shared_ptr<DataObjects::Peak>
   createPeak(const Mantid::Kernel::V3D &Q, const double binCount,
              const Geometry::InstrumentRayTracer &tracer);
 
+  /// Adds a peak based on Q, bin count
+  std::shared_ptr<DataObjects::LeanElasticPeak>
+  createLeanElasticPeak(const Mantid::Kernel::V3D &Q, const double binCount,
+                        const bool useGoniometer = false);
+
   /// Run find peaks on an MDEventWorkspace
   template <typename MDE, size_t nd>
   void findPeaks(typename DataObjects::MDEventWorkspace<MDE, nd>::sptr ws);
@@ -77,7 +90,7 @@ private:
   void findPeaksHisto(const Mantid::DataObjects::MDHistoWorkspace_sptr &ws);
 
   /// Output PeaksWorkspace
-  Mantid::DataObjects::PeaksWorkspace_sptr peakWS;
+  Mantid::API::IPeaksWorkspace_sptr peakWS;
 
   /// Estimated radius of peaks. Boxes closer than this are rejected
   coord_t peakRadiusSquared;
@@ -121,6 +134,8 @@ private:
   static const std::string volumeNormalization;
   /// NumberOfEventNormalization
   static const std::string numberOfEventsNormalization;
+
+  bool m_leanElasticPeak = false;
 };
 
 } // namespace MDAlgorithms
diff --git a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/UnitsConversionHelper.h b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/UnitsConversionHelper.h
index 9418deb7f6e7829396517b0c8f1737b5cced438d..75aab2e980a6fc2d0e46ea29aa3d660719fd3d3b 100644
--- a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/UnitsConversionHelper.h
+++ b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/UnitsConversionHelper.h
@@ -48,12 +48,15 @@ class DLLExport UnitsConversionHelper {
   //  these variables needed and used for conversion through TOF
   int m_Emode;
 
-  double m_L1, m_Efix, m_TwoTheta, m_L2;
+  double m_L1, m_Efix, m_TwoTheta, m_L2, m_DIFA, m_DIFC, m_TZERO;
   std::vector<double> const *m_pTwoThetas;
   std::vector<double> const *m_pL2s;
   // pointer to detector specific input energy (eFixed) defined for indirect
   // instruments;
   float *m_pEfixedArray;
+  std::vector<double> const *m_pDIFAs;
+  std::vector<double> const *m_pDIFCs;
+  std::vector<double> const *m_pTZEROs;
 
 public:
   UnitsConversionHelper();
diff --git a/Framework/MDAlgorithms/src/FindPeaksMD.cpp b/Framework/MDAlgorithms/src/FindPeaksMD.cpp
index 22f5f9ac99dafd87b7b2bdab07c75939e8384014..3b4418be39eb815de36ba48dd95955350a53ceb1 100644
--- a/Framework/MDAlgorithms/src/FindPeaksMD.cpp
+++ b/Framework/MDAlgorithms/src/FindPeaksMD.cpp
@@ -8,7 +8,6 @@
 #include "MantidAPI/Run.h"
 #include "MantidDataObjects/MDEventFactory.h"
 #include "MantidDataObjects/MDHistoWorkspace.h"
-#include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidGeometry/Crystal/EdgePixel.h"
 #include "MantidGeometry/Instrument/Goniometer.h"
 #include "MantidGeometry/Objects/InstrumentRayTracer.h"
@@ -227,8 +226,11 @@ void FindPeaksMD::init() {
                       std::make_unique<EnabledWhenProperty>(
                           "CalculateGoniometerForCW",
                           Mantid::Kernel::ePropertyCriterion::IS_NOT_DEFAULT));
-
-  declareProperty(std::make_unique<WorkspaceProperty<PeaksWorkspace>>(
+  std::vector<std::string> peakTypes = {"Automatic", "Peak", "LeanElasticPeak"};
+  declareProperty("OutputType", "Automatic",
+                  std::make_shared<StringListValidator>(peakTypes),
+                  "Type of Peak in OutputWorkspace");
+  declareProperty(std::make_unique<WorkspaceProperty<IPeaksWorkspace>>(
                       "OutputWorkspace", "", Direction::Output),
                   "An output PeaksWorkspace with the peaks' found positions.");
 
@@ -245,13 +247,25 @@ void FindPeaksMD::init() {
 
 //----------------------------------------------------------------------------------------------
 /** Extract needed data from the workspace's experiment info */
-void FindPeaksMD::readExperimentInfo(const ExperimentInfo_sptr &ei,
-                                     const IMDWorkspace_sptr &ws) {
+void FindPeaksMD::readExperimentInfo(const ExperimentInfo_sptr &ei) {
   // Instrument associated with workspace
   inst = ei->getInstrument();
   // Find the run number
   m_runNumber = ei->getRunNumber();
 
+  // Find the goniometer rotation matrix
+  m_goniometer =
+      Mantid::Kernel::Matrix<double>(3, 3, true); // Default IDENTITY matrix
+  try {
+    m_goniometer = ei->mutableRun().getGoniometerMatrix();
+  } catch (std::exception &e) {
+    g_log.warning() << "Error finding goniometer matrix. It will not be set in "
+                       "the peaks found.\n";
+    g_log.warning() << e.what() << '\n';
+  }
+}
+
+void FindPeaksMD::checkWorkspaceDims(const IMDWorkspace_sptr &ws) {
   // Check that the workspace dimensions are in Q-sample-frame or Q-lab-frame.
   std::string dim0 = ws->getDimension(0)->getName();
   if (dim0 == "H") {
@@ -265,16 +279,22 @@ void FindPeaksMD::readExperimentInfo(const ExperimentInfo_sptr &ei,
   else
     throw std::runtime_error(
         "Unexpected dimensions: need either Q_lab_x or Q_sample_x.");
+}
 
-  // Find the goniometer rotation matrix
-  m_goniometer =
-      Mantid::Kernel::Matrix<double>(3, 3, true); // Default IDENTITY matrix
-  try {
-    m_goniometer = ei->mutableRun().getGoniometerMatrix();
-  } catch (std::exception &e) {
-    g_log.warning() << "Error finding goniometer matrix. It will not be set in "
-                       "the peaks found.\n";
-    g_log.warning() << e.what() << '\n';
+void FindPeaksMD::determineOutputType(const std::string peakType,
+                                      const uint16_t numExperimentInfo) {
+  // This method will be expanded later to check a property on the
+  // input workspace which can specify a default peak type for that
+  // instrument.
+  m_leanElasticPeak = false;
+  if (peakType == "Automatic") {
+    if (numExperimentInfo == 0)
+      m_leanElasticPeak = true;
+  } else if (peakType == "LeanElasticPeak") {
+    m_leanElasticPeak = true;
+  } else { // Peak
+    if (numExperimentInfo == 0)
+      throw std::runtime_error("Cannot create Peak output with 0 expInfo");
   }
 }
 
@@ -301,6 +321,20 @@ void FindPeaksMD::addPeak(const V3D &Q, const double binCount,
   }
 }
 
+//----------------------------------------------------------------------------------------------
+/** Create and add a LeanElasticPeak to the output workspace
+ *
+ * @param Q :: Q_lab or Q_sample, depending on workspace
+ * @param binCount :: bin count to give to the peak.
+ * @param useGoniometer :: if to include set goniometer from the input workspace
+ * in the peak
+ */
+void FindPeaksMD::addLeanElasticPeak(const V3D &Q, const double binCount,
+                                     const bool useGoniometer) {
+  auto p = this->createLeanElasticPeak(Q, binCount, useGoniometer);
+  peakWS->addPeak(*p);
+}
+
 /**
  * Creates a Peak object from Q & bin count
  * */
@@ -324,9 +358,9 @@ FindPeaksMD::createPeak(const Mantid::Kernel::V3D &Q, const double binCount,
         if (inst->hasParameter("wavelength")) {
           wavelength = inst->getNumberParameter("wavelength").at(0);
         } else {
-          throw std::runtime_error(
-              "Could not get wavelength, neither Wavelength algorithm property "
-              "set nor instrument wavelength parameter");
+          throw std::runtime_error("Could not get wavelength, neither "
+                                   "Wavelength algorithm property "
+                                   "set nor instrument wavelength parameter");
         }
       }
 
@@ -359,9 +393,32 @@ FindPeaksMD::createPeak(const Mantid::Kernel::V3D &Q, const double binCount,
   return p;
 }
 
+/**
+ * Creates a Peak object from Q & bin count
+ * */
+std::shared_ptr<DataObjects::LeanElasticPeak>
+FindPeaksMD::createLeanElasticPeak(const Mantid::Kernel::V3D &Q,
+                                   const double binCount,
+                                   const bool useGoniometer) {
+  std::shared_ptr<DataObjects::LeanElasticPeak> p;
+  if (dimType == QSAMPLE) {
+    if (useGoniometer)
+      p = std::make_shared<LeanElasticPeak>(Q, m_goniometer);
+    else
+      p = std::make_shared<LeanElasticPeak>(Q);
+  } else {
+    throw std::invalid_argument(
+        "Cannot find peaks unless the dimension is QSAMPLE");
+  }
+
+  p->setBinCount(binCount);
+  // Save the run number found before.
+  p->setRunNumber(m_runNumber);
+  return p;
+}
 //----------------------------------------------------------------------------------------------
-/** Integrate the peaks of the workspace using parameters saved in the algorithm
- * class
+/** Integrate the peaks of the workspace using parameters saved in the
+ * algorithm class
  * @param ws ::  MDEventWorkspace to integrate
  */
 template <typename MDE, size_t nd>
@@ -384,168 +441,127 @@ void FindPeaksMD::findPeaks(typename MDEventWorkspace<MDE, nd>::sptr ws) {
   // Make sure all centroids are fresh
   // ws->getBox()->refreshCentroid();
 
-  uint16_t nexp = ws->getNumExperimentInfo();
-
-  if (nexp == 0)
-    throw std::runtime_error(
-        "No instrument was found in the MDEventWorkspace. Cannot find peaks.");
-
-  for (uint16_t iexp = 0; iexp < ws->getNumExperimentInfo(); iexp++) {
-    ExperimentInfo_sptr ei = ws->getExperimentInfo(iexp);
-    this->readExperimentInfo(ei, std::dynamic_pointer_cast<IMDWorkspace>(ws));
-
-    Geometry::InstrumentRayTracer tracer(inst);
-    // Copy the instrument, sample, run to the peaks workspace.
-    peakWS->copyExperimentInfoFrom(ei.get());
-
-    // Calculate a threshold below which a box is too diffuse to be considered a
-    // peak.
-    signal_t threshold =
-        m_useNumberOfEventsNormalization
-            ? ws->getBox()->getSignalByNEvents() * m_signalThresholdFactor
-            : ws->getBox()->getSignalNormalized() * DensityThresholdFactor;
-    threshold *= m_densityScaleFactor;
-
-    if (!std::isfinite(threshold)) {
-      g_log.warning()
-          << "Infinite or NaN overall density found. Your input data "
-             "may be invalid. Using a 0 threshold instead.\n";
-      threshold = 0;
-    }
-    g_log.information() << "Threshold signal density: " << threshold << '\n';
-
-    using boxPtr = API::IMDNode *;
-    // We will fill this vector with pointers to all the boxes (up to a given
-    // depth)
-    typename std::vector<API::IMDNode *> boxes;
-
-    // Get all the MDboxes
-    progress(0.10, "Getting Boxes");
-    ws->getBox()->getBoxes(boxes, 1000, true);
-
-    // This pair is the <density, ptr to the box>
-    using dens_box = std::pair<double, API::IMDNode *>;
-
-    // Map that will sort the boxes by increasing density. The key = density;
-    // value = box *.
-    typename std::multimap<double, API::IMDNode *> sortedBoxes;
-
-    // --------------- Sort and Filter by Density -----------------------------
-    progress(0.20, "Sorting Boxes by Density");
-    auto it1 = boxes.begin();
-    auto it1_end = boxes.end();
-    for (; it1 != it1_end; it1++) {
-      auto box = *it1;
-      double value = m_useNumberOfEventsNormalization
-                         ? box->getSignalByNEvents()
-                         : box->getSignalNormalized();
-      value *= m_densityScaleFactor;
-      // Skip any boxes with too small a signal value.
-      if (value > threshold)
-        sortedBoxes.insert(dens_box(value, box));
-    }
+  // Calculate a threshold below which a box is too diffuse to be considered a
+  // peak.
+  signal_t threshold =
+      m_useNumberOfEventsNormalization
+          ? ws->getBox()->getSignalByNEvents() * m_signalThresholdFactor
+          : ws->getBox()->getSignalNormalized() * DensityThresholdFactor;
+  threshold *= m_densityScaleFactor;
+
+  if (!std::isfinite(threshold)) {
+    g_log.warning() << "Infinite or NaN overall density found. Your input data "
+                       "may be invalid. Using a 0 threshold instead.\n";
+    threshold = 0;
+  }
+  g_log.information() << "Threshold signal density: " << threshold << '\n';
+
+  using boxPtr = API::IMDNode *;
+  // We will fill this vector with pointers to all the boxes (up to a given
+  // depth)
+  typename std::vector<API::IMDNode *> boxes;
+
+  // Get all the MDboxes
+  progress(0.10, "Getting Boxes");
+  ws->getBox()->getBoxes(boxes, 1000, true);
+
+  // This pair is the <density, ptr to the box>
+  using dens_box = std::pair<double, API::IMDNode *>;
+
+  // Map that will sort the boxes by increasing density. The key = density;
+  // value = box *.
+  typename std::multimap<double, API::IMDNode *> sortedBoxes;
+
+  // --------------- Sort and Filter by Density -----------------------------
+  progress(0.20, "Sorting Boxes by Density");
+  auto it1 = boxes.begin();
+  auto it1_end = boxes.end();
+  for (; it1 != it1_end; it1++) {
+    auto box = *it1;
+    double value = m_useNumberOfEventsNormalization
+                       ? box->getSignalByNEvents()
+                       : box->getSignalNormalized();
+    value *= m_densityScaleFactor;
+    // Skip any boxes with too small a signal value.
+    if (value > threshold)
+      sortedBoxes.insert(dens_box(value, box));
+  }
 
-    // --------------- Find Peak Boxes -----------------------------
-    // List of chosen possible peak boxes.
-    std::vector<API::IMDNode *> peakBoxes;
+  // --------------- Find Peak Boxes -----------------------------
+  // List of chosen possible peak boxes.
+  std::vector<API::IMDNode *> peakBoxes;
 
-    prog = std::make_unique<Progress>(this, 0.30, 0.95, m_maxPeaks);
+  prog = std::make_unique<Progress>(this, 0.30, 0.95, m_maxPeaks);
 
-    // used for selecting method for calculating BinCount
-    bool isMDEvent(ws->id().find("MDEventWorkspace") != std::string::npos);
+  // used for selecting method for calculating BinCount
+  bool isMDEvent(ws->id().find("MDEventWorkspace") != std::string::npos);
 
-    int64_t numBoxesFound = 0;
-    // Now we go (backwards) through the map
-    // e.g. from highest density down to lowest density.
-    typename std::multimap<double, boxPtr>::reverse_iterator it2;
-    auto it2_end = sortedBoxes.rend();
-    for (it2 = sortedBoxes.rbegin(); it2 != it2_end; ++it2) {
-      signal_t density = it2->first;
-      boxPtr box = it2->second;
+  int64_t numBoxesFound = 0;
+  // Now we go (backwards) through the map
+  // e.g. from highest density down to lowest density.
+  typename std::multimap<double, boxPtr>::reverse_iterator it2;
+  auto it2_end = sortedBoxes.rend();
+  for (it2 = sortedBoxes.rbegin(); it2 != it2_end; ++it2) {
+    signal_t density = it2->first;
+    boxPtr box = it2->second;
 #ifndef MDBOX_TRACK_CENTROID
-      coord_t boxCenter[nd];
-      box->calculateCentroid(boxCenter);
+    coord_t boxCenter[nd];
+    box->calculateCentroid(boxCenter);
 #else
-      const coord_t *boxCenter = box->getCentroid();
+    const coord_t *boxCenter = box->getCentroid();
 #endif
 
-      // Compare to all boxes already picked.
-      bool badBox = false;
-      for (auto &peakBoxe : peakBoxes) {
+    // Compare to all boxes already picked.
+    bool badBox = false;
+    for (auto &peakBoxe : peakBoxes) {
 
 #ifndef MDBOX_TRACK_CENTROID
-        coord_t otherCenter[nd];
-        (*it3)->calculateCentroid(otherCenter);
+      coord_t otherCenter[nd];
+      (*it3)->calculateCentroid(otherCenter);
 #else
-        const coord_t *otherCenter = peakBoxe->getCentroid();
+      const coord_t *otherCenter = peakBoxe->getCentroid();
 #endif
 
-        // Distance between this box and a box we already put in.
-        coord_t distSquared = 0.0;
-        for (size_t d = 0; d < nd; d++) {
-          coord_t dist = otherCenter[d] - boxCenter[d];
-          distSquared += (dist * dist);
-        }
-
-        // Reject this box if it is too close to another previously found box.
-        if (distSquared < peakRadiusSquared) {
-          badBox = true;
-          break;
-        }
+      // Distance between this box and a box we already put in.
+      coord_t distSquared = 0.0;
+      for (size_t d = 0; d < nd; d++) {
+        coord_t dist = otherCenter[d] - boxCenter[d];
+        distSquared += (dist * dist);
       }
 
-      // The box was not rejected for another reason.
-      if (!badBox) {
-        if (numBoxesFound++ >= m_maxPeaks) {
-          g_log.notice() << "Number of peaks found exceeded the limit of "
-                         << m_maxPeaks << ". Stopping peak finding.\n";
-          break;
-        }
+      // Reject this box if it is too close to another previously found box.
+      if (distSquared < peakRadiusSquared) {
+        badBox = true;
+        break;
+      }
+    }
 
-        peakBoxes.emplace_back(box);
-        g_log.debug() << "Found box at ";
-        for (size_t d = 0; d < nd; d++)
-          g_log.debug() << (d > 0 ? "," : "") << boxCenter[d];
-        g_log.debug() << "; Density = " << density << '\n';
-        // Report progres for each box found.
-        prog->report("Finding Peaks");
+    // The box was not rejected for another reason.
+    if (!badBox) {
+      if (numBoxesFound++ >= m_maxPeaks) {
+        g_log.notice() << "Number of peaks found exceeded the limit of "
+                       << m_maxPeaks << ". Stopping peak finding.\n";
+        break;
       }
+
+      peakBoxes.emplace_back(box);
+      g_log.debug() << "Found box at ";
+      for (size_t d = 0; d < nd; d++)
+        g_log.debug() << (d > 0 ? "," : "") << boxCenter[d];
+      g_log.debug() << "; Density = " << density << '\n';
+      // Report progres for each box found.
+      prog->report("Finding Peaks");
     }
+  }
 
-    prog->resetNumSteps(numBoxesFound, 0.95, 1.0);
+  prog->resetNumSteps(numBoxesFound, 0.95, 1.0);
 
+  uint16_t numExperimentInfo = ws->getNumExperimentInfo();
+
+  if (numExperimentInfo == 0) {
     // --- Convert the "boxes" to peaks ----
     for (auto box : peakBoxes) {
-      //  If no events from this experimental contribute to the box then skip
-      if (nexp > 1) {
-        auto *mdbox = dynamic_cast<MDBox<MDE, nd> *>(box);
-        const std::vector<MDE> &events = mdbox->getEvents();
-        if (std::none_of(events.cbegin(), events.cend(),
-                         [&iexp, &nexp](MDE event) {
-                           return event.getRunIndex() == iexp ||
-                                  event.getRunIndex() >= nexp;
-                         }))
-          continue;
-      }
-
-      // If multiple goniometers than use the average one from the
-      // events in the box, that matches this runIndex, this assumes
-      // the events are added in same order as the goniometers
-      if (ei->run().getNumGoniometers() > 1) {
-        const std::vector<MDE> &events =
-            dynamic_cast<MDBox<MDE, nd> *>(box)->getEvents();
-        double sum = 0;
-        double count = 0;
-        for (const auto &event : events) {
-          if (event.getRunIndex() == iexp) {
-            sum += event.getGoniometerIndex();
-            count++;
-          }
-        }
-        m_goniometer = ei->mutableRun().getGoniometerMatrix(lrint(sum / count));
-      }
       // The center of the box = Q in the lab frame
-
 #ifndef MDBOX_TRACK_CENTROID
       coord_t boxCenter[nd];
       box->calculateCentroid(boxCenter);
@@ -556,42 +572,111 @@ void FindPeaksMD::findPeaks(typename MDEventWorkspace<MDE, nd>::sptr ws) {
       // Q of the centroid of the box
       V3D Q(boxCenter[0], boxCenter[1], boxCenter[2]);
 
-      // The "bin count" used will be the box density or the number of events in
-      // the box
+      // The "bin count" used will be the box density.
       double binCount = box->getSignalNormalized() * m_densityScaleFactor;
       if (isMDEvent)
         binCount = static_cast<double>(box->getNPoints());
 
-      try {
-        auto p = this->createPeak(Q, binCount, tracer);
-        if (m_addDetectors) {
-          auto mdBox = dynamic_cast<MDBoxBase<MDE, nd> *>(box);
-          if (!mdBox) {
-            throw std::runtime_error("Failed to cast box to MDBoxBase");
+      // Create the peak
+      addLeanElasticPeak(Q, binCount);
+
+      // Report progres for each box found.
+      prog->report("Adding Peaks");
+
+    } // for each box found
+  } else {
+    for (uint16_t iexp = 0; iexp < ws->getNumExperimentInfo(); iexp++) {
+      ExperimentInfo_sptr ei = ws->getExperimentInfo(iexp);
+      this->readExperimentInfo(ei);
+
+      Geometry::InstrumentRayTracer tracer(inst);
+      // Copy the instrument, sample, run to the peaks workspace.
+      peakWS->copyExperimentInfoFrom(ei.get());
+
+      // --- Convert the "boxes" to peaks ----
+      for (auto box : peakBoxes) {
+        //  If no events from this experimental contribute to the box then skip
+        if (numExperimentInfo > 1) {
+          auto *mdbox = dynamic_cast<MDBox<MDE, nd> *>(box);
+          const std::vector<MDE> &events = mdbox->getEvents();
+          if (std::none_of(events.cbegin(), events.cend(),
+                           [&iexp, &numExperimentInfo](MDE event) {
+                             return event.getRunIndex() == iexp ||
+                                    event.getRunIndex() >= numExperimentInfo;
+                           }))
+            continue;
+        }
+
+        // If multiple goniometers than use the average one from the
+        // events in the box, that matches this runIndex, this assumes
+        // the events are added in same order as the goniometers
+        if (ei->run().getNumGoniometers() > 1) {
+          const std::vector<MDE> &events =
+              dynamic_cast<MDBox<MDE, nd> *>(box)->getEvents();
+          double sum = 0;
+          double count = 0;
+          for (const auto &event : events) {
+            if (event.getRunIndex() == iexp) {
+              sum += event.getGoniometerIndex();
+              count++;
+            }
           }
-          addDetectors(*p, *mdBox);
+          m_goniometer =
+              ei->mutableRun().getGoniometerMatrix(lrint(sum / count));
         }
-        if (p->getDetectorID() != -1) {
-          if (m_edge > 0) {
-            if (!edgePixel(inst, p->getBankName(), p->getCol(), p->getRow(),
-                           m_edge))
-              peakWS->addPeak(*p);
-            ;
-          } else {
-            peakWS->addPeak(*p);
+        // The center of the box = Q in the lab frame
+
+#ifndef MDBOX_TRACK_CENTROID
+        coord_t boxCenter[nd];
+        box->calculateCentroid(boxCenter);
+#else
+        const coord_t *boxCenter = box->getCentroid();
+#endif
+
+        // Q of the centroid of the box
+        V3D Q(boxCenter[0], boxCenter[1], boxCenter[2]);
+
+        // The "bin count" used will be the box density or the number of events
+        // in the box
+        double binCount = box->getSignalNormalized() * m_densityScaleFactor;
+        if (isMDEvent)
+          binCount = static_cast<double>(box->getNPoints());
+
+        if (m_leanElasticPeak) {
+          addLeanElasticPeak(Q, binCount, true);
+        } else {
+          try {
+            auto p = this->createPeak(Q, binCount, tracer);
+            if (m_addDetectors) {
+              auto mdBox = dynamic_cast<MDBoxBase<MDE, nd> *>(box);
+              if (!mdBox) {
+                throw std::runtime_error("Failed to cast box to MDBoxBase");
+              }
+              addDetectors(*p, *mdBox);
+            }
+            if (p->getDetectorID() != -1) {
+              if (m_edge > 0) {
+                if (!edgePixel(inst, p->getBankName(), p->getCol(), p->getRow(),
+                               m_edge))
+                  peakWS->addPeak(*p);
+                ;
+              } else {
+                peakWS->addPeak(*p);
+              }
+              g_log.information() << "Add new peak with Q-center = " << Q[0]
+                                  << ", " << Q[1] << ", " << Q[2] << "\n";
+            }
+          } catch (std::exception &e) {
+            g_log.notice() << "Error creating peak at " << Q << " because of '"
+                           << e.what() << "'. Peak will be skipped.\n";
           }
-          g_log.information() << "Add new peak with Q-center = " << Q[0] << ", "
-                              << Q[1] << ", " << Q[2] << "\n";
         }
-      } catch (std::exception &e) {
-        g_log.notice() << "Error creating peak at " << Q << " because of '"
-                       << e.what() << "'. Peak will be skipped.\n";
-      }
 
-      // Report progress for each box found.
-      prog->report("Adding Peaks");
+        // Report progress for each box found.
+        prog->report("Adding Peaks");
 
-    } // for each box found
+      } // for each box found
+    }
   }
   g_log.notice() << "Number of peaks found: " << peakWS->getNumberPeaks()
                  << '\n';
@@ -611,105 +696,94 @@ void FindPeaksMD::findPeaksHisto(
   g_log.warning("Workspace is an MDHistoWorkspace. Resultant PeaksWorkspaces "
                 "will not contain full detector information.");
 
-  if (ws->getNumExperimentInfo() == 0)
-    throw std::runtime_error(
-        "No instrument was found in the workspace. Cannot find peaks.");
-
-  for (uint16_t iexp = 0; iexp < ws->getNumExperimentInfo(); iexp++) {
-    ExperimentInfo_sptr ei = ws->getExperimentInfo(iexp);
-    this->readExperimentInfo(ei, std::dynamic_pointer_cast<IMDWorkspace>(ws));
-    Geometry::InstrumentRayTracer tracer(inst);
-
-    // Copy the instrument, sample, run to the peaks workspace.
-    peakWS->copyExperimentInfoFrom(ei.get());
-
-    // This pair is the <density, box index>
-    using dens_box = std::pair<double, size_t>;
-
-    // Map that will sort the boxes by increasing density. The key = density;
-    // value = box index.
-    std::multimap<double, size_t> sortedBoxes;
-
-    size_t numBoxes = ws->getNPoints();
-
-    // --------- Count the overall signal density -----------------------------
-    progress(0.10, "Counting Total Signal");
-    double totalSignal = 0;
-    for (size_t i = 0; i < numBoxes; i++)
-      totalSignal += ws->getSignalAt(i);
-    // Calculate the threshold density
-    double thresholdDensity =
-        (totalSignal * ws->getInverseVolume() / double(numBoxes)) *
-        DensityThresholdFactor * m_densityScaleFactor;
-    if (!std::isfinite(thresholdDensity)) {
-      g_log.warning()
-          << "Infinite or NaN overall density found. Your input data "
-             "may be invalid. Using a 0 threshold instead.\n";
-      thresholdDensity = 0;
-    }
-    g_log.information() << "Threshold signal density: " << thresholdDensity
-                        << '\n';
-
-    // -------------- Sort and Filter by Density -----------------------------
-    progress(0.20, "Sorting Boxes by Density");
-    for (size_t i = 0; i < numBoxes; i++) {
-      double density = ws->getSignalNormalizedAt(i) * m_densityScaleFactor;
-      // Skip any boxes with too small a signal density.
-      if (density > thresholdDensity)
-        sortedBoxes.insert(dens_box(density, i));
-    }
-
-    // --------------- Find Peak Boxes -----------------------------
-    // List of chosen possible peak boxes.
-    std::vector<size_t> peakBoxes;
-
-    prog = std::make_unique<Progress>(this, 0.30, 0.95, m_maxPeaks);
-
-    int64_t numBoxesFound = 0;
-    // Now we go (backwards) through the map
-    // e.g. from highest density down to lowest density.
-    std::multimap<double, size_t>::reverse_iterator it2;
-    auto it2_end = sortedBoxes.rend();
-    for (it2 = sortedBoxes.rbegin(); it2 != it2_end; ++it2) {
-      signal_t density = it2->first;
-      size_t index = it2->second;
-      // Get the center of the box
-      VMD boxCenter = ws->getCenter(index);
-
-      // Compare to all boxes already picked.
-      bool badBox = false;
-      for (auto &peakBoxe : peakBoxes) {
-        VMD otherCenter = ws->getCenter(peakBoxe);
-
-        // Distance between this box and a box we already put in.
-        coord_t distSquared = 0.0;
-        for (size_t d = 0; d < nd; d++) {
-          coord_t dist = otherCenter[d] - boxCenter[d];
-          distSquared += (dist * dist);
-        }
+  // This pair is the <density, box index>
+  using dens_box = std::pair<double, size_t>;
+
+  // Map that will sort the boxes by increasing density. The key = density;
+  // value = box index.
+  std::multimap<double, size_t> sortedBoxes;
+
+  size_t numBoxes = ws->getNPoints();
+
+  // --------- Count the overall signal density -----------------------------
+  progress(0.10, "Counting Total Signal");
+  double totalSignal = 0;
+  for (size_t i = 0; i < numBoxes; i++)
+    totalSignal += ws->getSignalAt(i);
+  // Calculate the threshold density
+  double thresholdDensity =
+      (totalSignal * ws->getInverseVolume() / double(numBoxes)) *
+      DensityThresholdFactor * m_densityScaleFactor;
+  if (!std::isfinite(thresholdDensity)) {
+    g_log.warning() << "Infinite or NaN overall density found. Your input data "
+                       "may be invalid. Using a 0 threshold instead.\n";
+    thresholdDensity = 0;
+  }
+  g_log.information() << "Threshold signal density: " << thresholdDensity
+                      << '\n';
+
+  // -------------- Sort and Filter by Density -----------------------------
+  progress(0.20, "Sorting Boxes by Density");
+  for (size_t i = 0; i < numBoxes; i++) {
+    double density = ws->getSignalNormalizedAt(i) * m_densityScaleFactor;
+    // Skip any boxes with too small a signal density.
+    if (density > thresholdDensity)
+      sortedBoxes.insert(dens_box(density, i));
+  }
 
-        // Reject this box if it is too close to another previously found box.
-        if (distSquared < peakRadiusSquared) {
-          badBox = true;
-          break;
-        }
+  // --------------- Find Peak Boxes -----------------------------
+  // List of chosen possible peak boxes.
+  std::vector<size_t> peakBoxes;
+
+  prog = std::make_unique<Progress>(this, 0.30, 0.95, m_maxPeaks);
+
+  int64_t numBoxesFound = 0;
+  // Now we go (backwards) through the map
+  // e.g. from highest density down to lowest density.
+  std::multimap<double, size_t>::reverse_iterator it2;
+  auto it2_end = sortedBoxes.rend();
+  for (it2 = sortedBoxes.rbegin(); it2 != it2_end; ++it2) {
+    signal_t density = it2->first;
+    size_t index = it2->second;
+    // Get the center of the box
+    VMD boxCenter = ws->getCenter(index);
+
+    // Compare to all boxes already picked.
+    bool badBox = false;
+    for (auto &peakBoxe : peakBoxes) {
+      VMD otherCenter = ws->getCenter(peakBoxe);
+
+      // Distance between this box and a box we already put in.
+      coord_t distSquared = 0.0;
+      for (size_t d = 0; d < nd; d++) {
+        coord_t dist = otherCenter[d] - boxCenter[d];
+        distSquared += (dist * dist);
       }
 
-      // The box was not rejected for another reason.
-      if (!badBox) {
-        if (numBoxesFound++ >= m_maxPeaks) {
-          g_log.notice() << "Number of peaks found exceeded the limit of "
-                         << m_maxPeaks << ". Stopping peak finding.\n";
-          break;
-        }
+      // Reject this box if it is too close to another previously found box.
+      if (distSquared < peakRadiusSquared) {
+        badBox = true;
+        break;
+      }
+    }
 
-        peakBoxes.emplace_back(index);
-        g_log.debug() << "Found box at index " << index;
-        g_log.debug() << "; Density = " << density << '\n';
-        // Report progres for each box found.
-        prog->report("Finding Peaks");
+    // The box was not rejected for another reason.
+    if (!badBox) {
+      if (numBoxesFound++ >= m_maxPeaks) {
+        g_log.notice() << "Number of peaks found exceeded the limit of "
+                       << m_maxPeaks << ". Stopping peak finding.\n";
+        break;
       }
+
+      peakBoxes.emplace_back(index);
+      g_log.debug() << "Found box at index " << index;
+      g_log.debug() << "; Density = " << density << '\n';
+      // Report progres for each box found.
+      prog->report("Finding Peaks");
     }
+  }
+
+  if (ws->getNumExperimentInfo() == 0) {
     // --- Convert the "boxes" to peaks ----
     for (auto index : peakBoxes) {
       // The center of the box = Q in the lab frame
@@ -722,36 +796,84 @@ void FindPeaksMD::findPeaksHisto(
       double binCount = ws->getSignalNormalizedAt(index) * m_densityScaleFactor;
 
       // Create the peak
-      addPeak(Q, binCount, tracer);
+      addLeanElasticPeak(Q, binCount);
 
       // Report progres for each box found.
       prog->report("Adding Peaks");
 
     } // for each box found
+  } else {
+    for (uint16_t iexp = 0; iexp < ws->getNumExperimentInfo(); iexp++) {
+      ExperimentInfo_sptr ei = ws->getExperimentInfo(iexp);
+      this->readExperimentInfo(ei);
+      Geometry::InstrumentRayTracer tracer(inst);
+
+      // Copy the instrument, sample, run to the peaks workspace.
+      peakWS->copyExperimentInfoFrom(ei.get());
+
+      // --- Convert the "boxes" to peaks ----
+      for (auto index : peakBoxes) {
+        // The center of the box = Q in the lab frame
+        VMD boxCenter = ws->getCenter(index);
+
+        // Q of the centroid of the box
+        V3D Q(boxCenter[0], boxCenter[1], boxCenter[2]);
+
+        // The "bin count" used will be the box density.
+        double binCount =
+            ws->getSignalNormalizedAt(index) * m_densityScaleFactor;
+
+        // Create the peak
+        if (m_leanElasticPeak)
+          addLeanElasticPeak(Q, binCount, true);
+        else
+          addPeak(Q, binCount, tracer);
+
+        // Report progres for each box found.
+        prog->report("Adding Peaks");
+
+      } // for each box found
+    }
   }
   g_log.notice() << "Number of peaks found: " << peakWS->getNumberPeaks()
                  << '\n';
-}
+} // namespace MDAlgorithms
 
 //----------------------------------------------------------------------------------------------
 /** Execute the algorithm.
  */
 void FindPeaksMD::exec() {
 
-  bool AppendPeaks = getProperty("AppendPeaks");
-
-  // Output peaks workspace, create if needed
-  peakWS = getProperty("OutputWorkspace");
-  if (!peakWS || !AppendPeaks)
-    peakWS = PeaksWorkspace_sptr(new PeaksWorkspace());
-
   // The MDEventWorkspace as input
   IMDWorkspace_sptr inWS = getProperty("InputWorkspace");
+  checkWorkspaceDims(inWS);
   MDHistoWorkspace_sptr inMDHW =
       std::dynamic_pointer_cast<MDHistoWorkspace>(inWS);
   IMDEventWorkspace_sptr inMDEW =
       std::dynamic_pointer_cast<IMDEventWorkspace>(inWS);
 
+  bool AppendPeaks = getProperty("AppendPeaks");
+
+  uint16_t numExperimentInfo = 0;
+  if (inMDHW)
+    numExperimentInfo = inMDHW->getNumExperimentInfo();
+  else if (inMDEW)
+    numExperimentInfo = inMDEW->getNumExperimentInfo();
+
+  std::string peakType = getProperty("OutputType");
+
+  determineOutputType(peakType, numExperimentInfo);
+
+  // Output peaks workspace, create if needed
+  peakWS = getProperty("OutputWorkspace");
+
+  if (!peakWS || !AppendPeaks) {
+    if (m_leanElasticPeak)
+      peakWS = LeanElasticPeaksWorkspace_sptr(new LeanElasticPeaksWorkspace());
+    else
+      peakWS = PeaksWorkspace_sptr(new PeaksWorkspace());
+  }
+
   // Other parameters
   double PeakDistanceThreshold = getProperty("PeakDistanceThreshold");
   peakRadiusSquared =
@@ -785,7 +907,7 @@ void FindPeaksMD::exec() {
   peakWS->sort(criteria);
 
   for (auto i = 0; i != peakWS->getNumberPeaks(); ++i) {
-    Peak &p = peakWS->getPeak(i);
+    Mantid::Geometry::IPeak &p = peakWS->getPeak(i);
     p.setPeakNumber(i + 1);
   }
   // Save the output
@@ -805,6 +927,8 @@ std::map<std::string, std::string> FindPeaksMD::validateInputs() {
   IMDWorkspace_sptr inWS = getProperty("InputWorkspace");
   IMDEventWorkspace_sptr inMDEW =
       std::dynamic_pointer_cast<IMDEventWorkspace>(inWS);
+  MDHistoWorkspace_sptr inMDHW =
+      std::dynamic_pointer_cast<MDHistoWorkspace>(inWS);
 
   if (useNumberOfEventsNormalization && !inMDEW) {
     result["PeakFindingStrategy"] = "The NumberOfEventsNormalization selection "
@@ -812,6 +936,19 @@ std::map<std::string, std::string> FindPeaksMD::validateInputs() {
                                     "as the input.";
   }
 
+  uint16_t numExperimentInfo = 0;
+  if (inMDHW)
+    numExperimentInfo = inMDHW->getNumExperimentInfo();
+  else if (inMDEW)
+    numExperimentInfo = inMDEW->getNumExperimentInfo();
+
+  std::string peakType = getProperty("OutputType");
+
+  if (peakType == "Peak" && numExperimentInfo == 0)
+    result["OutputType"] =
+        "The InputWorkspace doesn't contain any experiment information so the "
+        "OutputType cannot be Peak.";
+
   return result;
 }
 
diff --git a/Framework/MDAlgorithms/src/PreprocessDetectorsToMD.cpp b/Framework/MDAlgorithms/src/PreprocessDetectorsToMD.cpp
index 34e03a3dd87e53592a2e8e66da29bf20565a7e68..2a8740cd2eb282736130282e8255b4515e20541c 100644
--- a/Framework/MDAlgorithms/src/PreprocessDetectorsToMD.cpp
+++ b/Framework/MDAlgorithms/src/PreprocessDetectorsToMD.cpp
@@ -161,6 +161,9 @@ PreprocessDetectorsToMD::createTableWorkspace(
   m_getEFixed = this->getProperty("GetEFixed");
   if (m_getEFixed)
     targWS->addColumn("float", "eFixed");
+  targWS->addColumn("double", "DIFA");
+  targWS->addColumn("double", "DIFC");
+  targWS->addColumn("double", "TZERO");
 
   // will see about that
   // sin^2(Theta)
@@ -220,6 +223,9 @@ void PreprocessDetectorsToMD::processDetectorsPositions(
   auto &TwoTheta = targWS->getColVector<double>("TwoTheta");
   auto &Azimuthal = targWS->getColVector<double>("Azimuthal");
   auto &detDir = targWS->getColVector<Kernel::V3D>("DetDirections");
+  auto &DIFA = targWS->getColVector<double>("DIFA");
+  auto &DIFC = targWS->getColVector<double>("DIFC");
+  auto &TZERO = targWS->getColVector<double>("TZERO");
 
   // Efixed; do we need one and does one exist?
   auto Efi = targWS->getLogs()->getPropertyValueAsType<double>("Ei");
@@ -272,6 +278,13 @@ void PreprocessDetectorsToMD::processDetectorsPositions(
     TwoTheta[liveDetectorsCount] = polar;
     Azimuthal[liveDetectorsCount] = azim;
 
+    std::vector<int> warningDets;
+    auto diffConsts = spectrumInfo.diffractometerConstants(i, warningDets);
+    // map will create an entry with zero value if not present already
+    DIFA[liveDetectorsCount] = diffConsts[Kernel::UnitParams::difa];
+    DIFC[liveDetectorsCount] = diffConsts[Kernel::UnitParams::difc];
+    TZERO[liveDetectorsCount] = diffConsts[Kernel::UnitParams::tzero];
+
     double sPhi = sin(polar);
     double ez = cos(polar);
     double ex = sPhi * cos(azim);
diff --git a/Framework/MDAlgorithms/src/UnitsConversionHelper.cpp b/Framework/MDAlgorithms/src/UnitsConversionHelper.cpp
index a327a4b6223f0108c065695c566aac0b91ec46a6..186b8f5d0c97c7bea95e178e814497b0e8f6bf16 100644
--- a/Framework/MDAlgorithms/src/UnitsConversionHelper.cpp
+++ b/Framework/MDAlgorithms/src/UnitsConversionHelper.cpp
@@ -10,6 +10,8 @@
 #include "MantidKernel/UnitFactory.h"
 #include <cmath>
 
+using Mantid::Kernel::UnitParams;
+
 namespace Mantid {
 namespace MDAlgorithms {
 
@@ -265,6 +267,10 @@ void UnitsConversionHelper::initialize(
   if (m_Emode == static_cast<int>(Kernel::DeltaEMode::Indirect))
     m_pEfixedArray = DetWS->getColDataArray<float>("eFixed");
 
+  m_pDIFAs = &(DetWS->getColVector<double>("DIFA"));
+  m_pDIFCs = &(DetWS->getColVector<double>("DIFC"));
+  m_pTZEROs = &(DetWS->getColVector<double>("TZERO"));
+
   // set up conversion to working state -- in some tests it can be used straight
   // from the beginning.
   m_TwoTheta = (*m_pTwoThetas)[0];
@@ -272,14 +278,29 @@ void UnitsConversionHelper::initialize(
   double Efix = m_Efix;
   if (m_pEfixedArray)
     Efix = static_cast<double>(*(m_pEfixedArray + 0));
-
-  m_TargetUnit->initialize(m_L1, m_L2, m_TwoTheta, m_Emode, Efix, 0.);
+  m_DIFA = (*m_pDIFAs)[0];
+  m_DIFC = (*m_pDIFCs)[0];
+  m_TZERO = (*m_pTZEROs)[0];
+
+  m_TargetUnit->initialize(m_L1, m_Emode,
+                           {{UnitParams::l2, m_L2},
+                            {UnitParams::twoTheta, m_TwoTheta},
+                            {UnitParams::efixed, Efix},
+                            {UnitParams::difa, m_DIFA},
+                            {UnitParams::difc, m_DIFC},
+                            {UnitParams::tzero, m_TZERO}});
   if (m_SourceWSUnit) {
-    m_SourceWSUnit->initialize(m_L1, m_L2, m_TwoTheta, m_Emode, Efix, 0.);
+    m_SourceWSUnit->initialize(m_L1, m_Emode,
+                               {{UnitParams::l2, m_L2},
+                                {UnitParams::twoTheta, m_TwoTheta},
+                                {UnitParams::efixed, Efix},
+                                {UnitParams::difa, m_DIFA},
+                                {UnitParams::difc, m_DIFC},
+                                {UnitParams::tzero, m_TZERO}});
   }
 }
-/** Method updates unit conversion given the index of detector parameters in the
- * array of detectors */
+/** Method updates unit conversion given the index of detector parameters in
+ * the array of detectors */
 void UnitsConversionHelper::updateConversion(size_t i) {
   switch (m_UnitCnvrsn) {
   case (CnvrtToMD::ConvertNo):
@@ -287,33 +308,47 @@ void UnitsConversionHelper::updateConversion(size_t i) {
   case (CnvrtToMD::ConvertFast):
     return;
   case (CnvrtToMD::ConvertFromTOF): {
-    double delta(std::numeric_limits<double>::quiet_NaN());
     m_TwoTheta = (*m_pTwoThetas)[i];
     m_L2 = (*m_pL2s)[i];
     double Efix = m_Efix;
     if (m_pEfixedArray)
       Efix = static_cast<double>(*(m_pEfixedArray + i));
-
-    m_TargetUnit->initialize(m_L1, m_L2, m_TwoTheta, m_Emode, Efix, delta);
+    m_DIFA = (*m_pDIFAs)[i];
+    m_DIFC = (*m_pDIFCs)[i];
+    m_TZERO = (*m_pTZEROs)[i];
+
+    m_TargetUnit->initialize(m_L1, m_Emode,
+                             {{UnitParams::l2, m_L2},
+                              {UnitParams::twoTheta, m_TwoTheta},
+                              {UnitParams::efixed, Efix},
+                              {UnitParams::difa, m_DIFA},
+                              {UnitParams::difc, m_DIFC},
+                              {UnitParams::tzero, m_TZERO}});
     return;
   }
   case (CnvrtToMD::ConvertByTOF): {
-    double delta(std::numeric_limits<double>::quiet_NaN());
     m_TwoTheta = (*m_pTwoThetas)[i];
     m_L2 = (*m_pL2s)[i];
     double Efix = m_Efix;
     if (m_pEfixedArray)
       Efix = static_cast<double>(*(m_pEfixedArray + i));
-
-    m_TargetUnit->initialize(m_L1, m_L2, m_TwoTheta, m_Emode, Efix, delta);
-    m_SourceWSUnit->initialize(m_L1, m_L2, m_TwoTheta, m_Emode, Efix, delta);
+    m_DIFA = (*m_pDIFAs)[i];
+    m_DIFC = (*m_pDIFCs)[i];
+    m_TZERO = (*m_pTZEROs)[i];
+
+    Kernel::UnitParametersMap pmap = {
+        {UnitParams::l2, m_L2},     {UnitParams::twoTheta, m_TwoTheta},
+        {UnitParams::efixed, Efix}, {UnitParams::difa, m_DIFA},
+        {UnitParams::difc, m_DIFC}, {UnitParams::tzero, m_TZERO}};
+    m_TargetUnit->initialize(m_L1, m_Emode, pmap);
+    m_SourceWSUnit->initialize(m_L1, m_Emode, pmap);
     return;
   }
   default:
     throw std::runtime_error(
         "updateConversion: unknown type of conversion requested");
   }
-}
+} // namespace MDAlgorithms
 /** do actual unit conversion from  input to oputput data
 @param   val  -- the input value which has to be converted
 @return          the input value converted into the units requested.
@@ -350,9 +385,15 @@ UnitsConversionHelper::UnitsConversionHelper(
   m_Efix = another.m_Efix;
   m_TwoTheta = another.m_TwoTheta;
   m_L2 = another.m_L2;
+  m_DIFA = another.m_DIFA;
+  m_DIFC = another.m_DIFC;
+  m_TZERO = another.m_TZERO;
   m_pTwoThetas = another.m_pTwoThetas;
   m_pL2s = another.m_pL2s;
   m_pEfixedArray = another.m_pEfixedArray;
+  m_pDIFAs = another.m_pDIFAs;
+  m_pDIFCs = another.m_pDIFCs;
+  m_pTZEROs = another.m_pTZEROs;
 
   if (another.m_SourceWSUnit)
     m_SourceWSUnit = Kernel::Unit_sptr(another.m_SourceWSUnit->clone());
@@ -363,8 +404,10 @@ UnitsConversionHelper::UnitsConversionHelper(
 UnitsConversionHelper::UnitsConversionHelper()
     : m_UnitCnvrsn(CnvrtToMD::ConvertNo), m_Factor(1), m_Power(1),
       m_Emode(-1), // undefined
-      m_L1(1), m_Efix(1), m_TwoTheta(0), m_L2(1), m_pTwoThetas(nullptr),
-      m_pL2s(nullptr), m_pEfixedArray(nullptr) {}
+      m_L1(1), m_Efix(1), m_TwoTheta(0), m_L2(1), m_DIFA(0.), m_DIFC(0.),
+      m_TZERO(0.), m_pTwoThetas(nullptr), m_pL2s(nullptr),
+      m_pEfixedArray(nullptr), m_pDIFAs(nullptr), m_pDIFCs(nullptr),
+      m_pTZEROs(nullptr) {}
 
 } // namespace MDAlgorithms
 } // namespace Mantid
diff --git a/Framework/MDAlgorithms/test/FindPeaksMDTest.h b/Framework/MDAlgorithms/test/FindPeaksMDTest.h
index 0ba29c344a1658d51272cab7e7d592002e0d4870..a92363972e578950db03b74ce4f56ea4c7586e18 100644
--- a/Framework/MDAlgorithms/test/FindPeaksMDTest.h
+++ b/Framework/MDAlgorithms/test/FindPeaksMDTest.h
@@ -7,6 +7,7 @@
 #pragma once
 
 #include "MantidAPI/FrameworkManager.h"
+#include "MantidDataObjects/LeanElasticPeaksWorkspace.h"
 #include "MantidDataObjects/PeaksWorkspace.h"
 #include "MantidKernel/PropertyWithValue.h"
 #include "MantidMDAlgorithms/FindPeaksMD.h"
@@ -29,7 +30,7 @@ static void createMDEW() {
       "CreateMDWorkspace", 18, "Dimensions", "3", "EventType", "MDEvent",
       "Extents", "-10,10,-10,10,-10,10", "Names", "Q_lab_x,Q_lab_y,Q_lab_z",
       "Units", "-,-,-", "SplitInto", "5", "SplitThreshold", "20",
-      "MaxRecursionDepth", "15", "OutputWorkspace", "MDEWS");
+      "MaxRecursionDepth", "15", "OutputWorkspace", "MDWS");
 
   // Give it an instrument
   Instrument_sptr inst =
@@ -37,7 +38,7 @@ static void createMDEW() {
   IMDEventWorkspace_sptr ws;
   TS_ASSERT_THROWS_NOTHING(
       ws = AnalysisDataService::Instance().retrieveWS<IMDEventWorkspace>(
-          "MDEWS"));
+          "MDWS"));
   ExperimentInfo_sptr ei(new ExperimentInfo());
   ei->setInstrument(inst);
   // Give it a run number
@@ -52,14 +53,14 @@ static void addPeak(size_t num, double x, double y, double z, double radius) {
   std::ostringstream mess;
   mess << num / 2 << ", " << x << ", " << y << ", " << z << ", " << radius;
   FrameworkManager::Instance().exec("FakeMDEventData", 4, "InputWorkspace",
-                                    "MDEWS", "PeakParams", mess.str().c_str());
+                                    "MDWS", "PeakParams", mess.str().c_str());
 
   // Add a center with more events (half radius, half the total), to create a
   // "peak"
   std::ostringstream mess2;
   mess2 << num / 2 << ", " << x << ", " << y << ", " << z << ", " << radius / 2;
   FrameworkManager::Instance().exec("FakeMDEventData", 4, "InputWorkspace",
-                                    "MDEWS", "PeakParams", mess2.str().c_str());
+                                    "MDWS", "PeakParams", mess2.str().c_str());
 }
 
 //=====================================================================================
@@ -91,14 +92,14 @@ public:
       FrameworkManager::Instance().exec(
           "BinMD", 14, "AxisAligned", "1", "AlignedDim0", "Q_lab_x,-10,10,100",
           "AlignedDim1", "Q_lab_y,-10,10,100", "AlignedDim2",
-          "Q_lab_z,-10,10,100", "IterateEvents", "1", "InputWorkspace", "MDEWS",
-          "OutputWorkspace", "MDEWS");
+          "Q_lab_z,-10,10,100", "IterateEvents", "1", "InputWorkspace", "MDWS",
+          "OutputWorkspace", "MDWS");
     }
 
     FindPeaksMD alg;
     TS_ASSERT_THROWS_NOTHING(alg.initialize())
     TS_ASSERT(alg.isInitialized())
-    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("InputWorkspace", "MDEWS"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("InputWorkspace", "MDWS"));
     TS_ASSERT_THROWS_NOTHING(
         alg.setPropertyValue("OutputWorkspace", outWSName));
     TS_ASSERT_THROWS_NOTHING(
@@ -160,7 +161,7 @@ public:
       // Remove workspace from the data service.
       AnalysisDataService::Instance().remove(outWSName);
     }
-    AnalysisDataService::Instance().remove("MDEWS");
+    AnalysisDataService::Instance().remove("MDWS");
   }
 
   /** Running the algo twice with same output workspace = replace the output,
@@ -225,14 +226,14 @@ public:
     FrameworkManager::Instance().exec(
         "BinMD", 14, "AxisAligned", "1", "AlignedDim0", "Q_lab_x,-10,10,100",
         "AlignedDim1", "Q_lab_y,-10,10,100", "AlignedDim2",
-        "Q_lab_z,-10,10,100", "IterateEvents", "1", "InputWorkspace", "MDEWS",
-        "OutputWorkspace", "MDEWS");
+        "Q_lab_z,-10,10,100", "IterateEvents", "1", "InputWorkspace", "MDWS",
+        "OutputWorkspace", "MDWS");
 
     FindPeaksMD alg;
     alg.setRethrows(true);
     TS_ASSERT_THROWS_NOTHING(alg.initialize());
     TS_ASSERT(alg.isInitialized());
-    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("InputWorkspace", "MDEWS"));
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("InputWorkspace", "MDWS"));
     TS_ASSERT_THROWS_NOTHING(
         alg.setPropertyValue("OutputWorkspace", "place_holder"));
     TS_ASSERT_THROWS_NOTHING(
@@ -245,7 +246,113 @@ public:
     TS_ASSERT_THROWS_NOTHING(alg.setProperty("SignalThresholdFactor", 1.3));
     TS_ASSERT_THROWS(alg.execute(), const std::runtime_error &);
 
-    AnalysisDataService::Instance().remove("MDEWS");
+    AnalysisDataService::Instance().remove("MDWS");
+  }
+
+  void do_test_LeanElastic(bool expInfo, bool histo) {
+    FrameworkManager::Instance().exec(
+        "CreateMDWorkspace", 18, "Dimensions", "3", "EventType", "MDEvent",
+        "Extents", "-10,10,-10,10,-10,10", "Names",
+        "Q_sample_x,Q_sample_y,Q_sample_z", "Units", "-,-,-", "SplitInto", "5",
+        "SplitThreshold", "20", "MaxRecursionDepth", "15", "OutputWorkspace",
+        "MDWS");
+
+    if (expInfo) {
+      Instrument_sptr inst =
+          ComponentCreationHelper::createTestInstrumentRectangular2(1, 100,
+                                                                    0.05);
+      IMDEventWorkspace_sptr ws;
+      TS_ASSERT_THROWS_NOTHING(
+          ws = AnalysisDataService::Instance().retrieveWS<IMDEventWorkspace>(
+              "MDWS"));
+      ExperimentInfo_sptr ei(new ExperimentInfo());
+      ei->setInstrument(inst);
+      // Give it a run number
+      ei->mutableRun().addProperty(
+          new PropertyWithValue<std::string>("run_number", "12345"), true);
+      ws->addExperimentInfo(ei);
+    }
+
+    addPeak(1000, 1, 2, 3, 0.1);
+    addPeak(3000, 4, 5, 6, 0.2);
+    addPeak(5000, -5, -5, 5, 0.2);
+
+    if (histo) {
+      FrameworkManager::Instance().exec(
+          "BinMD", 14, "AxisAligned", "1", "AlignedDim0",
+          "Q_sample_x,-10,10,100", "AlignedDim1", "Q_sample_y,-10,10,100",
+          "AlignedDim2", "Q_sample_z,-10,10,100", "IterateEvents", "1",
+          "InputWorkspace", "MDWS", "OutputWorkspace", "MDWS");
+    }
+
+    std::string outWSName("peaksFound");
+    FindPeaksMD alg;
+    TS_ASSERT_THROWS_NOTHING(alg.initialize())
+    TS_ASSERT(alg.isInitialized())
+    TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("InputWorkspace", "MDWS"));
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("OutputWorkspace", outWSName));
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("DensityThresholdFactor", "2.0"));
+    TS_ASSERT_THROWS_NOTHING(
+        alg.setPropertyValue("PeakDistanceThreshold", "0.7"));
+
+    if (expInfo)
+      TS_ASSERT_THROWS_NOTHING(
+          alg.setPropertyValue("OutputType", "LeanElasticPeak"));
+
+    TS_ASSERT_THROWS_NOTHING(alg.execute(););
+    TS_ASSERT(alg.isExecuted());
+
+    // Retrieve the workspace from data service.
+    LeanElasticPeaksWorkspace_sptr ws;
+    TS_ASSERT_THROWS_NOTHING(
+        ws = AnalysisDataService::Instance()
+                 .retrieveWS<LeanElasticPeaksWorkspace>(outWSName));
+    TS_ASSERT(ws);
+    if (!ws)
+      return;
+
+    // Should find all 3 peaks.
+    TS_ASSERT_EQUALS(ws->getNumberPeaks(), 3);
+
+    TS_ASSERT_DELTA(ws->getPeak(0).getQSampleFrame()[0], -5.0, 0.11);
+    TS_ASSERT_DELTA(ws->getPeak(0).getQSampleFrame()[1], -5.0, 0.11);
+    TS_ASSERT_DELTA(ws->getPeak(0).getQSampleFrame()[2], 5.0, 0.11);
+    if (expInfo) {
+      TS_ASSERT_EQUALS(ws->getPeak(0).getRunNumber(), 12345);
+    } else {
+      TS_ASSERT_EQUALS(ws->getPeak(0).getRunNumber(), -1);
+    }
+    // Bin count = density of the box / 1e6
+    double BinCount = ws->getPeak(0).getBinCount();
+    if (histo) {
+      TS_ASSERT_DELTA(BinCount, 0.08375, 0.001);
+    } else {
+      TS_ASSERT_DELTA(BinCount, 7., 001000.);
+    }
+
+    TS_ASSERT_DELTA(ws->getPeak(1).getQSampleFrame()[0], 4.0, 0.11);
+    TS_ASSERT_DELTA(ws->getPeak(1).getQSampleFrame()[1], 5.0, 0.11);
+    TS_ASSERT_DELTA(ws->getPeak(1).getQSampleFrame()[2], 6.0, 0.11);
+
+    TS_ASSERT_DELTA(ws->getPeak(2).getQSampleFrame()[0], 1.0, 0.11);
+    TS_ASSERT_DELTA(ws->getPeak(2).getQSampleFrame()[1], 2.0, 0.11);
+    TS_ASSERT_DELTA(ws->getPeak(2).getQSampleFrame()[2], 3.0, 0.11);
+
+    AnalysisDataService::Instance().remove("MDWS");
+  }
+
+  void test_exec_LeanElastic() { do_test_LeanElastic(false, false); }
+
+  void test_exec_LeanElastic_histo() { do_test_LeanElastic(false, true); }
+
+  void test_exec_LeanElastic_with_expInfo() {
+    do_test_LeanElastic(true, false);
+  }
+
+  void test_exec_LeanElastic_histo_with_expInfo() {
+    do_test_LeanElastic(true, true);
   }
 };
 
@@ -286,7 +393,7 @@ public:
     FindPeaksMD alg;
     alg.initialize();
 
-    alg.setPropertyValue("InputWorkspace", "MDEWS");
+    alg.setPropertyValue("InputWorkspace", "MDWS");
     alg.setPropertyValue("OutputWorkspace", outWSName);
     alg.setPropertyValue("DensityThresholdFactor", "2.0");
     alg.setPropertyValue("PeakDistanceThreshold", "0.7");
diff --git a/Framework/MDAlgorithms/test/MDTransfModQTest.h b/Framework/MDAlgorithms/test/MDTransfModQTest.h
index d03727735866e5ef7dcdece1fd638015d50e3c3c..73128cba17fc04c860aa00e6caa8a2d1c7129365 100644
--- a/Framework/MDAlgorithms/test/MDTransfModQTest.h
+++ b/Framework/MDAlgorithms/test/MDTransfModQTest.h
@@ -6,6 +6,7 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #pragma once
 
+#include "MantidAPI/AlgorithmManager.h"
 #include "MantidGeometry/Instrument/Goniometer.h"
 #include "MantidKernel/DeltaEMode.h"
 #include "MantidMDAlgorithms/MDTransfQ3D.h"
@@ -179,8 +180,14 @@ public:
         ModQTransf.initialize(WSDescr), const std::runtime_error &);
 
     // let's preprocess detectors positions to go any further
-    WSDescr.m_PreprDetTable =
-        WorkspaceCreationHelper::buildPreprocessedDetectorsWorkspace(ws2Dbig);
+    auto ppDets_alg = Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+        "PreprocessDetectorsToMD");
+    ppDets_alg->initialize();
+    ppDets_alg->setChild(true);
+    ppDets_alg->setProperty("InputWorkspace", ws2Dbig);
+    ppDets_alg->setProperty("OutputWorkspace", "UnitsConversionHelperTableWs");
+    ppDets_alg->execute();
+    WSDescr.m_PreprDetTable = ppDets_alg->getProperty("OutputWorkspace");
     TSM_ASSERT_THROWS_NOTHING("should initialize properly: ",
                               ModQTransf.initialize(WSDescr));
     std::vector<coord_t> coord(4);
diff --git a/Framework/MDAlgorithms/test/MDTransfQ3DTest.h b/Framework/MDAlgorithms/test/MDTransfQ3DTest.h
index 73349981b759e484ed458f5fffec7dda2ed50a87..f570a2f8d3bdcb1f9bb19554b483fb44063ca17f 100644
--- a/Framework/MDAlgorithms/test/MDTransfQ3DTest.h
+++ b/Framework/MDAlgorithms/test/MDTransfQ3DTest.h
@@ -6,6 +6,7 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #pragma once
 
+#include "MantidAPI/AlgorithmManager.h"
 #include "MantidGeometry/Instrument.h"
 #include "MantidGeometry/Instrument/Goniometer.h"
 #include "MantidKernel/DeltaEMode.h"
@@ -43,9 +44,14 @@ createTestTransform(const double efixed, const DeltaEMode::Type emode) {
   wsDescription.setMinMax({-100, -100, -100, -100}, {100, 100, 100, 100});
   wsDescription.buildFromMatrixWS(indirectInelasticWS, q3dTransform.transfID(),
                                   DeltaEMode::asString(emode), {});
-  wsDescription.m_PreprDetTable =
-      WorkspaceCreationHelper::buildPreprocessedDetectorsWorkspace(
-          indirectInelasticWS);
+  auto ppDets_alg = Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+      "PreprocessDetectorsToMD");
+  ppDets_alg->initialize();
+  ppDets_alg->setChild(true);
+  ppDets_alg->setProperty("InputWorkspace", indirectInelasticWS);
+  ppDets_alg->setProperty("OutputWorkspace", "UnitsConversionHelperTableWs");
+  ppDets_alg->execute();
+  wsDescription.m_PreprDetTable = ppDets_alg->getProperty("OutputWorkspace");
   q3dTransform.initialize(wsDescription);
 
   return std::make_tuple(q3dTransform, wsDescription);
@@ -147,9 +153,14 @@ public:
         Q3DTransf.initialize(wsDescription), const std::runtime_error &)
 
     // let's preprocess detectors positions to go any further
-    wsDescription.m_PreprDetTable =
-        WorkspaceCreationHelper::buildPreprocessedDetectorsWorkspace(
-            elasticTestWS);
+    auto ppDets_alg = Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+        "PreprocessDetectorsToMD");
+    ppDets_alg->initialize();
+    ppDets_alg->setChild(true);
+    ppDets_alg->setProperty("InputWorkspace", elasticTestWS);
+    ppDets_alg->setProperty("OutputWorkspace", "UnitsConversionHelperTableWs");
+    ppDets_alg->execute();
+    wsDescription.m_PreprDetTable = ppDets_alg->getProperty("OutputWorkspace");
     // let's set 2Theta=0 for simplicity and violate const correctness for
     // testing purposes here
     auto &TwoTheta = const_cast<std::vector<double> &>(
diff --git a/Framework/MDAlgorithms/test/PreprocessDetectorsToMDTest.h b/Framework/MDAlgorithms/test/PreprocessDetectorsToMDTest.h
index 8cb1315a6c9faf52c3d16f3df0fd77c204c49cb4..6aec885ed33f05e6f97e995c29ac13302de23cec 100644
--- a/Framework/MDAlgorithms/test/PreprocessDetectorsToMDTest.h
+++ b/Framework/MDAlgorithms/test/PreprocessDetectorsToMDTest.h
@@ -55,7 +55,7 @@ public:
     TS_ASSERT(tws);
 
     TS_ASSERT_EQUALS(4, tws->rowCount());
-    TS_ASSERT_EQUALS(8, tws->columnCount());
+    TS_ASSERT_EQUALS(11, tws->columnCount());
   }
 
   void testPreprocessDetectors() {
diff --git a/Framework/MDAlgorithms/test/UnitsConversionHelperTest.h b/Framework/MDAlgorithms/test/UnitsConversionHelperTest.h
index ca9f8b0f0889d5f8622ec5989cab3c21683d10a6..ce0d1fd493d4bb7a8e6655b59a845b03a22d288e 100644
--- a/Framework/MDAlgorithms/test/UnitsConversionHelperTest.h
+++ b/Framework/MDAlgorithms/test/UnitsConversionHelperTest.h
@@ -6,6 +6,7 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #pragma once
 
+#include "MantidAPI/AlgorithmManager.h"
 #include "MantidAPI/FrameworkManager.h"
 #include "MantidAPI/NumericAxis.h"
 #include "MantidKernel/PhysicalConstants.h"
@@ -45,13 +46,19 @@ public:
 
     auto pSourceWSUnit = UnitFactory::Instance().create("Wavelength");
     auto pWSUnit = UnitFactory::Instance().create("MomentumTransfer");
-    double delta;
     double L1(10), L2(10), TwoTheta(0.1), efix(10);
+
     int emode(0);
     TS_ASSERT_THROWS_NOTHING(
-        pWSUnit->initialize(L1, L2, TwoTheta, emode, efix, delta));
+        pWSUnit->initialize(L1, emode,
+                            {{UnitParams::l2, L2},
+                             {UnitParams::twoTheta, TwoTheta},
+                             {UnitParams::efixed, efix}}));
     TS_ASSERT_THROWS_NOTHING(
-        pSourceWSUnit->initialize(L1, L2, TwoTheta, emode, efix, delta));
+        pSourceWSUnit->initialize(L1, emode,
+                                  {{UnitParams::l2, L2},
+                                   {UnitParams::twoTheta, TwoTheta},
+                                   {UnitParams::efixed, efix}}));
 
     double X0(5);
     double tof(0);
@@ -287,6 +294,13 @@ public:
     ws2D = WorkspaceCreationHelper::createProcessedInelasticWS(
         L2, polar, azimutal, numBins, -1, 3, 3);
 
-    detLoc = WorkspaceCreationHelper::buildPreprocessedDetectorsWorkspace(ws2D);
+    auto ppDets_alg = Mantid::API::AlgorithmManager::Instance().createUnmanaged(
+        "PreprocessDetectorsToMD");
+    ppDets_alg->initialize();
+    ppDets_alg->setChild(true);
+    ppDets_alg->setProperty("InputWorkspace", ws2D);
+    ppDets_alg->setProperty("OutputWorkspace", "UnitsConversionHelperTableWs");
+    ppDets_alg->execute();
+    detLoc = ppDets_alg->getProperty("OutputWorkspace");
   }
 };
\ No newline at end of file
diff --git a/Framework/PythonInterface/mantid/api/src/Exports/IPeak.cpp b/Framework/PythonInterface/mantid/api/src/Exports/IPeak.cpp
index d9398e8d6d986f8b56b4da973d78e823db2d7a9c..03e4be767ee78c3a2951ca0fa23360b1de848d8e 100644
--- a/Framework/PythonInterface/mantid/api/src/Exports/IPeak.cpp
+++ b/Framework/PythonInterface/mantid/api/src/Exports/IPeak.cpp
@@ -209,8 +209,6 @@ void export_IPeak() {
            "For :class:`~mantid.geometry.RectangularDetector` s only, returns "
            "the column (x) of the pixel of the "
            ":class:`~mantid.geometry.Detector`.")
-      .def("getDetPos", &IPeak::getDetPos, arg("self"),
-           "Return the :class:`~mantid.geometry.Detector` position vector")
       .def("getL1", &IPeak::getL1, arg("self"),
            "Return the L1 flight path length (source to "
            ":class:`~mantid.api.Sample`), in meters. ")
diff --git a/Framework/PythonInterface/mantid/kernel/src/Exports/Unit.cpp b/Framework/PythonInterface/mantid/kernel/src/Exports/Unit.cpp
index dd8458c298e0be85627775e99e01fe5f70a95275..9d750c5d1ba844b04f98ea8a73817d807abe671f 100644
--- a/Framework/PythonInterface/mantid/kernel/src/Exports/Unit.cpp
+++ b/Framework/PythonInterface/mantid/kernel/src/Exports/Unit.cpp
@@ -8,6 +8,7 @@
 #include "MantidPythonInterface/core/GetPointer.h"
 
 #include <boost/python/class.hpp>
+#include <boost/python/enum.hpp>
 #include <boost/python/register_ptr_to_python.hpp>
 #include <boost/python/tuple.hpp>
 
@@ -57,6 +58,15 @@ void export_Unit() {
 
   register_ptr_to_python<std::shared_ptr<Unit>>();
 
+  enum_<Mantid::Kernel::UnitParams>("UnitParams")
+      .value("l2", Mantid::Kernel::UnitParams::l2)
+      .value("twoTheta", Mantid::Kernel::UnitParams::twoTheta)
+      .value("delta", Mantid::Kernel::UnitParams::delta)
+      .value("efixed", Mantid::Kernel::UnitParams::efixed)
+      .value("difa", Mantid::Kernel::UnitParams::difa)
+      .value("difc", Mantid::Kernel::UnitParams::difc)
+      .value("tzero", Mantid::Kernel::UnitParams::tzero);
+
   class_<Unit, boost::noncopyable>("Unit", no_init)
       .def("name", &deprecatedName, arg("self"),
            "Return the full name of the unit (deprecated, use caption)")
diff --git a/Framework/PythonInterface/mantid/kernel/src/Exports/UnitConversion.cpp b/Framework/PythonInterface/mantid/kernel/src/Exports/UnitConversion.cpp
index 7e371ad185ef594a874a0b836d2a9ab74b4b32ab..0579e67257838d2146d691e889f5b66a87292b60 100644
--- a/Framework/PythonInterface/mantid/kernel/src/Exports/UnitConversion.cpp
+++ b/Framework/PythonInterface/mantid/kernel/src/Exports/UnitConversion.cpp
@@ -6,21 +6,42 @@
 // SPDX - License - Identifier: GPL - 3.0 +
 #include "MantidKernel/UnitConversion.h"
 #include <boost/python/class.hpp>
+#include <boost/python/suite/indexing/map_indexing_suite.hpp>
 
 using Mantid::Kernel::DeltaEMode;
 using Mantid::Kernel::UnitConversion;
 using namespace boost::python;
 
+double deprecatedSignature(const std::string &src, const std::string &dest,
+                           const double srcValue, const double l1,
+                           const double l2, const double theta,
+                           const DeltaEMode::Type emode, const double efixed) {
+  PyErr_Warn(
+      PyExc_DeprecationWarning,
+      ".run(src, dest, srcValue, l1, l2, theta, emode, efixed) is deprecated. "
+      "Use .run(src, dest, srcValue, l1, emode, params) instead.");
+  return UnitConversion::run(src, dest, srcValue, l1, l2, theta, emode, efixed);
+}
+
 void export_UnitConversion() {
   // Function pointer typedef
-  using StringVersion = double (*)(
+  using newStringVersion = double (*)(
       const std::string &, const std::string &, const double, const double,
-      const double, const double, const DeltaEMode::Type, const double);
+      const DeltaEMode::Type, const Mantid::Kernel::UnitParametersMap &);
+
+  class_<std::unordered_map<Mantid::Kernel::UnitParams, double>>(
+      "UnitParametersMap")
+      .def(map_indexing_suite<
+           std::unordered_map<Mantid::Kernel::UnitParams, double>>());
 
   class_<UnitConversion, boost::noncopyable>("UnitConversion", no_init)
-      .def("run", (StringVersion)&UnitConversion::run,
+      .def("run", &deprecatedSignature,
            (arg("src"), arg("dest"), arg("srcValue"), arg("l1"), arg("l2"),
             arg("theta"), arg("emode"), arg("efixed")),
+           "Performs a unit conversion on a single value (deprecated).")
+      .def("run", (newStringVersion)&UnitConversion::run,
+           (arg("src"), arg("dest"), arg("srcValue"), arg("l1"), arg("emode"),
+            arg("params")),
            "Performs a unit conversion on a single value.")
       .staticmethod("run");
 }
diff --git a/Framework/PythonInterface/plugins/CMakeLists.txt b/Framework/PythonInterface/plugins/CMakeLists.txt
index 382277f5ca6f4487d19d00a3ec94b86203e8466f..4969fc4d8bd842634fd73b5e6b3cf287a350d282 100644
--- a/Framework/PythonInterface/plugins/CMakeLists.txt
+++ b/Framework/PythonInterface/plugins/CMakeLists.txt
@@ -73,6 +73,7 @@ set(PYTHON_PLUGINS
     algorithms/FitIncidentSpectrum.py
     algorithms/GSASIIRefineFitPeaks.py
     algorithms/GenerateGroupingSNSInelastic.py
+    algorithms/GenerateLogbook.py
     algorithms/GetEiT0atSNS.py
     algorithms/GetIPTS.py
     algorithms/GetLiveInstrumentValue.py
diff --git a/Framework/PythonInterface/plugins/algorithms/AlignComponents.py b/Framework/PythonInterface/plugins/algorithms/AlignComponents.py
index b08090217db2eaa06a5ab29fdd6d8a5a6d198014..78105372ae2dfabd8cbf337a167840368a5735a1 100644
--- a/Framework/PythonInterface/plugins/algorithms/AlignComponents.py
+++ b/Framework/PythonInterface/plugins/algorithms/AlignComponents.py
@@ -214,6 +214,17 @@ class AlignComponents(PythonAlgorithm):
                       "GammaRotation", "MinGammaRotation", "MaxGammaRotation"]
         [self.setPropertyGroup(name, "Rotation") for name in properties]
 
+        #
+        # Minimization Properties
+        self.declareProperty(name='Minimizer', defaultValue='L-BFGS-B', direction=Direction.Input,
+                             validator=StringListValidator(['L-BFGS-B', 'differential_evolution']),
+                             doc='Minimizer to Use')
+        self.declareProperty(name='MaxIterations', defaultValue=100, direction=Direction.Input,
+                             doc='Maximum number of iterations for minimizer differential_evolution')
+
+        properties = ['Minimizer', 'MaxIterations']
+        [self.setPropertyGroup(name, "Minimization") for name in properties]
+
     def validateInputs(self):
         """
         Does basic validation for inputs
@@ -387,7 +398,7 @@ class AlignComponents(PythonAlgorithm):
                     instrument = api.mtd[wks_name].getInstrument()
                     name_finder = {'Source': instrument.getSource().getName(),
                                    'Sample': instrument.getSample().getName()}
-                    component_adjustments = [name_finder[component]] + xmap[:3] + [0.0] * 4 # no rotations
+                    component_adjustments = [name_finder[component]] + xmap[:3] + [0.0] * 4  # no rotations
                     adjustments_table.addRow(component_adjustments)
 
                 # Need to grab the component again, as things have changed
@@ -440,12 +451,17 @@ class AlignComponents(PythonAlgorithm):
                     boundsList.append((self._initialPos[iopt] + self.getProperty("Min"+opt).value,
                                        self._initialPos[iopt] + self.getProperty("Max"+opt).value))
 
-            # scipy.opimize.minimize with the L-BFGS-B algorithm
-            results: OptimizeResult = minimize(self._minimisation_func, x0=x0List,
-                                               method='L-BFGS-B',
-                                               args=(wks_name, component, firstIndex, lastIndex),
-                                               bounds=boundsList)
-
+            minimizer_selection = self.getProperty('Minimizer').value
+            if minimizer_selection == 'L-BFGS-B':
+                # scipy.opimize.minimize with the L-BFGS-B algorithm
+                results: OptimizeResult = minimize(self._minimisation_func, x0=x0List, method='L-BFGS-B',
+                                                   args=(wks_name, component, firstIndex, lastIndex),
+                                                   bounds=boundsList)
+            elif minimizer_selection == 'differential_evolution':
+                results: OptimizeResult = differential_evolution(self._minimisation_func,
+                                                                 bounds=boundsList,
+                                                                 args=(wks_name, component, firstIndex, lastIndex),
+                                                                 maxiter=self.getProperty('MaxIterations').value)
             # Apply the results to the output workspace
             xmap = self._mapOptions(results.x)
 
@@ -475,7 +491,7 @@ class AlignComponents(PythonAlgorithm):
             prog.report()
         api.DeleteWorkspace(wks_name)
         self.setProperty("OutputWorkspace", output_workspace)
-        logger.notice("Results applied to workspace "+wks_name)
+        logger.notice("Results applied to workspace " + wks_name)
 
     def _initialize_adjustments_table(self, table_name):
         r"""Create a table with appropriate column names for saving the adjustments to each component"""
@@ -499,7 +515,6 @@ class AlignComponents(PythonAlgorithm):
         indexes_and_titles = [(index, title) for index, title in enumerate(table_tofs.getColumnNames()) if '@' in title]
         column_indexes, titles = list(zip(*indexes_and_titles))
         peak_tofs = np.array([table_tofs.column(i) for i in column_indexes])  # shape = (peak_count, detector_count)
-        # sort by increasing peak centers (d-spacing units)
         peak_centers = np.array([float(title.replace('@', '')) for title in titles])
         permutation = np.argsort(peak_centers)  # reorder of indices guarantee increase in d-spacing
         peak_tofs = peak_tofs[permutation]  # sort by increasing d-spacing
@@ -629,7 +644,7 @@ class AlignComponents(PythonAlgorithm):
 
 
 try:
-    from scipy.optimize import minimize, OptimizeResult
+    from scipy.optimize import minimize, differential_evolution, OptimizeResult
     AlgorithmFactory.subscribe(AlignComponents)
 except ImportError:
     logger.debug('Failed to subscribe algorithm AlignComponets; cannot import minimize from scipy.optimize')
diff --git a/Framework/PythonInterface/plugins/algorithms/GenerateLogbook.py b/Framework/PythonInterface/plugins/algorithms/GenerateLogbook.py
new file mode 100644
index 0000000000000000000000000000000000000000..54cd919abf976c50d8f8f945aecace363d704aaf
--- /dev/null
+++ b/Framework/PythonInterface/plugins/algorithms/GenerateLogbook.py
@@ -0,0 +1,376 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2021 ISIS Rutherford Appleton Laboratory UKRI,
+#   NScD Oak Ridge National Laboratory, European Spallation Source,
+#   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+# SPDX - License - Identifier: GPL - 3.0 +
+
+from mantid import config
+from mantid.api import AlgorithmFactory, FileAction, FileProperty, \
+    ITableWorkspaceProperty, Progress, PythonAlgorithm
+from mantid.kernel import Direction, IntArrayBoundedValidator, \
+    StringListValidator, StringMandatoryValidator
+from mantid.simpleapi import *
+
+import fnmatch
+import h5py
+import numpy as np
+import os
+import re
+
+
+class GenerateLogbook(PythonAlgorithm):
+
+    _data_directory = None
+    _facility = None
+    _instrument = None
+    _numor_range = None
+    _metadata_headers = None
+    _metadata_entries = None
+
+    def category(self):
+        return 'Utility'
+
+    def summary(self):
+        return 'Generates logbook containing meta-data specific to the instrument and technique used to obtain the raw data'
+
+    def name(self):
+        return 'GenerateLogbook'
+
+    def validateInputs(self):
+        issues = dict()
+
+        instrument = self.getPropertyValue('Instrument')
+        ws_tmp = CreateSingleValuedWorkspace()
+        try:
+            LoadParameterFile(Workspace=ws_tmp, Filename=instrument + '_Parameters.xml')
+        except Exception as e:
+            self.log().error(str(e))
+            issues['Instrument'] = 'There is no parameter file for {} instrument.'.format(instrument)
+        DeleteWorkspace(Workspace=ws_tmp)
+
+        if not self.getProperty('NumorRange').isDefault:
+            numor_range = self.getProperty('NumorRange').value
+            if len(numor_range) < 2:
+                issues['NumorRange'] = 'Please provide both bottom and upper numor limits.'
+            if numor_range[0] > numor_range[-1]:
+                issues['NumorRange'] = 'The upper limit must be larger than the bottom one.'
+
+        if not self.getProperty('CustomEntries').isDefault:
+            custom_entries = self.getPropertyValue('CustomEntries')
+            custom_entries = custom_entries.split(',')
+            if not self.getProperty('CustomHeaders').isDefault:
+                custom_headers = self.getPropertyValue('CustomHeaders')
+                custom_headers = custom_headers.split(',')
+                if len(custom_entries) != len(custom_headers):
+                    issues['CustomHeaders'] = 'Provide none or as many headers as custom entries.'
+
+        return issues
+
+    def PyInit(self):
+
+        self.declareProperty(FileProperty('Directory', '',
+                                          action=FileAction.Directory),
+                             doc='Path to directory containing data files for logging.')
+
+        self.declareProperty(ITableWorkspaceProperty('OutputWorkspace', '',
+                                                     direction=Direction.Output),
+                             doc='The output table workspace.')
+
+        self.declareProperty("NumorRange", [0, 0],
+                             direction=Direction.Input,
+                             validator=IntArrayBoundedValidator(lower=0),
+                             doc='Numor range or a list of numors to be analysed in the directory.')
+
+        facilities = StringListValidator(list(config.getFacilityNames()))
+        self.declareProperty(name='Facility', defaultValue='ILL',
+                             validator=facilities,
+                             direction=Direction.Input,
+                             doc='Facility the data belongs to.')
+
+        self.declareProperty('Instrument', '',
+                             validator=StringMandatoryValidator(),
+                             direction=Direction.Input,
+                             doc='Instrument the data has been collected with.')
+
+        self.declareProperty(FileProperty('OutputFile', '',
+                                          extensions=".csv",
+                                          action=FileAction.OptionalSave),
+                             doc='Comma-separated output file.')
+
+        self.declareProperty('OptionalHeaders', '',
+                             doc='Names of optional metadata to be included in the logbook. Entries need to be specified'
+                                 'in the instrument IPF.')
+
+        self.declareProperty('CustomEntries', '',
+                             doc='Custom NeXus paths for additional metadata to be included in the logbook.')
+
+        self.declareProperty('CustomHeaders', '',
+                             doc='Names of those additional custom entries.')
+
+    def _prepare_file_array(self):
+        """Prepares a list containing the NeXus files in the specified directory."""
+        instrument_name_len = 0
+        if self._facility != 'ILL':
+            instrument_name_len = len(self._instrument)
+
+        file_list = []
+        for file in sorted(fnmatch.filter(os.listdir(self._data_directory), '*.nxs')):
+            try:
+                numor = int(os.path.splitext(file[instrument_name_len:])[0])
+                if self._numor_range is None or numor in self._numor_range:
+                    file_list.append(os.path.splitext(file)[0])
+            except (ValueError, OverflowError):
+                self.log().debug("File {} cannot be cast into an integer numor".format(file))
+                continue
+        if file_list == list():
+            raise RuntimeError("There are no files in {} with specified numors.".format(self._data_directory))
+        return file_list
+
+    def _get_default_entries(self):
+        """Gets default and optional metadata entries using the specified instrument IPF."""
+        self._metadata_entries = []
+        self._metadata_headers = ['run_number']
+        tmp_instr = self._instrument + '_tmp'
+        # Load empty instrument to access parameters defining metadata entries to be searched
+        LoadEmptyInstrument(Filename=self._instrument + "_Definition.xml", OutputWorkspace=tmp_instr)
+        parameters = mtd[tmp_instr].getInstrument()
+        try:
+            logbook_default_parameters = (parameters.getStringParameter('logbook_default_parameters')[0]).split(',')
+            for parameter in logbook_default_parameters:
+                parameter = parameter.split(':')
+                self._metadata_headers.append(str(parameter[0]).strip()) # removes whitespaces
+                self._metadata_entries.append(parameter[1])
+        except IndexError:
+            raise RuntimeError("The default logbook entries and headers are not defined for {}".format(self._instrument))
+        default_entries = list(self._metadata_entries)
+
+        if not self.getProperty('OptionalHeaders').isDefault:
+            try:
+                logbook_optional_parameters = parameters.getStringParameter('logbook_optional_parameters')[0]
+            except IndexError:
+                raise RuntimeError("Optional headers are requested but are not defined for {}.".format(self._instrument))
+            else:
+                logbook_optional_parameters = logbook_optional_parameters.split(',')
+                # create tmp dictionary with headers and paths read from IPF with whitespaces removed from the header
+                optional_entries = {str(entry.split(':')[0]).strip() : entry.split(':')[1]
+                                    for entry in logbook_optional_parameters}
+                requested_headers = self.getPropertyValue('OptionalHeaders')
+                if str(requested_headers).casefold() == 'all':
+                    for header in optional_entries:
+                        self._metadata_headers.append(header)
+                        self._metadata_entries.append(optional_entries[header])
+                else:
+                    for header in requested_headers.split(','):
+                        if header in optional_entries:
+                            self._metadata_headers.append(header)
+                            self._metadata_entries.append(optional_entries[header])
+                        else:
+                            raise RuntimeError("Header {} requested, but not defined for {}.".format(header,
+                                                                                                     self._instrument))
+
+        if not self.getProperty('CustomEntries').isDefault:
+            logbook_custom_entries = self.getPropertyValue('CustomEntries')
+            logbook_custom_entries = logbook_custom_entries.split(',')
+            self._metadata_entries += logbook_custom_entries
+            logbook_custom_headers = [""]*len(logbook_custom_entries)
+            operators = ["+", "-", "*", "//"]
+            if self.getProperty('CustomHeaders').isDefault:
+                # derive headers from custom entries:
+                for entry_no, entry in enumerate(logbook_custom_entries):
+                    if any(op in entry for op in operators):
+                        list_entries, binary_operations = self._process_regex(entry)
+                        header = ""
+                        for split_entry_no, split_entry in enumerate(list_entries):
+                            # always use two strings around the final '/' for more informative header
+                            partial_header = split_entry[split_entry.rfind('/', 0,
+                                                                           split_entry.rfind('/') - 1) + 1:]
+                            header += partial_header
+                            header += binary_operations[split_entry_no]\
+                                if split_entry_no < len(binary_operations) else ""
+                        logbook_custom_headers[entry_no] = header
+                    else:
+                        # always use two strings around the final '/' for more informative header
+                        logbook_custom_headers[entry_no] = entry[entry.rfind('/', 0, entry.rfind('/') - 1) + 1:]
+            else:
+                logbook_custom_headers = self.getPropertyValue('CustomHeaders')
+                logbook_custom_headers = logbook_custom_headers.split(',')
+            self._metadata_headers += logbook_custom_headers
+        DeleteWorkspace(Workspace=tmp_instr)
+        return default_entries
+
+    def _verify_contains_metadata(self, data_array):
+        """Verifies that the raw data indeed contains the desired meta-data to be logged."""
+        default_entries = self._get_default_entries()
+        data_path = os.path.join(self._data_directory, data_array[0] + '.nxs')
+        # check only if default entries exist in the first file in the directory
+        with h5py.File(data_path, 'r') as f:
+            for entry in default_entries:
+                try:
+                    f.get(entry)[0]
+                except TypeError:
+                    self.log().warning("The requested entry: {}, is not present in the raw data. ".format(entry))
+
+    def _prepare_logbook_ws(self):
+        """Prepares the TableWorkspace logbook for filling with entries, sets up the headers."""
+        logbook_ws = self.getPropertyValue('OutputWorkspace')
+        CreateEmptyTableWorkspace(OutputWorkspace=logbook_ws)
+        for headline in self._metadata_headers:
+            mtd[logbook_ws].addColumn("str", headline)
+        return logbook_ws
+
+    def _perform_binary_operations(self, values, binary_operations, operations):
+        """Performs binary arithmetic operations based on the list of operations
+        to perform and list of values."""
+        while True:
+            operation = [(ind, ind+1, op) for ind, op in enumerate(binary_operations)
+                         if op in operations]
+            if operation == list():
+                break
+            ind1, ind2, op = operation[0]
+            if op == "+":
+                new_val = values[ind1] + values[ind2]
+            elif op == "-":
+                new_val = values[ind1] - values[ind2]
+            elif op == "*":
+                new_val = values[ind1] * values[ind2]
+            elif op == "//":
+                if values[ind2] == 0:
+                    self.log().warning("Divisor is equal to 0.")
+                    new_val = 'N/A'
+                else:
+                    new_val = values[ind1] / values[ind2]
+            else:
+                raise RuntimeError("Unknown operation: {}".format(operation))
+            values[ind1] = new_val
+            values.pop(ind2)
+            binary_operations.pop(ind1)
+        return values, binary_operations
+
+    @staticmethod
+    def _get_index(entry_name):
+        try:
+            index = int(entry_name[entry_name.rfind('/')+1:])
+        except ValueError:
+            index = 0
+            new_name = entry_name
+        else:
+            new_name = entry_name[:entry_name.rfind('/')]
+        return new_name, index
+
+    @staticmethod
+    def _process_regex(entry):
+        regex_all = r'(\*)|(//)|(\+)|(\-)'
+        p = re.compile(regex_all)
+        list_entries = []
+        binary_operations = []
+        prev_pos = 0
+        for obj in p.finditer(entry):
+            list_entries.append(entry[prev_pos:obj.span()[0]])
+            prev_pos = obj.span()[1]
+            binary_operations.append(obj.group())
+        list_entries.append(entry[prev_pos:])  # add the last remaining file
+        return list_entries, binary_operations
+
+    def _fill_logbook(self, logbook_ws, data_array, progress):
+        """Fills out the logbook with the requested meta-data."""
+        n_entries = len(self._metadata_headers)
+        entry_not_found_msg = "The requested entry: {}, is not present in the raw data"
+        operators = ["+","-","*","//"]
+        cache_entries_ops = {}
+
+        for file_no, file_name in enumerate(data_array):
+            # reporting progress each 10% of the data
+            if file_no % (len(data_array)/10) == 0:
+                progress.report("Filling logbook table...")
+            file_path = os.path.join(self._data_directory, file_name + '.nxs')
+            with h5py.File(file_path, 'r') as f:
+                rowData = np.empty(n_entries, dtype=object)
+                rowData[0] = str(file_name)
+                for entry_no, entry in enumerate(self._metadata_entries, 1):
+                    if any(op in entry for op in operators):
+                        if entry in cache_entries_ops:
+                            list_entries, binary_operations = cache_entries_ops[entry]
+                            binary_operations = binary_operations.copy()
+                        else:
+                            list_entries, binary_operations = self._process_regex(entry)
+                            cache_entries_ops[entry] = (list_entries, list(binary_operations))
+                        # load all entries from the file
+                        values = [0]*len(list_entries)
+                        for split_entry_no, split_entry in enumerate(list_entries):
+                            try:
+                                split_entry, index = self._get_index(split_entry)
+                                data = f.get(split_entry)[index]
+                            except TypeError:
+                                values[0] = "Not found"
+                                binary_operations = []
+                                self.log().warning(entry_not_found_msg.format(entry))
+                                break
+                            else:
+                                if isinstance(data, np.bytes_):
+                                    if any(op in operators[1:] for op in binary_operations):
+                                        self.log().warning("Only 'sum' operation is supported for string entries")
+                                        values[0] = "N/A"
+                                        binary_operations = []
+                                        break
+                                    else:
+                                        data = data.decode('utf-8')
+                                        data = data.replace(',', ';')  # needed for CSV output
+                            values[split_entry_no] = data
+                        values, binary_operations = self._perform_binary_operations(values, binary_operations,
+                                                                                    operations=['*', '//'])
+                        values, _ = self._perform_binary_operations(values, binary_operations,
+                                                                    operations=['+', '-'])
+                        if isinstance(values, np.ndarray):
+                            tmp_data = ""
+                            for value in values[0]:
+                                tmp_data += str(value) + ','
+                            rowData[entry_no] = tmp_data[:-1]
+                        else:
+                            rowData[entry_no] = str(values[0]).strip()
+                    else:
+                        try:
+                            entry, index = self._get_index(entry)
+                            data = f.get(entry)[index]
+                        except TypeError:
+                            rowData[entry_no] = "Not found"
+                            self.log().warning(entry_not_found_msg.format(entry))
+                        else:
+                            if isinstance(data, np.ndarray):
+                                tmp_data = ""
+                                for array in data:
+                                    tmp_data += ",".join(array)
+                                data = tmp_data
+                            elif isinstance(data, np.bytes_):
+                                data = data.decode('utf-8')
+                                data = data.replace(',', ';') # needed for CSV output
+                            rowData[entry_no] = str(data).strip()
+                mtd[logbook_ws].addRow(rowData)
+
+    def _store_logbook_as_csv(self, logbook_ws):
+        """Calls algorithm that will store the logbook TableWorkspace in the specified location."""
+        SaveAscii(InputWorkspace=logbook_ws, Filename=self.getPropertyValue('OutputFile'),
+                  Separator='CSV')
+
+    def PyExec(self):
+        self._data_directory = self.getPropertyValue('Directory')
+        self._facility = self.getPropertyValue('Facility')
+        self._instrument = self.getPropertyValue('Instrument')
+        if not self.getProperty('NumorRange').isDefault:
+            self._numor_range = self.getProperty('NumorRange').value
+        progress = Progress(self, start=0.0, end=1.0, nreports=15)
+        progress.report("Preparing file list")
+        data_array = self._prepare_file_array()
+        progress.report("Verifying conformity")
+        self._verify_contains_metadata(data_array)
+        progress.report("Preparing logbook table")
+        logbook_ws = self._prepare_logbook_ws()
+        self._fill_logbook(logbook_ws, data_array, progress)
+        if not self.getProperty('OutputFile').isDefault:
+            progress.report("Saving logbook as CSV")
+            self._store_logbook_as_csv(logbook_ws)
+        progress.report("Done")
+        self.setProperty('OutputWorkspace', mtd[logbook_ws])
+
+
+AlgorithmFactory.subscribe(GenerateLogbook)
diff --git a/Framework/PythonInterface/plugins/algorithms/HB2AReduce.py b/Framework/PythonInterface/plugins/algorithms/HB2AReduce.py
index c8462f5d2f861d8b3176d71dbc5f530a8e29921d..7e00cd32079d8f6fda098456af028d47656ef861 100644
--- a/Framework/PythonInterface/plugins/algorithms/HB2AReduce.py
+++ b/Framework/PythonInterface/plugins/algorithms/HB2AReduce.py
@@ -14,6 +14,7 @@ from mantid import logger
 import numpy as np
 import datetime
 import os
+import os.path
 import re
 import warnings
 
@@ -229,7 +230,7 @@ class HB2AReduce(PythonAlgorithm):
         # Get either vcorr file or vanadium data
         vanadium_count, vanadium_monitor, vcorr = self.get_vanadium(detector_mask, data['m1'][0],
                                                                     data['colltrans'][0], exp,
-                                                                    indir)
+                                                                    indir, metadata)
 
         def_x = self.getProperty("DefX").value
         if not def_x:
@@ -318,7 +319,7 @@ class HB2AReduce(PythonAlgorithm):
         detector_mask[exclude_detectors - 1] = False
         return detector_mask
 
-    def get_vanadium(self, detector_mask, m1, colltrans, exp, indir):
+    def get_vanadium(self, detector_mask, m1, colltrans, exp, indir, metadata):
         """
         This function returns either (vanadium_count, vanadium_monitor, None) or
         (None, None, vcorr) depending what type of file is provided by getProperty("Vanadium")
@@ -341,11 +342,21 @@ class HB2AReduce(PythonAlgorithm):
             # m1 = 0 -> Ge 115, 1.54A
             # m1 = 9.45 -> Ge 113, 2.41A
             # colltrans is the collimator position, whether in or out of the beam
-            # colltrans = 0 -> IN
-            # colltrans = +/-80 -> OUT
-            vcorr_filename = 'HB2A_{}__Ge_{}_{}_vcorr.txt'.format(
-                exp, 115 if np.isclose(m1, 0, atol=0.1) else 113,
-                "IN" if np.isclose(colltrans, 0, atol=0.1) else "OUT")
+            new_convention = np.datetime64(datetime.datetime(2021, 2, 23))
+            date_created = self.get_date(metadata)
+            if date_created >= new_convention:
+                # colltrans = 0 -> OUT
+                # colltrans = +/-80 -> IN
+                vcorr_filename = 'HB2A_{}__Ge_{}_{}_vcorr.txt'.format(
+                    exp, 115 if np.isclose(m1, 0, atol=0.1) else 113,
+                    "OUT" if np.isclose(colltrans, 0, atol=0.1) else "IN")
+            elif date_created < new_convention:
+                # colltrans = +/-80 -> OUT
+                # colltrans = 0 -> IN
+                vcorr_filename = 'HB2A_{}__Ge_{}_{}_vcorr.txt'.format(
+                    exp, 115 if np.isclose(m1, 0, atol=0.1) else 113,
+                    "IN" if np.isclose(colltrans, 0, atol=0.1) else "OUT")
+
         vcorr_filename = os.path.join(indir, vcorr_filename)
         logger.notice("Using vcorr file: {}".format(vcorr_filename))
         if not os.path.isfile(vcorr_filename):
@@ -353,6 +364,13 @@ class HB2AReduce(PythonAlgorithm):
 
         return None, None, np.genfromtxt(vcorr_filename)[detector_mask]
 
+    def get_date(self, metadata):
+        # Get correct start time
+        date_created = np.datetime64(
+            datetime.datetime.strptime(metadata['time'] + ' ' + metadata['date'],
+                                       '%I:%M:%S %p %m/%d/%Y'))
+        return date_created
+
     def process(self,
                 counts,
                 scale,
diff --git a/Framework/PythonInterface/plugins/algorithms/IntegratePeaksProfileFitting.py b/Framework/PythonInterface/plugins/algorithms/IntegratePeaksProfileFitting.py
index d5bcffc2f38f4f61b32850aad3603e2a0feaf942..0635480750f4bdbe3b0565f6b071727b2094dcd2 100644
--- a/Framework/PythonInterface/plugins/algorithms/IntegratePeaksProfileFitting.py
+++ b/Framework/PythonInterface/plugins/algorithms/IntegratePeaksProfileFitting.py
@@ -254,7 +254,7 @@ class IntegratePeaksProfileFitting(PythonAlgorithm):
                     strongPeakParamsToSend = strongPeakParams
 
                 # Will allow forced weak and edge peaks to be fit using a neighboring peak profile
-                Y3D, goodIDX, pp_lambda, params = BVGFT.get3DPeak(peak, peaks_ws, box, padeCoefficients,qMask,
+                Y3D, goodIDX, pp_lambda, params = BVGFT.get3DPeak(peak, peaks_ws, box, padeCoefficients, qMask,
                                                                   nTheta=nTheta, nPhi=nPhi, plotResults=False,
                                                                   zBG=zBG,fracBoxToHistogram=1.0,bgPolyOrder=1,
                                                                   strongPeakParams=strongPeakParamsToSend,
diff --git a/Framework/PythonInterface/plugins/algorithms/SaveHKLCW.py b/Framework/PythonInterface/plugins/algorithms/SaveHKLCW.py
index 85eca56ccbe945c51cff6ca379e557922865ba7d..62453bc4be8fece65043bf208eb7045519510dfe 100644
--- a/Framework/PythonInterface/plugins/algorithms/SaveHKLCW.py
+++ b/Framework/PythonInterface/plugins/algorithms/SaveHKLCW.py
@@ -71,17 +71,19 @@ class SaveHKLCW(PythonAlgorithm):
             if directionCosines:
                 U = peak_ws.sample().getOrientedLattice().getU()
                 sample_pos = peak_ws.getInstrument().getSample().getPos()
-                q_reverse_incident = peak_ws.getInstrument().getSource().getPos() - sample_pos
-                q_reverse_incident = np.array(q_reverse_incident) / q_reverse_incident.norm()
+                source_pos = peak_ws.getInstrument().getSource().getPos()
+                ki_n = sample_pos - source_pos  # direction of incident wavevector
+                ki_n = ki_n * (1. / ki_n.norm())
 
             for p in peak_ws:
                 if directionCosines:
                     R = p.getGoniometerMatrix()
                     RU = np.dot(R, U)
-                    q_diffracted = p.getDetPos() - sample_pos
-                    q_diffracted = np.array(q_diffracted) / q_diffracted.norm()
-                    dir_cos_1 = np.dot(RU.T, q_reverse_incident)
-                    dir_cos_2 = np.dot(RU.T, q_diffracted)
+                    ki = ki_n * (2 * np.pi / p.getWavelength())
+                    kf_n = ki - p.getQLabFrame()  # direction of scattered wavevector
+                    kf_n = kf_n * (1. / kf_n.norm())
+                    dir_cos_1 = np.dot(RU.T, -ki_n)  # notice ki direction is reversed
+                    dir_cos_2 = np.dot(RU.T, kf_n)
                     f.write(
                         "{:4.0f}{:4.0f}{:4.0f}{:8.2f}{:8.2f}{:4d}{:8.5f}{:8.5f}{:8.5f}{:8.5f}{:8.5f}{:8.5f}\n"
                         .format(p.getH(), p.getK(), p.getL(), p.getIntensity(),
diff --git a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/CorelliPowderCalibrationCreate.py b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/CorelliPowderCalibrationCreate.py
index 1dbe7f1cf8b5b70901043e2c08584d0b2a95e7bf..b2b27d3f7e95d39fc29061befb812bdb1c08fafb 100644
--- a/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/CorelliPowderCalibrationCreate.py
+++ b/Framework/PythonInterface/plugins/algorithms/WorkflowAlgorithms/CorelliPowderCalibrationCreate.py
@@ -13,6 +13,7 @@ from mantid.api import (
     AlgorithmFactory, AnalysisDataService, DataProcessorAlgorithm, WorkspaceProperty, mtd, Progress, TextAxis,
     Workspace, WorkspaceGroup, WorkspaceUnitValidator)
 from mantid.dataobjects import TableWorkspace, Workspace2D
+from mantid.kernel import StringListValidator
 from mantid.simpleapi import (
     CalculateDIFC, CloneWorkspace, CopyInstrumentParameters, ConvertUnits, CreateEmptyTableWorkspace,
     CreateGroupingWorkspace, CreateWorkspace, DeleteWorkspace, GroupDetectors, GroupWorkspaces, Multiply,
@@ -136,9 +137,10 @@ class CorelliPowderCalibrationCreate(DataProcessorAlgorithm):
         self.setPropertySettings("SourceMaxTranslation",
                                  EnabledWhenProperty("AdjustSource", PropertyCriterion.IsNotDefault))
         property_names = ['FixSource', 'SourceToSampleDistance', 'AdjustSource', 'SourceMaxTranslation']
-        [self.setPropertyGroup(name, 'Source Position') for name in property_names]
+        [self.setPropertyGroup(name, 'Source Calibration') for name in property_names]
 
         # AlignComponents properties
+        self.declareProperty(name='FixY', defaultValue=True, doc="Vertical bank position is left unchanged")
         self.declareProperty(StringArrayProperty('ComponentList', values=self._banks, direction=Direction.Input),
                              doc='Comma separated list on banks to refine')
         self.declareProperty(name='ComponentMaxTranslation', defaultValue=0.02,
@@ -146,7 +148,18 @@ class CorelliPowderCalibrationCreate(DataProcessorAlgorithm):
         self.declareProperty(name='ComponentMaxRotation', defaultValue=3.0,
                              doc='Maximum rotation of each component along either of the X, Y, Z axes (deg)')
         property_names = ['ComponentList', 'ComponentMaxTranslation', 'ComponentMaxRotation']
-        [self.setPropertyGroup(name, 'AlignComponents') for name in property_names]
+        [self.setPropertyGroup(name, 'Banks Calibration') for name in property_names]
+
+        #
+        # Minimization Properties
+        self.declareProperty(name='Minimizer', defaultValue='L-BFGS-B', direction=Direction.Input,
+                             validator=StringListValidator(['L-BFGS-B', 'differential_evolution']),
+                             doc='Minimizer to use, differential_evolution is more accurate and slower.')
+        self.declareProperty(name='MaxIterations', defaultValue=20, direction=Direction.Input,
+                             doc='Maximum number of iterations for minimizer differential_evolution')
+
+        properties = ['Minimizer', 'MaxIterations']
+        [self.setPropertyGroup(name, "Minimization") for name in properties]
 
     def PyExec(self):
         temporary_workspaces = []
@@ -229,7 +242,8 @@ class CorelliPowderCalibrationCreate(DataProcessorAlgorithm):
                           AdjustmentsTable=adjustments_table_name,
                           FitSourcePosition=True,
                           FitSamplePosition=False,
-                          Zposition=True, MinZPosition=-dz, MaxZPosition=dz)
+                          Zposition=True, MinZPosition=-dz, MaxZPosition=dz,
+                          Minimizer='L-BFGS-B')
             self.run_algorithm('AlignComponents', 0.1, 0.2, **kwargs)
         else:
             # Impose the fixed position of the source and save into the adjustments table
@@ -238,6 +252,7 @@ class CorelliPowderCalibrationCreate(DataProcessorAlgorithm):
         # The instrument in `input_workspace` is adjusted in-place
         dt = self.getProperty('ComponentMaxTranslation').value  # maximum translation along either axis
         dr = self.getProperty('ComponentMaxRotation').value  # maximum rotation along either axis
+        move_y = False if self.getProperty('FixY').value is True else True
         kwargs = dict(InputWorkspace=input_workspace,
                       OutputWorkspace=input_workspace,
                       PeakCentersTofTable=peak_centers_in_tof,
@@ -248,12 +263,14 @@ class CorelliPowderCalibrationCreate(DataProcessorAlgorithm):
                       FitSamplePosition=False,
                       ComponentList=self.getProperty('ComponentList').value,
                       Xposition=True, MinXPosition=-dt, MaxXPosition=dt,
-                      Yposition=True, MinYPosition=-dt, MaxYPosition=dt,
+                      Yposition=move_y, MinYPosition=-dt, MaxYPosition=dt,
                       Zposition=True, MinZPosition=-dt, MaxZPosition=dt,
                       AlphaRotation=True, MinAlphaRotation=-dr, MaxAlphaRotation=dr,
                       BetaRotation=True, MinBetaRotation=-dr, MaxBetaRotation=dr,
                       GammaRotation=True, MinGammaRotation=-dr, MaxGammaRotation=dr,
-                      EulerConvention='YXZ')
+                      EulerConvention='YXZ',
+                      Minimizer=self.getProperty('Minimizer').value,
+                      MaxIterations=self.getProperty('MaxIterations').value)
         self.run_algorithm('AlignComponents', 0.2, 0.97, **kwargs)
         progress.report('AlignComponents has been applied')
 
diff --git a/Framework/PythonInterface/test/python/mantid/geometry/IPeakTest.py b/Framework/PythonInterface/test/python/mantid/geometry/IPeakTest.py
index 43b6f2d80092ea6a4a17bf5d06c1909ebc107f82..5cbba41e5414a5122b27843304a4329bdebbaee6 100644
--- a/Framework/PythonInterface/test/python/mantid/geometry/IPeakTest.py
+++ b/Framework/PythonInterface/test/python/mantid/geometry/IPeakTest.py
@@ -152,10 +152,6 @@ class IPeakTest(unittest.TestCase):
         self.assertEqual(self._peak.getRow(), row)
         self.assertEqual(self._peak.getCol(), col)
 
-    def test_get_det_pos(self):
-        expected_det_pos = np.array([0.05962, -0.09450, -0.23786])
-        npt.assert_allclose(self._peak.getDetPos(), expected_det_pos, atol=1e-5)
-
     def test_get_l1(self):
         expected_l1 = 8.3
         self.assertEqual(self._peak.getL1(), expected_l1)
diff --git a/Framework/PythonInterface/test/python/mantid/kernel/UnitConversionTest.py b/Framework/PythonInterface/test/python/mantid/kernel/UnitConversionTest.py
index 4b8ebf717e17d93ffc7e8b02b577491f31b43ffe..4bc63af249428b295c8cb756ac8890cdc552728b 100644
--- a/Framework/PythonInterface/test/python/mantid/kernel/UnitConversionTest.py
+++ b/Framework/PythonInterface/test/python/mantid/kernel/UnitConversionTest.py
@@ -5,7 +5,7 @@
 #   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 # SPDX - License - Identifier: GPL - 3.0 +
 import unittest
-from mantid.kernel import UnitConversion, DeltaEModeType
+from mantid.kernel import UnitConversion, DeltaEModeType, UnitParams, UnitParametersMap
 import math
 
 
@@ -23,5 +23,22 @@ class UnitConversionTest(unittest.TestCase):
         result = UnitConversion.run(src_unit, dest_unit, src_value, l1, l2, theta, emode, efixed)
         self.assertAlmostEqual(result, expected, 12)
 
+    def test_run_accepts_params_version(self):
+        src_unit = "Wavelength"
+        src_value = 1.5
+        dest_unit = "Momentum"
+
+        l1 = l2 = theta = efixed = 0.0
+        emode = DeltaEModeType.Indirect;
+        expected = 2.0*math.pi/src_value
+        params = UnitParametersMap()
+        params[UnitParams.l2] = l2
+        params[UnitParams.twoTheta] = theta
+        # Haven't got a dictionary to convert automatically into std::unordered_map yet
+        #params = {UnitParams.l2: l2, UnitParams.twoTheta: theta}
+
+        result = UnitConversion.run(src_unit, dest_unit, src_value, l1, emode, params)
+        self.assertAlmostEqual(result, expected, 12)
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt
index e25a76830a7a15f7e0377e1156b86e7c2fe05f61..42458b28afdfa0c98928f76b383634b1bf3a2764 100644
--- a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt
@@ -46,6 +46,7 @@ set(TEST_PY_FILES
     FitGaussianTest.py
     FitIncidentSpectrumTest.py
     FractionalIndexingTest.py
+    GenerateLogbookTest.py
     GetEiT0atSNSTest.py
     HB2AReduceTest.py
     HB3AAdjustSampleNormTest.py
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/GenerateLogbookTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/GenerateLogbookTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..f49d6324b00dcc81b98a9ae3581c5964a289bf2f
--- /dev/null
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/GenerateLogbookTest.py
@@ -0,0 +1,84 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2021 ISIS Rutherford Appleton Laboratory UKRI,
+#   NScD Oak Ridge National Laboratory, European Spallation Source,
+#   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+# SPDX - License - Identifier: GPL - 3.0 +
+import unittest
+from mantid.api import mtd, ITableWorkspace
+from mantid.simpleapi import config, GenerateLogbook
+import os
+from tempfile import gettempdir
+
+class GenerateLogbookTest(unittest.TestCase):
+
+    _data_directory = None
+
+    def setUp(self):
+        data_dirs = config['datasearch.directories'].split(';')
+        unit_test_data_dir = [p for p in data_dirs if 'UnitTest' in p][0]
+        d7_dir = 'ILL/D7'
+        if 'ILL' in unit_test_data_dir:
+            d7_dir = 'D7'
+        self._data_directory = os.path.abspath(os.path.join(unit_test_data_dir,  d7_dir))
+
+    def tearDown(self):
+        mtd.clear()
+
+    @classmethod
+    def tearDownClass(cls):
+        mtd.clear()
+        if os.path.exists(os.path.join(gettempdir(), 'logbook.csv')):
+            os.remove(os.path.join(gettempdir(), 'logbook.csv'))
+
+    def test_instrument_does_not_exist(self):
+        self.assertTrue(os.path.exists(self._data_directory))
+        with self.assertRaises(RuntimeError):
+            GenerateLogbook(Directory=self._data_directory,
+                            OutputWorkspace='__unused', Facility='ISIS', Instrument='nonexistent')
+
+    def test_d7_default(self):
+        self.assertTrue(os.path.exists(self._data_directory))
+        GenerateLogbook(Directory=self._data_directory,
+                        OutputWorkspace='default_logbook', Facility='ILL', Instrument='D7',
+                        NumorRange="396990:396993")
+        self._check_output('default_logbook', numberEntries=3, numberColumns=6)
+
+    def test_d7_optional(self):
+        self.assertTrue(os.path.exists(self._data_directory))
+        GenerateLogbook(Directory=self._data_directory,
+                        OutputWorkspace='optional_logbook', Facility='ILL', Instrument='D7',
+                        NumorRange="396990:396993", OptionalHeaders='TOF')
+        self._check_output('optional_logbook', numberEntries=3, numberColumns=7)
+
+    def test_d7_custom(self):
+        self.assertTrue(os.path.exists(self._data_directory))
+        GenerateLogbook(Directory=self._data_directory,
+                        OutputWorkspace='custom_logbook', Facility='ILL', Instrument='D7',
+                        NumorRange="396990:396993", CustomEntries='/entry0/acquisition_mode')
+        self._check_output('custom_logbook', numberEntries=3, numberColumns=7)
+
+    def test_d7_custom_with_summing(self):
+        self.assertTrue(os.path.exists(self._data_directory))
+        GenerateLogbook(Directory=self._data_directory,
+                        OutputWorkspace='custom_logbook_w_summing', Facility='ILL', Instrument='D7',
+                        NumorRange="396990:396993", CustomEntries='/entry0/acquisition_mode',
+                        OptionalHeaders='polarisation')
+        self._check_output('custom_logbook_w_summing', numberEntries=3, numberColumns=8)
+
+    def test_d7_save_csv(self):
+        self.assertTrue(os.path.exists(self._data_directory))
+        GenerateLogbook(Directory=self._data_directory,
+                        OutputWorkspace='__unused', Facility='ILL', Instrument='D7',
+                        NumorRange="396990:396993", OutputFile=os.path.join(gettempdir(), 'logbook.csv'))
+        self.assertTrue(os.path.join(gettempdir(), 'logbook.csv'))
+
+    def _check_output(self, ws, numberEntries, numberColumns):
+        self.assertTrue(mtd[ws])
+        self.assertTrue(isinstance(mtd[ws], ITableWorkspace))
+        self.assertEquals(len(mtd[ws].row(0)), numberColumns)
+        self.assertEquals(len(mtd[ws].column(0)), numberEntries)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/HB2AReduceTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/HB2AReduceTest.py
index e8daad689ce92cad4f26d47868598a43f6eccfd4..06f984b3f698f13a7c6872569482d578e75a80b2 100644
--- a/Framework/PythonInterface/test/python/plugins/algorithms/HB2AReduceTest.py
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/HB2AReduceTest.py
@@ -113,6 +113,12 @@ class HB2AReduceTest(unittest.TestCase):
             os.path.exists(os.path.join(self._default_save_directory, f"{HB2AReduce_ws}.gss")))
         HB2AReduce_ws.delete()
 
+    def test_new_convention(self):
+        HB2AReduce_ws = HB2AReduce('HB2A_exp0666_scan0024.dat',
+                                   IndividualDetectors=True,
+                                   SaveData=False)
+        HB2AReduce_ws.delete()
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/CorelliPowderCalibrationCreateTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/CorelliPowderCalibrationCreateTest.py
index c9c642dc1b1e342990edfa6e0a61eb2545dd73da..578d88bba817990dd8e3b0ba29638293f29c9df7 100644
--- a/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/CorelliPowderCalibrationCreateTest.py
+++ b/Framework/PythonInterface/test/python/plugins/algorithms/WorkflowAlgorithms/CorelliPowderCalibrationCreateTest.py
@@ -5,70 +5,134 @@
 #   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
 # SPDX - License - Identifier: GPL - 3.0 +
 
+import numpy as np
 from numpy.testing import assert_allclose
 import unittest
 
 from mantid.api import mtd
 from mantid.simpleapi import (
-    CorelliPowderCalibrationCreate, CreateSampleWorkspace, MoveInstrumentComponent, RotateInstrumentComponent)
+    CloneWorkspace, CompareWorkspaces, CorelliPowderCalibrationCreate, ConvertUnits, CreateSampleWorkspace,
+    DeleteWorkspaces, MoveInstrumentComponent, Rebin, RotateInstrumentComponent)
 
 
 class CorelliPowderCalibrationCreateTest(unittest.TestCase):
 
-    def test_exec(self):
-        # Single 10x10 rectangular detector, located 5m downstream the sample
-        CreateSampleWorkspace(WorkspaceType="Event", Function="Powder Diffraction", XMin=300, XMax=16666.7, BinWidth=1,
-                              NumBanks=1, NumEvents=100000, PixelSpacing=0.02, OutputWorkspace="test_workspace",
-                              SourceDistanceFromSample=10.0, BankDistanceFromSample=5.0)
-        # The detector ID at the center of the detector panel is detector-ID = 155, corresponding to workspace index 55.
-        # When the detector panel is placed perpendicular to the X axis and five meters away from the sample,
-        # detector-ID 155 shows nine peaks with the following peak-centers, in d-spacing (Angstroms) units:
-        spacings_reference = [0.304670, 0.610286, 0.915385, 1.220476, 1.525575, 1.830671, 2.135765, 2.44092, 2.74598]
-        # We select these d-spacings as the reference d-spacings
-        # Place the detector at a position and orientation close, but not equal to, perpendicular to the X axis
-        # 5 meters from the sample
-        RotateInstrumentComponent(Workspace='test_workspace', ComponentName='bank1', X=0.1, Y=99, z=0.1, Angle=88,
-                                  RelativeRotation=True)
-        MoveInstrumentComponent(Workspace='test_workspace', ComponentName='bank1', X=4.98, y=-0.12, z=0.08,
+    def setUp(self) -> None:
+        r"""Fixture runs at the beginning of every test method"""
+        spacings_reference = [0.9179, 0.9600, 1.0451, 1.2458, 1.3576, 1.5677, 1.6374, 3.1353]  # silicon
+        # add one Gaussian peak for every reference d-spacing
+        peak_functions = list()
+        for spacing in spacings_reference:
+            peak_function = f'name=Gaussian, PeakCentre={spacing}, Height={10 * np.sqrt(spacing)}, Sigma={0.003 * spacing}'
+            peak_functions.append(peak_function)
+        function = ';'.join(peak_functions)
+        begin, end, bin_width = spacings_reference[0] - 0.5, spacings_reference[-1] + 0.5, 0.0001
+        # Single 10x10 rectangular detector, located 4m downstream the sample along the X-axis
+        # Each detector has the same histogram of intensities, showing eight Gaussian peaks with centers at the
+        # reference d-spacings
+        CreateSampleWorkspace(WorkspaceType='Histogram', Function='User Defined', UserDefinedFunction=function,
+                              XUnit='dSpacing', XMin=begin, XMax=end, BinWidth=bin_width,
+                              NumBanks=1, PixelSpacing=0.02, SourceDistanceFromSample=10.0, BankDistanceFromSample=4.0,
+                              OutputWorkspace='test_workspace_dSpacing')
+        RotateInstrumentComponent(Workspace='test_workspace_dSpacing', ComponentName='bank1', X=0., Y=1., z=0.,
+                                  Angle=90, RelativeRotation=True)
+        MoveInstrumentComponent(Workspace='test_workspace_dSpacing', ComponentName='bank1', X=4.0, y=0.0, z=0.0,
                                 RelativePosition=False)
+        # Eight peaks now in TOF. Only when the instrument is located 4m downstream along the X-axis will we obtain
+        # the correct d-Spacings if we convert back to dSpacings units. If we perturb the instrument and convert
+        # back to dSpacing units, we'll obtain eight peaks centered at d-spacings sligthly different than the
+        # reference values
+        ConvertUnits(InputWorkspace='test_workspace_dSpacing', Target='TOF', EMode='Elastic',
+                     OutputWorkspace='test_workspace_TOF')
+        Rebin(InputWorkspace='test_workspace_TOF', Params=[300, 1.0, 16666.7], OutputWorkspace='test_workspace_TOF')
+        ConvertUnits(InputWorkspace='test_workspace_TOF', Target='dSpacing', EMode='Elastic',
+                     OutputWorkspace='test_workspace_dSpacing')
+        self.spacings_reference = spacings_reference
+
+    def tearDown(self) -> None:
+        r"""Fixture runs at the end of every test method"""
+        DeleteWorkspaces(['test_workspace_dSpacing', 'test_workspace_TOF'])
 
+    def spacings_recovered(self, input_workspace, calibrate=True):
+        r"""Compare the input_workspace to the reference workspace 'test_workspace_dSpacing' after being
+        converted to TOF units. If calibrate=True, calibrate first and then convert units"""
+        if calibrate:
+            CorelliPowderCalibrationCreate(
+                InputWorkspace='perturbed', OutputWorkspacesPrefix='cal_',
+                TofBinning=[300, 1.0, 16666.7], PeakPositions=self.spacings_reference, SourceToSampleDistance=10.0,
+                FixY=False, ComponentList='bank1', ComponentMaxTranslation=0.03, ComponentMaxRotation=6,
+                Minimizer='differential_evolution', MaxIterations=10)
+        ConvertUnits(InputWorkspace='perturbed', Target='dSpacing', EMode='Elastic', OutputWorkspace='perturbed_dS')
+        results = CompareWorkspaces(Workspace1='test_workspace_dSpacing', Workspace2='perturbed_dS', Tolerance=0.001,
+                                    CheckInstrument=False)
+        DeleteWorkspaces(['perturbed_dS'])
+        return results.Result
+
+    def test_exceptions(self):
         # Both FixSource=True, AdjustSource=True can't be True
         try:
             CorelliPowderCalibrationCreate(
-                InputWorkspace='test_workspace', OutputWorkspacesPrefix='cal_',
-                TofBinning=[300, 1.0, 16666.7], PeakPositions=spacings_reference, FixSource=True, AdjustSource=True,
-                ComponentList='bank1', ComponentMaxTranslation=0.2, ComponentMaxRotation=10)
+                InputWorkspace='test_workspace_TOF', OutputWorkspacesPrefix='cal_',
+                TofBinning=[300, 1.0, 16666.7], PeakPositions=self.spacings_reference, FixSource=True,
+                AdjustSource=True, FixY=False, ComponentList='bank1', ComponentMaxTranslation=0.2,
+                ComponentMaxRotation=10)
         except RuntimeError as error:
             assert 'Some invalid Properties found' in str(error)
 
         # Both FixSource=True, AdjustSource=True can't be False
         try:
             CorelliPowderCalibrationCreate(
-                InputWorkspace='test_workspace', OutputWorkspacesPrefix='cal_',
-                TofBinning=[300, 1.0, 16666.7], PeakPositions=spacings_reference, FixSource=False, AdjustSource=False,
-                ComponentList='bank1', ComponentMaxTranslation=0.2, ComponentMaxRotation=10)
+                InputWorkspace='test_workspace_TOF', OutputWorkspacesPrefix='cal_',
+                TofBinning=[300, 1.0, 16666.7], PeakPositions=self.spacings_reference, FixSource=False,
+                AdjustSource=False, FixY=False, ComponentList='bank1', ComponentMaxTranslation=0.2,
+                ComponentMaxRotation=10)
         except RuntimeError as error:
             assert 'Some invalid Properties found' in str(error)
 
-        # The calibration algorithm will attempt to correct the position and orientation of the bank so that peak
-        # centers for all detectors in the bank (not just detector-ID 155) approach our reference values. As
-        # a result, the final position and orientation is not exactly perpendicular to the X-axis and positioned
-        # five meters away from the sample.
+    @unittest.skip("causes surpassing the timeout in the Jenkins servers")
+    def test_translation(self):
+        CloneWorkspace(InputWorkspace='test_workspace_TOF', OutputWorkspace='perturbed')
+        CloneWorkspace(InputWorkspace='test_workspace_TOF', OutputWorkspace='perturbed')
+        MoveInstrumentComponent(Workspace='perturbed', ComponentName='bank1', X=0.02, y=0.005, z=0.005,
+                                RelativePosition=True)
+        assert self.spacings_recovered('perturbed', calibrate=False) is False
+        assert self.spacings_recovered('perturbed', calibrate=True)
+        DeleteWorkspaces(['perturbed'])
+
+    @unittest.skip("causes surpassing the timeout in the Jenkins servers")
+    def test_rotation(self):
+        CloneWorkspace(InputWorkspace='test_workspace_TOF', OutputWorkspace='perturbed')
+        RotateInstrumentComponent(Workspace='perturbed', ComponentName='bank1', X=0, Y=0, z=1, Angle=5,
+                                  RelativeRotation=True)
+        assert self.spacings_recovered('perturbed', calibrate=False) is False
+        assert self.spacings_recovered('perturbed', calibrate=True)
+        DeleteWorkspaces(['perturbed'])
+
+    def test_translation_rotation(self):
+        CloneWorkspace(InputWorkspace='test_workspace_TOF', OutputWorkspace='perturbed')
+        MoveInstrumentComponent(Workspace='perturbed', ComponentName='bank1', X=0.02, y=0.005, z=0.005,
+                                RelativePosition=True)
+        RotateInstrumentComponent(Workspace='perturbed', ComponentName='bank1', X=0, Y=0, z=1, Angle=5,
+                                  RelativeRotation=True)
+        assert self.spacings_recovered('perturbed', calibrate=False) is False
+        assert self.spacings_recovered('perturbed', calibrate=True)
+        DeleteWorkspaces(['perturbed'])
+
+    def test_fix_y(self) -> None:
+        CloneWorkspace(InputWorkspace='test_workspace_TOF', OutputWorkspace='perturbed')
+        y = -0.0042  # desired fixed position
+        MoveInstrumentComponent(Workspace='perturbed', ComponentName='bank1', X=0, y=y, z=0,
+                                RelativePosition=False)
+        r"""Pass option FixY=True"""
         CorelliPowderCalibrationCreate(
-            InputWorkspace='test_workspace', OutputWorkspacesPrefix='cal_',
-            TofBinning=[300, 1.0, 16666.7], PeakPositions=spacings_reference, SourceToSampleDistance=10.0,
-            ComponentList='bank1', ComponentMaxTranslation=0.2, ComponentMaxRotation=10)
-        # Check source position
-        row = mtd['cal_adjustments'].row(0)
-        assert_allclose([row[name] for name in ('Xposition', 'Yposition', 'Zposition')], [0., 0., -10.0], atol=0.001)
-        # Check position of first bank
+            InputWorkspace='perturbed', OutputWorkspacesPrefix='cal_',
+            TofBinning=[300, 1.0, 16666.7], PeakPositions=self.spacings_reference, SourceToSampleDistance=10.0,
+            FixY=True, ComponentList='bank1', ComponentMaxTranslation=0.2, ComponentMaxRotation=10,
+            Minimizer='L-BFGS-B')
+        # Check Y-position of first bank hasn't changed
         row = mtd['cal_adjustments'].row(1)
-        target_position, target_orientation, target_rotation = [5.18, -0.32,  0.20], [0.001, 0.999, -0.027], 98.0
-        # ToDO investigate the relatively large tolerance required for some operative systems, atol=0.05
-        assert_allclose([row[name] for name in ('Xposition', 'Yposition', 'Zposition')], target_position, atol=0.05)
-        assert_allclose([row[name] for name in ('XdirectionCosine', 'YdirectionCosine', 'ZdirectionCosine')],
-                        target_orientation, atol=0.05)
-        assert_allclose(row['RotationAngle'], target_rotation, atol=2.0)
+        self.assertAlmostEquals(row['Yposition'], y, places=5)
+        DeleteWorkspaces(['perturbed'])
 
 
 if __name__ == '__main__':
diff --git a/Framework/SINQ/src/PoldiPeakSearch.cpp b/Framework/SINQ/src/PoldiPeakSearch.cpp
index d16c4966824658d5d522a3c30ba1532708f514af..519ecf47cd43b8494abb53d998b7135cfa192ce3 100644
--- a/Framework/SINQ/src/PoldiPeakSearch.cpp
+++ b/Framework/SINQ/src/PoldiPeakSearch.cpp
@@ -224,8 +224,8 @@ double PoldiPeakSearch::getTransformedCenter(double value,
 
   // Transform value to d-spacing.
   Unit_sptr dUnit = UnitFactory::Instance().create("dSpacing");
-  return UnitConversion::run((*transformUnit), (*dUnit), value, 0, 0, 0,
-                             DeltaEMode::Elastic, 0.0);
+  return UnitConversion::run((*transformUnit), (*dUnit), value, 0,
+                             DeltaEMode::Elastic);
 }
 
 /** Creates PoldiPeak-objects from peak position iterators
diff --git a/Framework/TestHelpers/src/WorkspaceCreationHelper.cpp b/Framework/TestHelpers/src/WorkspaceCreationHelper.cpp
index aa0b53078a1f70fd1ce0d965e8e13da34f320485..e405ff29092fc2735500d396d3fadda110ac448c 100644
--- a/Framework/TestHelpers/src/WorkspaceCreationHelper.cpp
+++ b/Framework/TestHelpers/src/WorkspaceCreationHelper.cpp
@@ -1356,134 +1356,6 @@ createPeaksWorkspace(const int numPeaks,
   return peaksWS;
 }
 
-/** helper method to create preprocessed detector's table workspace */
-std::shared_ptr<DataObjects::TableWorkspace>
-createTableWorkspace(const API::MatrixWorkspace_const_sptr &inputWS) {
-  const size_t nHist = inputWS->getNumberHistograms();
-
-  // set the target workspace
-  auto targWS = std::make_shared<TableWorkspace>(nHist);
-  // detectors positions
-  targWS->addColumn("V3D", "DetDirections");
-  // sample-detector distance;
-  targWS->addColumn("double", "L2");
-  // Diffraction angle
-  targWS->addColumn("double", "TwoTheta");
-  targWS->addColumn("double", "Azimuthal");
-  // the detector ID;
-  targWS->addColumn("int", "DetectorID");
-  // stores spectra index which corresponds to a valid detector index;
-  targWS->addColumn("size_t", "detIDMap");
-  // stores detector index which corresponds to the workspace index;
-  targWS->addColumn("size_t", "spec2detMap");
-
-  // will see about that
-  // sin^2(Theta)
-  //    std::vector<double>      SinThetaSq;
-
-  //,"If the detectors were actually processed from real instrument or generated
-  // for some fake one ");
-  return targWS;
-}
-
-/** method does preliminary calculations of the detectors positions to convert
- results into k-dE space ;
- and places the results into static cash to be used in subsequent calls to this
- algorithm */
-void processDetectorsPositions(const API::MatrixWorkspace_const_sptr &inputWS,
-                               DataObjects::TableWorkspace_sptr &targWS,
-                               double Ei) {
-  Geometry::Instrument_const_sptr instrument = inputWS->getInstrument();
-  //
-  Geometry::IComponent_const_sptr source = instrument->getSource();
-  Geometry::IComponent_const_sptr sample = instrument->getSample();
-  if ((!source) || (!sample)) {
-
-    throw Kernel::Exception::InstrumentDefinitionError(
-        "Instrument not sufficiently defined: failed to get source and/or "
-        "sample");
-  }
-
-  // L1
-  try {
-    double L1 = source->getDistance(*sample);
-    targWS->logs()->addProperty<double>("L1", L1, true);
-  } catch (Kernel::Exception::NotFoundError &) {
-    throw Kernel::Exception::InstrumentDefinitionError(
-        "Unable to calculate source-sample distance for workspace",
-        inputWS->getTitle());
-  }
-  // Instrument name
-  std::string InstrName = instrument->getName();
-  targWS->logs()->addProperty<std::string>("InstrumentName", InstrName, true);
-  targWS->logs()->addProperty<bool>("FakeDetectors", false, true);
-  // Incident energy for Direct or Analysis energy for indirect instrument;
-  targWS->logs()->addProperty<double>("Ei", Ei, true);
-
-  // get access to the workspace memory
-  auto &sp2detMap = targWS->getColVector<size_t>("spec2detMap");
-  auto &detId = targWS->getColVector<int32_t>("DetectorID");
-  auto &detIDMap = targWS->getColVector<size_t>("detIDMap");
-  auto &L2 = targWS->getColVector<double>("L2");
-  auto &TwoTheta = targWS->getColVector<double>("TwoTheta");
-  auto &Azimuthal = targWS->getColVector<double>("Azimuthal");
-  auto &detDir = targWS->getColVector<Kernel::V3D>("DetDirections");
-
-  //// progress messave appearence
-  size_t nHist = targWS->rowCount();
-  //// Loop over the spectra
-  uint32_t liveDetectorsCount(0);
-  const auto &spectrumInfo = inputWS->spectrumInfo();
-  for (size_t i = 0; i < nHist; i++) {
-    sp2detMap[i] = std::numeric_limits<size_t>::quiet_NaN();
-    detId[i] = std::numeric_limits<int32_t>::quiet_NaN();
-    detIDMap[i] = std::numeric_limits<size_t>::quiet_NaN();
-    L2[i] = std::numeric_limits<double>::quiet_NaN();
-    TwoTheta[i] = std::numeric_limits<double>::quiet_NaN();
-    Azimuthal[i] = std::numeric_limits<double>::quiet_NaN();
-
-    if (!spectrumInfo.hasDetectors(i) || spectrumInfo.isMonitor(i))
-      continue;
-
-    // calculate the requested values;
-    sp2detMap[i] = liveDetectorsCount;
-    detId[liveDetectorsCount] = int32_t(spectrumInfo.detector(i).getID());
-    detIDMap[liveDetectorsCount] = i;
-    L2[liveDetectorsCount] = spectrumInfo.l2(i);
-
-    double polar = spectrumInfo.twoTheta(i);
-    double azim = spectrumInfo.detector(i).getPhi();
-    TwoTheta[liveDetectorsCount] = polar;
-    Azimuthal[liveDetectorsCount] = azim;
-
-    double sPhi = sin(polar);
-    double ez = cos(polar);
-    double ex = sPhi * cos(azim);
-    double ey = sPhi * sin(azim);
-
-    detDir[liveDetectorsCount].setX(ex);
-    detDir[liveDetectorsCount].setY(ey);
-    detDir[liveDetectorsCount].setZ(ez);
-
-    // double sinTheta=sin(0.5*polar);
-    // this->SinThetaSq[liveDetectorsCount]  = sinTheta*sinTheta;
-
-    liveDetectorsCount++;
-  }
-  targWS->logs()->addProperty<uint32_t>(
-      "ActualDetectorsNum", liveDetectorsCount,
-      true); //,"The actual number of detectors receivinv signal");
-}
-
-std::shared_ptr<Mantid::DataObjects::TableWorkspace>
-buildPreprocessedDetectorsWorkspace(
-    const Mantid::API::MatrixWorkspace_sptr &ws) {
-  Mantid::DataObjects::TableWorkspace_sptr DetPos = createTableWorkspace(ws);
-  auto Ei = ws->run().getPropertyValueAsType<double>("Ei");
-  processDetectorsPositions(ws, DetPos, Ei);
-
-  return DetPos;
-}
 void create2DAngles(std::vector<double> &L2, std::vector<double> &polar,
                     std::vector<double> &azim, size_t nPolar, size_t nAzim,
                     double polStart, double polEnd, double azimStart,
diff --git a/Testing/Data/SystemTest/ILL/D11/017038.nxs.md5 b/Testing/Data/SystemTest/ILL/D11/017038.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..7ec4f67a013468657ed03ccbd828a57e5cad1922
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/D11/017038.nxs.md5
@@ -0,0 +1 @@
+bdfcae2eaed0e0b01341634fdc6cd0cd
diff --git a/Testing/Data/SystemTest/ILL/D11/017039.nxs.md5 b/Testing/Data/SystemTest/ILL/D11/017039.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..99bac83685bae9c7bbf36dcfdc2d3418683fceae
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/D11/017039.nxs.md5
@@ -0,0 +1 @@
+53961c80deb81e9e8efaea527f519df0
diff --git a/Testing/Data/SystemTest/ILL/D11B/000361.nxs.md5 b/Testing/Data/SystemTest/ILL/D11B/000361.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..a269a2f35dcd3dbc444bed5c8ad739543039c9be
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/D11B/000361.nxs.md5
@@ -0,0 +1 @@
+080d74ccc4c020de100ca30a751bac66
diff --git a/Testing/Data/SystemTest/ILL/D11B/000362.nxs.md5 b/Testing/Data/SystemTest/ILL/D11B/000362.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..05caa66bcdd417b1bf775ecafb329e1fa9768df7
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/D11B/000362.nxs.md5
@@ -0,0 +1 @@
+6bb405a1c7e1e2de3d6732ae736c1ed0
diff --git a/Testing/Data/SystemTest/ILL/D16/000245.nxs.md5 b/Testing/Data/SystemTest/ILL/D16/000245.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..2d05a9f050593605835648d2f3885ed8e76871d8
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/D16/000245.nxs.md5
@@ -0,0 +1 @@
+cf8df46e8667fb822b4714f7511069d4
diff --git a/Testing/Data/SystemTest/ILL/D16/000246.nxs.md5 b/Testing/Data/SystemTest/ILL/D16/000246.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..c06e1e982f2ad104f1cefe0211be6005139714d7
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/D16/000246.nxs.md5
@@ -0,0 +1 @@
+f6884fc02fa929d7aed0cc00f0ab8f3e
diff --git a/Testing/Data/SystemTest/ILL/D22/354717.nxs.md5 b/Testing/Data/SystemTest/ILL/D22/354717.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..fdef270acb93cc120ecef1ce5221d063af8df8f6
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/D22/354717.nxs.md5
@@ -0,0 +1 @@
+d72338f10f5a23fc5f99e2ba2cc9e57f
diff --git a/Testing/Data/SystemTest/ILL/D22/354718.nxs.md5 b/Testing/Data/SystemTest/ILL/D22/354718.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..58e7aa9069896fde5239cc09451ad054e69d36e4
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/D22/354718.nxs.md5
@@ -0,0 +1 @@
+33e93eca711d48f80d4d3ce16d7f8626
diff --git a/Testing/Data/SystemTest/ILL/D22B/398672.nxs.md5 b/Testing/Data/SystemTest/ILL/D22B/398672.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..de10b9690fc584125f99e3e6f47ce67dc9a19c94
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/D22B/398672.nxs.md5
@@ -0,0 +1 @@
+12bb240ed13085568f55e7fa28f09aa1
diff --git a/Testing/Data/SystemTest/ILL/D22B/398673.nxs.md5 b/Testing/Data/SystemTest/ILL/D22B/398673.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..c403ec6a76106de2160824a59ca41afab9168eb2
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/D22B/398673.nxs.md5
@@ -0,0 +1 @@
+95bc097bc69b6cdf8565ab2cf483425f
diff --git a/Testing/Data/SystemTest/ILL/D33/162689.nxs.md5 b/Testing/Data/SystemTest/ILL/D33/162689.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..d1a88ad0b2bf7b62b51145c7a8237abde40632d4
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/D33/162689.nxs.md5
@@ -0,0 +1 @@
+3855c6e372bf2e2cc0441db81da9940e
diff --git a/Testing/Data/SystemTest/ILL/D33/162690.nxs.md5 b/Testing/Data/SystemTest/ILL/D33/162690.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..530b6479d142f1c7de9e5b813d6b58b609614a21
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/D33/162690.nxs.md5
@@ -0,0 +1 @@
+38e69c005c9c6f1cad91919d0b9cb81e
diff --git a/Testing/Data/SystemTest/ILL/IN4/092375.nxs.md5 b/Testing/Data/SystemTest/ILL/IN4/092375.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..1cc48f9d973cc663ab3fe3ab4238c6387121a9b9
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/IN4/092375.nxs.md5
@@ -0,0 +1 @@
+d334093deaa681a5c1d3aabd12102c34
diff --git a/Testing/Data/SystemTest/ILL/IN4/092376.nxs.md5 b/Testing/Data/SystemTest/ILL/IN4/092376.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..0586dc36c731c4433291dcbe9a314e1a19b15189
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/IN4/092376.nxs.md5
@@ -0,0 +1 @@
+cf2f4aba7bad27fd7e7c75d17d620d3d
diff --git a/Testing/Data/SystemTest/ILL/IN5/199728.nxs.md5 b/Testing/Data/SystemTest/ILL/IN5/199728.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..f2a17ed65ce51dce9e433e497503beb221f6c02b
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/IN5/199728.nxs.md5
@@ -0,0 +1 @@
+76a0d6f2b58f7d7af08d6ad937f6b206
diff --git a/Testing/Data/SystemTest/ILL/IN5/199729.nxs.md5 b/Testing/Data/SystemTest/ILL/IN5/199729.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..6a780c7b0fdadb83af4939e35148098830a0c905
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/IN5/199729.nxs.md5
@@ -0,0 +1 @@
+43cb5b256ef134faffd360fd5c31c612
diff --git a/Testing/Data/SystemTest/ILL/IN6/224436.nxs.md5 b/Testing/Data/SystemTest/ILL/IN6/224436.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..9acdbe6bb4371fb276ea5edc86c822ba3a0d680d
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/IN6/224436.nxs.md5
@@ -0,0 +1 @@
+51ceb5ddb8b550a59ab28690c5b955d1
diff --git a/Testing/Data/SystemTest/ILL/IN6/224437.nxs.md5 b/Testing/Data/SystemTest/ILL/IN6/224437.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..099a83231f0fb17dc04ba6d2bebf43e18a33ae4a
--- /dev/null
+++ b/Testing/Data/SystemTest/ILL/IN6/224437.nxs.md5
@@ -0,0 +1 @@
+20eaf76d94cad9489dce524eac87a8a1
diff --git a/Testing/Data/SystemTest/ISIS_Powder/input/calibration/hrp/16_5/ISIS_Powder-HRPD-VanSplined_66031_hrpd_new_072_01_corr.cal.nxs.md5 b/Testing/Data/SystemTest/ISIS_Powder/input/calibration/hrp/16_5/ISIS_Powder-HRPD-VanSplined_66031_hrpd_new_072_01_corr.cal.nxs.md5
index e932090757cf5c7aed40586b630187f58edc5467..74b69e93ad5924e08c079dd70dd4cf85dcce8abf 100644
--- a/Testing/Data/SystemTest/ISIS_Powder/input/calibration/hrp/16_5/ISIS_Powder-HRPD-VanSplined_66031_hrpd_new_072_01_corr.cal.nxs.md5
+++ b/Testing/Data/SystemTest/ISIS_Powder/input/calibration/hrp/16_5/ISIS_Powder-HRPD-VanSplined_66031_hrpd_new_072_01_corr.cal.nxs.md5
@@ -1 +1 @@
-a2ec43e892f8d2188afb7ab40fa314b0
+608f0a5a0776e7c4318c277306409a75
diff --git a/Testing/Data/UnitTest/HB2A_exp0666__Ge_113_OUT_vcorr.txt.md5 b/Testing/Data/UnitTest/HB2A_exp0666__Ge_113_OUT_vcorr.txt.md5
new file mode 100644
index 0000000000000000000000000000000000000000..a2b3ef633209e966fc5f1429115337fb70c14af5
--- /dev/null
+++ b/Testing/Data/UnitTest/HB2A_exp0666__Ge_113_OUT_vcorr.txt.md5
@@ -0,0 +1 @@
+7fbf5aa9d9eba9b582ed028775cfad10
diff --git a/Testing/Data/UnitTest/HB2A_exp0742__Ge_113_IN_vcorr.txt.md5 b/Testing/Data/UnitTest/HB2A_exp0742__Ge_113_IN_vcorr.txt.md5
new file mode 100644
index 0000000000000000000000000000000000000000..f5777969e7802b6667b1d7853ebefcada30c8e98
--- /dev/null
+++ b/Testing/Data/UnitTest/HB2A_exp0742__Ge_113_IN_vcorr.txt.md5
@@ -0,0 +1 @@
+73dc7cf1b20ab8dad34c3fe1483be454
diff --git a/Testing/Data/UnitTest/HB2A_exp0742__Ge_113_OUT_vcorr.txt.md5 b/Testing/Data/UnitTest/HB2A_exp0742__Ge_113_OUT_vcorr.txt.md5
new file mode 100644
index 0000000000000000000000000000000000000000..f5777969e7802b6667b1d7853ebefcada30c8e98
--- /dev/null
+++ b/Testing/Data/UnitTest/HB2A_exp0742__Ge_113_OUT_vcorr.txt.md5
@@ -0,0 +1 @@
+73dc7cf1b20ab8dad34c3fe1483be454
diff --git a/Testing/Data/UnitTest/HB2A_exp0742__Ge_115_IN_vcorr.txt.md5 b/Testing/Data/UnitTest/HB2A_exp0742__Ge_115_IN_vcorr.txt.md5
new file mode 100644
index 0000000000000000000000000000000000000000..af6bed54477165458112c7bc8368add7abf32165
--- /dev/null
+++ b/Testing/Data/UnitTest/HB2A_exp0742__Ge_115_IN_vcorr.txt.md5
@@ -0,0 +1 @@
+6112caadec2cfdaf5f8b979b612dcb07
diff --git a/Testing/Data/UnitTest/HB2A_exp0742__Ge_115_OUT_vcorr.txt.md5 b/Testing/Data/UnitTest/HB2A_exp0742__Ge_115_OUT_vcorr.txt.md5
new file mode 100644
index 0000000000000000000000000000000000000000..af6bed54477165458112c7bc8368add7abf32165
--- /dev/null
+++ b/Testing/Data/UnitTest/HB2A_exp0742__Ge_115_OUT_vcorr.txt.md5
@@ -0,0 +1 @@
+6112caadec2cfdaf5f8b979b612dcb07
diff --git a/Testing/Data/UnitTest/HB2A_exp0742_scan0028.dat.md5 b/Testing/Data/UnitTest/HB2A_exp0742_scan0028.dat.md5
new file mode 100644
index 0000000000000000000000000000000000000000..2ddbaeeab100909549904371e26e8ff3d575465c
--- /dev/null
+++ b/Testing/Data/UnitTest/HB2A_exp0742_scan0028.dat.md5
@@ -0,0 +1 @@
+61dd268cd2f083178530f30549ad974a
diff --git a/Testing/SystemTests/tests/framework/ConvertHFIRSCDtoMDETest.py b/Testing/SystemTests/tests/framework/ConvertHFIRSCDtoMDETest.py
index d1bc1ab2b9c77983da9a83d474b89b5b8deb7628..3c2bc4deba30f6c3b7547839ecf01c955f508697 100644
--- a/Testing/SystemTests/tests/framework/ConvertHFIRSCDtoMDETest.py
+++ b/Testing/SystemTests/tests/framework/ConvertHFIRSCDtoMDETest.py
@@ -45,6 +45,18 @@ class ConvertHFIRSCDtoMDETest(systemtesting.MantidSystemTest):
                                        atol=0.005,
                                        err_msg=f"mismatch for peak {p}")
 
+        # now try using LeanElasticPeak
+        ConvertHFIRSCDtoMDETest_peaks3 = FindPeaksMD(InputWorkspace=ConvertHFIRSCDtoMDETest_Q, PeakDistanceThreshold=2.2,
+                                                     OutputType='LeanElasticPeak')
+
+        self.assertEqual(ConvertHFIRSCDtoMDETest_peaks3.getNumberPeaks(), 14)
+
+        for p in range(14):
+            np.testing.assert_allclose(ConvertHFIRSCDtoMDETest_peaks3.getPeak(p).getQSampleFrame(),
+                                       ConvertHFIRSCDtoMDETest_peaks.getPeak(p).getQSampleFrame(),
+                                       atol=0.005,
+                                       err_msg=f"mismatch for peak {p}")
+
 
 class ConvertHFIRSCDtoMDE_HB3A_Test(systemtesting.MantidSystemTest):
     def requiredMemoryMB(self):
diff --git a/Testing/SystemTests/tests/framework/DrillProcessTest.py b/Testing/SystemTests/tests/framework/DrillProcessTest.py
index 81ac652dec93ab0e6af8b07a0b207661f0c7a1f1..21ca386abc6a080b5900378b5e09a1853f9ccded 100644
--- a/Testing/SystemTests/tests/framework/DrillProcessTest.py
+++ b/Testing/SystemTests/tests/framework/DrillProcessTest.py
@@ -30,6 +30,7 @@ class DrillProcessSANSTest(systemtesting.MantidSystemTest):
         super().__init__()
         config['default.facility'] = 'ILL'
         config['default.instrument'] = 'D11'
+        self.disableChecking = ['Instrument']
         config.appendDataSearchSubDir('ILL/D11/')
 
     def validate(self):
diff --git a/Testing/SystemTests/tests/framework/EnginXScriptTest.py b/Testing/SystemTests/tests/framework/EnginXScriptTest.py
index 21592257bd79535d6fad8f6562d8ae2abd6d5875..73383e66b736596e89e1121b6972dac812c8375d 100644
--- a/Testing/SystemTests/tests/framework/EnginXScriptTest.py
+++ b/Testing/SystemTests/tests/framework/EnginXScriptTest.py
@@ -10,7 +10,7 @@ import systemtesting
 
 import mantid.simpleapi as simple
 
-from mantid import config
+from mantid import config, mtd
 from Engineering.EnginX import main
 
 DIRS = config['datasearch.directories'].split(';')
@@ -73,19 +73,28 @@ class CreateCalibrationCroppedTest(systemtesting.MantidSystemTest):
         main(vanadium_run="236516", user="test", focus_run=None, do_cal=True, force_cal=True, directory=cal_directory,
              crop_type="spectra", crop_on="1-20")
 
+    def validateSingleColumn(self, col1, col2, tolerance):
+        assert len(col1)==len(col2)
+        for a, b in zip(col1, col2):
+            den = 0.5 * (abs(a) + abs(b))
+            assert (abs(a - b) / den) < tolerance
+
     def validate(self):
         self.tolerance_is_rel_err = True
         self.tolerance = 1e-2
 
         # this is neccesary due to appendspectra creating spectrum numbers of 0
         self.disableChecking.append('SpectraMap')
+        # fitted peaks table workspace is v sensitive to changes in input data so just validate X0 col
         if systemtesting.using_gsl_v1():
-            return ("cropped", "engggui_calibration_bank_cropped_gsl1.nxs",
-                    "engg_calibration_banks_parameters", "engggui_calibration_cropped_parameters_gsl1.nxs",
+            reffile = simple.LoadNexus(Filename="engggui_calibration_bank_cropped_gsl1.nxs")
+            self.validateSingleColumn(mtd["cropped"].column("X0"),reffile.column("X0"),self.tolerance)
+            return ("engg_calibration_banks_parameters", "engggui_calibration_cropped_parameters_gsl1.nxs",
                     "Engg difc Zero Peaks Bank cropped", "engggui_difc_zero_peaks_bank_cropped_gsl1.nxs")
         else:
-            return ("cropped", "engggui_calibration_bank_cropped.nxs",
-                    "engg_calibration_banks_parameters", "engggui_calibration_bank_cropped_parameters.nxs",
+            reffile = simple.LoadNexus(Filename="engggui_calibration_bank_cropped.nxs")
+            self.validateSingleColumn(mtd["cropped"].column("X0"),reffile.column("X0"),self.tolerance)
+            return ("engg_calibration_banks_parameters", "engggui_calibration_bank_cropped_parameters.nxs",
                     "Engg difc Zero Peaks Bank cropped", "engggui_difc_zero_peaks_bank_cropped.nxs")
 
     def cleanup(self):
diff --git a/Testing/SystemTests/tests/framework/GenerateLogbookTest.py b/Testing/SystemTests/tests/framework/GenerateLogbookTest.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d5371e846aefc8e74b6ce47f334e228bc83c82b
--- /dev/null
+++ b/Testing/SystemTests/tests/framework/GenerateLogbookTest.py
@@ -0,0 +1,351 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2021 ISIS Rutherford Appleton Laboratory UKRI,
+#   NScD Oak Ridge National Laboratory, European Spallation Source,
+#   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+# SPDX - License - Identifier: GPL - 3.0 +
+from mantid.simpleapi import GenerateLogbook, config, mtd
+import systemtesting
+
+import os
+
+
+class D11_GenerateLogbook_Test(systemtesting.MantidSystemTest):
+    """
+    Tests generating logbook for D11 data.
+    """
+
+    _data_directory = None
+
+    def __init__(self):
+        super(D11_GenerateLogbook_Test, self).__init__()
+        self.setUp()
+
+    def setUp(self):
+        config['default.facility'] = 'ILL'
+        config['default.instrument'] = 'D11'
+        config['logging.loggers.root.level'] = 'Warning'
+
+        data_dirs = config['datasearch.directories'].split(';')
+        test_data_dir = [p for p in data_dirs if 'SystemTest' in p][0]
+        d11_dir = 'ILL/D11'
+        if 'ILL' in test_data_dir:
+            d11_dir = 'D11'
+        self._data_directory = os.path.abspath(os.path.join(test_data_dir,  d11_dir))
+
+    def cleanup(self):
+        mtd.clear()
+
+    def validate(self):
+        self.tolerance = 1e-3
+        self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
+        return ['d11_logbook', 'D11_Logbook_Reference.nxs']
+
+    def runTest(self):
+        GenerateLogbook(Directory=self._data_directory, OutputWorkspace='d11_logbook',
+                        Facility='ILL', Instrument='D11', NumorRange='017038,017039')
+
+
+class D11B_GenerateLogbook_Test(systemtesting.MantidSystemTest):
+    """
+    Tests generating logbook for D11B data.
+    """
+
+    _data_directory = None
+
+    def __init__(self):
+        super(D11B_GenerateLogbook_Test, self).__init__()
+        self.setUp()
+
+    def setUp(self):
+        config['default.facility'] = 'ILL'
+        config['default.instrument'] = 'D11B'
+        config['logging.loggers.root.level'] = 'Warning'
+
+        data_dirs = config['datasearch.directories'].split(';')
+        test_data_dir = [p for p in data_dirs if 'SystemTest' in p][0]
+        d11b_dir = 'ILL/D11B'
+        if 'ILL' in test_data_dir:
+            d11b_dir = 'D11B'
+        self._data_directory = os.path.abspath(os.path.join(test_data_dir,  d11b_dir))
+
+    def cleanup(self):
+        mtd.clear()
+
+    def validate(self):
+        self.tolerance = 1e-3
+        self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
+        return ['d11b_logbook', 'D11b_Logbook_Reference.nxs']
+
+    def runTest(self):
+        GenerateLogbook(Directory=self._data_directory, OutputWorkspace='d11b_logbook',
+                        Facility='ILL', Instrument='D11B', NumorRange='000361,000362',
+                        OptionalHeaders='all')
+
+
+class D22_GenerateLogbook_Test(systemtesting.MantidSystemTest):
+    """
+    Tests generating logbook for D22 data.
+    """
+
+    _data_directory = None
+
+    def __init__(self):
+        super(D22_GenerateLogbook_Test, self).__init__()
+        self.setUp()
+
+    def setUp(self):
+        config['default.facility'] = 'ILL'
+        config['default.instrument'] = 'D22'
+        config['logging.loggers.root.level'] = 'Warning'
+
+        data_dirs = config['datasearch.directories'].split(';')
+        test_data_dir = [p for p in data_dirs if 'SystemTest' in p][0]
+        d22_dir = 'ILL/D22'
+        if 'ILL' in test_data_dir:
+            d22_dir = 'D22'
+        self._data_directory = os.path.abspath(os.path.join(test_data_dir,  d22_dir))
+
+    def cleanup(self):
+        mtd.clear()
+
+    def validate(self):
+        self.tolerance = 1e-3
+        self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
+        return ['d22_logbook', 'D22_Logbook_Reference.nxs']
+
+    def runTest(self):
+        GenerateLogbook(Directory=self._data_directory, OutputWorkspace='d22_logbook',
+                        Facility='ILL', Instrument='D22', NumorRange='354717,354718',
+                        OptionalHeaders='all')
+
+
+class D22B_GenerateLogbook_Test(systemtesting.MantidSystemTest):
+    """
+    Tests generating logbook for D22B data.
+    """
+
+    _data_directory = None
+
+    def __init__(self):
+        super(D22B_GenerateLogbook_Test, self).__init__()
+        self.setUp()
+
+    def setUp(self):
+        config['default.facility'] = 'ILL'
+        config['default.instrument'] = 'D22B'
+        config['logging.loggers.root.level'] = 'Warning'
+
+        data_dirs = config['datasearch.directories'].split(';')
+        test_data_dir = [p for p in data_dirs if 'SystemTest' in p][0]
+        d22b_dir = 'ILL/D22B'
+        if 'ILL' in test_data_dir:
+            d22b_dir = 'D22B'
+        self._data_directory = os.path.abspath(os.path.join(test_data_dir,  d22b_dir))
+
+    def cleanup(self):
+        mtd.clear()
+
+    def validate(self):
+        self.tolerance = 1e-3
+        self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
+        return ['d22b_logbook', 'D22b_Logbook_Reference.nxs']
+
+    def runTest(self):
+        GenerateLogbook(Directory=self._data_directory, OutputWorkspace='d22b_logbook',
+                        Facility='ILL', Instrument='D22B', NumorRange='398672,398673',
+                        OptionalHeaders='all')
+
+
+class IN4_GenerateLogbook_Test(systemtesting.MantidSystemTest):
+    """
+    Tests generating logbook for IN4 data.
+    """
+
+    _data_directory = None
+
+    def __init__(self):
+        super(IN4_GenerateLogbook_Test, self).__init__()
+        self.setUp()
+
+    def setUp(self):
+        config['default.facility'] = 'ILL'
+        config['default.instrument'] = 'IN4'
+        config['logging.loggers.root.level'] = 'Warning'
+
+        data_dirs = config['datasearch.directories'].split(';')
+        test_data_dir = [p for p in data_dirs if 'SystemTest' in p][0]
+        in4_dir = 'ILL/IN4'
+        if 'ILL' in test_data_dir:
+            in4_dir = 'IN4'
+        self._data_directory = os.path.abspath(os.path.join(test_data_dir,  in4_dir))
+
+    def cleanup(self):
+        mtd.clear()
+
+    def validate(self):
+        self.tolerance = 1e-3
+        self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
+        return ['in4_logbook', 'IN4_Logbook_Reference.nxs']
+
+    def runTest(self):
+        GenerateLogbook(Directory=self._data_directory, OutputWorkspace='in4_logbook',
+                        Facility='ILL', Instrument='IN4', NumorRange='092375,092376',
+                        OptionalHeaders='all')
+
+
+class IN5_GenerateLogbook_Test(systemtesting.MantidSystemTest):
+    """
+    Tests generating logbook for IN5 data.
+    """
+
+    _data_directory = None
+
+    def __init__(self):
+        super(IN5_GenerateLogbook_Test, self).__init__()
+        self.setUp()
+
+    def setUp(self):
+        config['default.facility'] = 'ILL'
+        config['default.instrument'] = 'IN5'
+        config['logging.loggers.root.level'] = 'Warning'
+
+        data_dirs = config['datasearch.directories'].split(';')
+        test_data_dir = [p for p in data_dirs if 'SystemTest' in p][0]
+        in5_dir = 'ILL/IN5'
+        if 'ILL' in test_data_dir:
+            in5_dir = 'IN5'
+        self._data_directory = os.path.abspath(os.path.join(test_data_dir,  in5_dir))
+
+    def cleanup(self):
+        mtd.clear()
+
+    def validate(self):
+        self.tolerance = 1e-3
+        self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
+        return ['in5_logbook', 'IN5_Logbook_Reference.nxs']
+
+    def runTest(self):
+        GenerateLogbook(Directory=self._data_directory, OutputWorkspace='in5_logbook',
+                        Facility='ILL', Instrument='IN5', NumorRange='199728,199729',
+                        OptionalHeaders='all')
+
+
+class IN6_GenerateLogbook_Test(systemtesting.MantidSystemTest):
+    """
+    Tests generating logbook for IN6 data.
+    """
+
+    _data_directory = None
+
+    def __init__(self):
+        super(IN6_GenerateLogbook_Test, self).__init__()
+        self.setUp()
+
+    def setUp(self):
+        config['default.facility'] = 'ILL'
+        config['default.instrument'] = 'IN6'
+        config['logging.loggers.root.level'] = 'Warning'
+
+        data_dirs = config['datasearch.directories'].split(';')
+        test_data_dir = [p for p in data_dirs if 'SystemTest' in p][0]
+        in6_dir = 'ILL/IN6'
+        if 'ILL' in test_data_dir:
+            in6_dir = 'IN6'
+        self._data_directory = os.path.abspath(os.path.join(test_data_dir,  in6_dir))
+
+    def cleanup(self):
+        mtd.clear()
+
+    def validate(self):
+        self.tolerance = 1e-3
+        self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
+        return ['in6_logbook', 'IN6_Logbook_Reference.nxs']
+
+    def runTest(self):
+        GenerateLogbook(Directory=self._data_directory, OutputWorkspace='in6_logbook',
+                        Facility='ILL', Instrument='IN6', NumorRange='224436,224437',
+                        OptionalHeaders='all')
+
+
+class D33_GenerateLogbook_Test(systemtesting.MantidSystemTest):
+    """
+    Tests generating logbook for D33 data.
+    """
+
+    _data_directory = None
+
+    def __init__(self):
+        super(D33_GenerateLogbook_Test, self).__init__()
+        self.setUp()
+
+    def setUp(self):
+        config['default.facility'] = 'ILL'
+        config['default.instrument'] = 'D33'
+        config['logging.loggers.root.level'] = 'Warning'
+
+        data_dirs = config['datasearch.directories'].split(';')
+        test_data_dir = [p for p in data_dirs if 'SystemTest' in p][0]
+        d33_dir = 'ILL/D33'
+        if 'ILL' in test_data_dir:
+            d33_dir = 'D33'
+        self._data_directory = os.path.abspath(os.path.join(test_data_dir,  d33_dir))
+
+    def cleanup(self):
+        mtd.clear()
+
+    def validate(self):
+        self.tolerance = 1e-3
+        self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
+        return ['d33_logbook', 'D33_Logbook_Reference.nxs']
+
+    def runTest(self):
+        GenerateLogbook(Directory=self._data_directory, OutputWorkspace='d33_logbook',
+                        Facility='ILL', Instrument='D33', NumorRange='162689,162690',
+                        OptionalHeaders='all')
+
+
+class D16_GenerateLogbook_Test(systemtesting.MantidSystemTest):
+    """
+    Tests generating logbook for D16 data.
+    """
+
+    _data_directory = None
+
+    def __init__(self):
+        super(D16_GenerateLogbook_Test, self).__init__()
+        self.setUp()
+
+    def setUp(self):
+        config['default.facility'] = 'ILL'
+        config['default.instrument'] = 'D16'
+        config['logging.loggers.root.level'] = 'Warning'
+
+        data_dirs = config['datasearch.directories'].split(';')
+        test_data_dir = [p for p in data_dirs if 'SystemTest' in p][0]
+        d33_dir = 'ILL/D16'
+        if 'ILL' in test_data_dir:
+            d33_dir = 'D16'
+        self._data_directory = os.path.abspath(os.path.join(test_data_dir,  d33_dir))
+
+    def cleanup(self):
+        mtd.clear()
+
+    def validate(self):
+        self.tolerance = 1e-3
+        self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
+        return ['d16_logbook', 'D16_Logbook_Reference.nxs']
+
+    def runTest(self):
+        GenerateLogbook(Directory=self._data_directory, OutputWorkspace='d16_logbook',
+                        Facility='ILL', Instrument='D16', NumorRange='000245,000246',
+                        OptionalHeaders='all')
diff --git a/Testing/SystemTests/tests/framework/ISIS_PowderPolarisTest.py b/Testing/SystemTests/tests/framework/ISIS_PowderPolarisTest.py
index ef4cf93f5cf5eb4864ffff700166942a409f100d..ab3e737e644161e76c080e5ea59b3e13bdf22a33 100644
--- a/Testing/SystemTests/tests/framework/ISIS_PowderPolarisTest.py
+++ b/Testing/SystemTests/tests/framework/ISIS_PowderPolarisTest.py
@@ -63,8 +63,8 @@ class CreateVanadiumTest(systemtesting.MantidSystemTest):
         splined_ws, unsplined_ws = self.calibration_results
         for ws in splined_ws+unsplined_ws:
             self.assertEqual(ws.sample().getMaterial().name(), 'V')
-        return (unsplined_ws.name(), "ISIS_Powder-POLARIS00098533_unsplined.nxs",
-                splined_ws.name(), "ISIS_Powder-POLARIS00098533_splined.nxs")
+        return (unsplined_ws.name(), "ISIS_Powder-POLARIS00098532_unsplined.nxs",
+                splined_ws.name(), "ISIS_Powder-POLARIS00098532_splined.nxs")
 
     def cleanup(self):
         try:
@@ -108,7 +108,8 @@ class FocusTest(systemtesting.MantidSystemTest):
 
         for ws in self.focus_results:
             self.assertEqual(ws.sample().getMaterial().name(), 'Si')
-        self.tolerance = 1e-7
+        self.tolerance_is_rel_err = True
+        self.tolerance = 1e-6
         return self.focus_results.name(), "ISIS_Powder-POLARIS98533_FocusSempty.nxs"
 
     def cleanup(self):
diff --git a/Testing/SystemTests/tests/framework/PaalmanPingsMonteCarloAbsorptionTest.py b/Testing/SystemTests/tests/framework/PaalmanPingsMonteCarloAbsorptionTest.py
index 7decae3697b7c0eb0af2dfa419829fcdae66274e..c542a470b35e20b1d78d87ccfc47d60fb49c1a0a 100644
--- a/Testing/SystemTests/tests/framework/PaalmanPingsMonteCarloAbsorptionTest.py
+++ b/Testing/SystemTests/tests/framework/PaalmanPingsMonteCarloAbsorptionTest.py
@@ -41,7 +41,8 @@ class FlatPlateTest(systemtesting.MantidSystemTest):
             ContainerBackThickness=0.02,
             ContainerChemicalFormula='V',
             ContainerDensity=6.0,
-            CorrectionsWorkspace='flat_plate_corr'
+            CorrectionsWorkspace='flat_plate_corr',
+            EventsPerPoint = 5000
         )
 
 
@@ -76,7 +77,8 @@ class CylinderTest(systemtesting.MantidSystemTest):
             ContainerRadius=0.22,
             ContainerChemicalFormula='V',
             ContainerDensity=6.0,
-            CorrectionsWorkspace='cylinder_corr'
+            CorrectionsWorkspace='cylinder_corr',
+            EventsPerPoint=5000
         )
 
 
@@ -114,5 +116,5 @@ class AnnulusTest(systemtesting.MantidSystemTest):
             ContainerChemicalFormula='V',
             ContainerDensity=6.0,
             CorrectionsWorkspace='annulus_corr',
-            EventsPerPoint=5000
+            EventsPerPoint=10000
         )
diff --git a/Testing/SystemTests/tests/framework/SANSILLAutoProcessTest.py b/Testing/SystemTests/tests/framework/SANSILLAutoProcessTest.py
index 3ad111a128bb57b9fb187adda92732e30fdd1037..daad92bec6ed6cef1ff87084cd0ed07a25f7b289 100644
--- a/Testing/SystemTests/tests/framework/SANSILLAutoProcessTest.py
+++ b/Testing/SystemTests/tests/framework/SANSILLAutoProcessTest.py
@@ -33,6 +33,7 @@ class D11_AutoProcess_Test(systemtesting.MantidSystemTest):
     def validate(self):
         self.tolerance = 1e-3
         self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
         return ['out', 'D11_AutoProcess_Reference.nxs']
 
     def runTest(self):
@@ -91,6 +92,7 @@ class D11_AutoProcess_Wedges_Test(systemtesting.MantidSystemTest):
     def validate(self):
         self.tolerance = 1e-3
         self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
         return ['out', 'D11_AutoProcess_Wedges_Reference.nxs']
 
     def runTest(self):
@@ -158,6 +160,7 @@ class D11_AutoProcess_IQxQy_Test(systemtesting.MantidSystemTest):
     def validate(self):
         self.tolerance = 1e-3
         self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
         return ['iqxy', 'D11_AutoProcess_IQxQy_Reference.nxs']
 
     def runTest(self):
@@ -214,6 +217,7 @@ class D11_AutoProcess_Multiple_Transmissions_Test(systemtesting.MantidSystemTest
     def validate(self):
         self.tolerance = 1e-3
         self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
         return ['iq_mult_wavelengths', 'D11_AutoProcess_Multiple_Tr_Reference.nxs']
 
     def runTest(self):
@@ -269,6 +273,7 @@ class D33_AutoProcess_Test(systemtesting.MantidSystemTest):
     def validate(self):
         self.tolerance = 1e-3
         self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
         return ['out', 'D33_AutoProcess_Reference.nxs']
 
     def runTest(self):
@@ -320,6 +325,7 @@ class D33_AutoProcess_IPhiQ_Test(systemtesting.MantidSystemTest):
     def validate(self):
         self.tolerance = 1e-3
         self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
         return ['out', 'D33_AutoProcess_IPhiQ_Reference.nxs']
 
     def runTest(self):
@@ -459,6 +465,7 @@ class D22_AutoProcess_Single_Sensitivity(systemtesting.MantidSystemTest):
     def validate(self):
         self.tolerance = 1e-3
         self.tolerance_is_rel_err = True
+        self.disableChecking.append('Instrument')
         return ['d22_single_sens', 'D22_AutoProcess_Single_Sens_Reference.nxs']
 
     def runTest(self):
@@ -510,6 +517,7 @@ class D22_AutoProcess_Multi_Sensitivity(systemtesting.MantidSystemTest):
     def validate(self):
         self.tolerance = 1e-3
         self.tolerance_is_rel_err = True
+        self.disableChecking = ['Instrument']
         return ['sens', 'D22_AutoProcess_Multi_Sens_Reference.nxs']
 
     def runTest(self):
diff --git a/Testing/SystemTests/tests/framework/TOPAZPeakFinding.py b/Testing/SystemTests/tests/framework/TOPAZPeakFinding.py
index 55e6b9354bfd4e1a8921c25df0337e7b5486a8bc..104f80fc754bbae89dee2880ed5317b016ee7bd9 100644
--- a/Testing/SystemTests/tests/framework/TOPAZPeakFinding.py
+++ b/Testing/SystemTests/tests/framework/TOPAZPeakFinding.py
@@ -13,6 +13,7 @@ them.
 import systemtesting
 import numpy
 from mantid.simpleapi import *
+from mantid.dataobjects import PeaksWorkspace, LeanElasticPeaksWorkspace
 
 
 class TOPAZPeakFinding(systemtesting.MantidSystemTest):
@@ -72,6 +73,42 @@ class TOPAZPeakFinding(systemtesting.MantidSystemTest):
         ConvertToDiffractionMDWorkspace(InputWorkspace='topaz_3132',OutputWorkspace='topaz_3132_QSample',
                                         OutputDimensions='Q (sample frame)',LorentzCorrection='1',SplitInto='2',SplitThreshold='150')
         FindPeaksMD(InputWorkspace='topaz_3132_QSample',PeakDistanceThreshold='0.12',MaxPeaks='200',OutputWorkspace='peaks_QSample')
+        self.assertTrue(isinstance(mtd['peaks_QSample'], PeaksWorkspace))
+        FindUBUsingFFT(PeaksWorkspace='peaks_QSample',MinD='2',MaxD='16')
+        CopySample(InputWorkspace='peaks_QSample',OutputWorkspace='topaz_3132',CopyName='0',CopyMaterial='0',
+                   CopyEnvironment='0',CopyShape='0')
+
+        # Index the peaks and check
+        results = IndexPeaks(PeaksWorkspace='peaks_QSample')
+        indexed = results[0]
+        if indexed < 199:
+            raise Exception("Expected at least 199 of 200 peaks to be indexed. Only indexed %d!" % indexed)
+
+        # Check the UB matrix
+        w = mtd["topaz_3132"]
+        s = w.sample()
+        ol = s.getOrientedLattice()
+        self.assertDelta( ol.a(), 4.714, 0.01, "Correct lattice a value not found.")
+        self.assertDelta( ol.b(), 6.06, 0.01, "Correct lattice b value not found.")
+        self.assertDelta( ol.c(), 10.42, 0.01, "Correct lattice c value not found.")
+        self.assertDelta( ol.alpha(), 90, 0.4, "Correct lattice angle alpha value not found.")
+        self.assertDelta( ol.beta(), 90, 0.4, "Correct lattice angle beta value not found.")
+        self.assertDelta( ol.gamma(), 90, 0.4, "Correct lattice angle gamma value not found.")
+
+        # Compare new and old UBs
+        newUB = numpy.array(mtd["topaz_3132"].sample().getOrientedLattice().getUB())
+        # UB Matrices are not necessarily the same, some of the H,K and/or L sign can be reversed
+        diff = abs(newUB) - abs(originalUB) < 0.001
+        for c in range(3):
+            # This compares each column, allowing old == new OR old == -new
+            if not numpy.all(diff[:,c]) :
+                raise Exception("More than 0.001 difference between UB matrices: Q (lab frame):\n"
+                                "%s\nQ (sample frame):\n%s" % (originalUB, newUB) )
+
+        # repeat but use LeanElasticPeaks
+        FindPeaksMD(InputWorkspace='topaz_3132_QSample',PeakDistanceThreshold='0.12',MaxPeaks='200',OutputWorkspace='peaks_QSample',
+                    OutputType='LeanElasticPeak')
+        self.assertTrue(isinstance(mtd['peaks_QSample'], LeanElasticPeaksWorkspace))
         FindUBUsingFFT(PeaksWorkspace='peaks_QSample',MinD='2',MaxD='16')
         CopySample(InputWorkspace='peaks_QSample',OutputWorkspace='topaz_3132',CopyName='0',CopyMaterial='0',
                    CopyEnvironment='0',CopyShape='0')
diff --git a/Testing/SystemTests/tests/framework/reference/D11B_Logbook_Reference.nxs.md5 b/Testing/SystemTests/tests/framework/reference/D11B_Logbook_Reference.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..298c4bf5527b195ebc5d6b39e62810c8ed03e313
--- /dev/null
+++ b/Testing/SystemTests/tests/framework/reference/D11B_Logbook_Reference.nxs.md5
@@ -0,0 +1 @@
+2b1f7951b15fca02375ebc975979c93a
diff --git a/Testing/SystemTests/tests/framework/reference/D11_Logbook_Reference.nxs.md5 b/Testing/SystemTests/tests/framework/reference/D11_Logbook_Reference.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..af69a9709ab62a355459c54edc304852bcb324b3
--- /dev/null
+++ b/Testing/SystemTests/tests/framework/reference/D11_Logbook_Reference.nxs.md5
@@ -0,0 +1 @@
+e0ac4f36057d882aa3c5645e471357b5
diff --git a/Testing/SystemTests/tests/framework/reference/D16_Logbook_Reference.nxs.md5 b/Testing/SystemTests/tests/framework/reference/D16_Logbook_Reference.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..46430a4690058189be7818c71ae4d061cfdb185a
--- /dev/null
+++ b/Testing/SystemTests/tests/framework/reference/D16_Logbook_Reference.nxs.md5
@@ -0,0 +1 @@
+80415cf8496a981a3829d3882f69d640
diff --git a/Testing/SystemTests/tests/framework/reference/D22B_Logbook_Reference.nxs.md5 b/Testing/SystemTests/tests/framework/reference/D22B_Logbook_Reference.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..311a92917bc9a2f1d36295d48556e5d894447494
--- /dev/null
+++ b/Testing/SystemTests/tests/framework/reference/D22B_Logbook_Reference.nxs.md5
@@ -0,0 +1 @@
+bf493c30907b261a4c1b3e63f45c8cba
diff --git a/Testing/SystemTests/tests/framework/reference/D22_Logbook_Reference.nxs.md5 b/Testing/SystemTests/tests/framework/reference/D22_Logbook_Reference.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..c18506d5102f050748a45cb660b89d291366f4e5
--- /dev/null
+++ b/Testing/SystemTests/tests/framework/reference/D22_Logbook_Reference.nxs.md5
@@ -0,0 +1 @@
+840ab02b3f81afaa497c8dbb4c1517df
diff --git a/Testing/SystemTests/tests/framework/reference/D33_Logbook_Reference.nxs.md5 b/Testing/SystemTests/tests/framework/reference/D33_Logbook_Reference.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..a551ee059df00bf6717fcacd82f65e11fab84add
--- /dev/null
+++ b/Testing/SystemTests/tests/framework/reference/D33_Logbook_Reference.nxs.md5
@@ -0,0 +1 @@
+1d9fec71735f380892668eea28c8b7b3
diff --git a/Testing/SystemTests/tests/framework/reference/HRPD66063_focused.nxs.md5 b/Testing/SystemTests/tests/framework/reference/HRPD66063_focused.nxs.md5
index e15d64c7d356898159b330d8f8663cda9f1a4913..cfc032862af689ccf1afa5bee1841445b4c29009 100644
--- a/Testing/SystemTests/tests/framework/reference/HRPD66063_focused.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/HRPD66063_focused.nxs.md5
@@ -1 +1 @@
-8c3a6b213a055248e8a24a7057c095d6
+1be2026d1de55fd2bed0a6f8ddfc4d39
diff --git a/Testing/SystemTests/tests/framework/reference/HRPD66063_focused_with_sac.nxs.md5 b/Testing/SystemTests/tests/framework/reference/HRPD66063_focused_with_sac.nxs.md5
index 7722a1b9661b05fe79506c46e5336a1757b372c2..4c86bda5634b41b8796664a98d26c391e924ba3f 100644
--- a/Testing/SystemTests/tests/framework/reference/HRPD66063_focused_with_sac.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/HRPD66063_focused_with_sac.nxs.md5
@@ -1 +1 @@
-4e22b59dcf154fe612664caad97106a5
+37ca9e41dadb8209c48a94456b293424
diff --git a/Testing/SystemTests/tests/framework/reference/IN4_Logbook_Reference.nxs.md5 b/Testing/SystemTests/tests/framework/reference/IN4_Logbook_Reference.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..c188c3f8d6cb65301c53afb8eca346dbe3161429
--- /dev/null
+++ b/Testing/SystemTests/tests/framework/reference/IN4_Logbook_Reference.nxs.md5
@@ -0,0 +1 @@
+d6b5e23810e538cdaa81e9bf93dbf024
diff --git a/Testing/SystemTests/tests/framework/reference/IN5_Logbook_Reference.nxs.md5 b/Testing/SystemTests/tests/framework/reference/IN5_Logbook_Reference.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..89d4c16886be802bbfb99048b1e7d93015ab9231
--- /dev/null
+++ b/Testing/SystemTests/tests/framework/reference/IN5_Logbook_Reference.nxs.md5
@@ -0,0 +1 @@
+d76718b8119d6e6be0e916e88be3d790
diff --git a/Testing/SystemTests/tests/framework/reference/IN6_Logbook_Reference.nxs.md5 b/Testing/SystemTests/tests/framework/reference/IN6_Logbook_Reference.nxs.md5
new file mode 100644
index 0000000000000000000000000000000000000000..b143237a352bc43cd505e216c01cd1e8c1439256
--- /dev/null
+++ b/Testing/SystemTests/tests/framework/reference/IN6_Logbook_Reference.nxs.md5
@@ -0,0 +1 @@
+2b3efd1c722dcc1b54b81ec5f1482bdd
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty.nxs.md5
index 829a5bf235854f5599dbea56c6aace748e8e462b..14147199e4f3b4eac0211ba479716e0c9cc6ca99 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty.nxs.md5
@@ -1 +1 @@
-66daa3c0ca467551327f0fab16603176
+ecea38a404523a3760af402b23ba138d
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty_abscorr.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty_abscorr.nxs.md5
index 46f87415aa779faca870815277b7f888c5c4c979..450cc2132b9b75e641d34f083adba7269c33639e 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty_abscorr.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM83605_FocusSempty_abscorr.nxs.md5
@@ -1 +1 @@
-4449362e48a44a6b9dd022e94bbea290
+3a4603faeb65bdac0c39438df73b6ae1
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_grouped.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_grouped.nxs.md5
index 99802ed127ec851ff85bead36382901d21b32a79..ae217da0e72640706087aaefe87c89afc64d9729 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_grouped.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_grouped.nxs.md5
@@ -1 +1 @@
-66c9c9248b6aa178ea50be7722aebaca
+49479cfe5b2ef222d6fef8e7262b33fe
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_groupedGSAS1.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_groupedGSAS1.nxs.md5
index 88231da7cc10cd6fefd727e7af62ccf34d6eb92a..7db8702743e3420c7f6c5e6413ce35850f9b2e17 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_groupedGSAS1.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-GEM87618_groupedGSAS1.nxs.md5
@@ -1 +1 @@
-8e69351a25f506a59d3de8ca6a68403a
+9cf6f0ffda00dfcad3d186590434b7d5
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098472_splined.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098472_splined.nxs.md5
index 52b72dec866425098afe83ef1cc55dd1358c473b..1c048e99c58ca027af4b79664bf7dd11ae318028 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098472_splined.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098472_splined.nxs.md5
@@ -1 +1 @@
-a828d3c74ab4ce5876aae29e61794f22
+912967bbcca604e98b02540b2d1d069d
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5
index 62a7f42f93600ba829c93c99d8dfc8fd584728fc..66f96e555bb1b0f41cde7af01b5435e8c47a19d2 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70Atten.nxs.md5
@@ -1 +1 @@
-1b79639f158a73a1f1f4d76da14f4ae4
+fdacd843f9fbe1f8309fcc55d75ffa3a
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70NoEmptySub.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70NoEmptySub.nxs.md5
index ba8e617da7b8af635a8753278a2c1202ebbbd54b..b8dcb8acae0c7a8f5da2a09983a026958d1e10e2 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70NoEmptySub.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70NoEmptySub.nxs.md5
@@ -1 +1 @@
-205ea520333c267369b8c750b7c8afaf
+1d4149c223976a13cc0231315fd8a423
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5
index 74e9ea2361d64e5e0e147dd88fe2ab9caecdc2c9..769f6dfcd6d28a2384005e09d8ff209e8d83b57f 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL00098507_tt70_absorb.nxs.md5
@@ -1 +1 @@
-a22807ee5cd6565886832ddba9cb3f16
+7ec38fcc0e659a3941b36a5384a309a3
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL98494_grouped.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL98494_grouped.nxs.md5
index 85159fd3ae5819d82f207365d911baf49b3d05a1..3a984c302608ecf0f833fde8e41a5e9d5f2338f2 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL98494_grouped.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-PEARL98494_grouped.nxs.md5
@@ -1 +1 @@
-b957a6b0928e12a8c86284ea010df429
\ No newline at end of file
+de74dd3f5c3df66740a0f3b5c955b16d
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098533_splined.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098532_splined.nxs.md5
similarity index 100%
rename from Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098533_splined.nxs.md5
rename to Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098532_splined.nxs.md5
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098533_unsplined.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098532_unsplined.nxs.md5
similarity index 100%
rename from Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098533_unsplined.nxs.md5
rename to Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS00098532_unsplined.nxs.md5
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_Auto_chopper.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_Auto_chopper.nxs.md5
index 1495b8317052730570e375f0b6d6ddeb088472a7..5b6652bd1903ef3dadbc9d9528670248d2ef4215 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_Auto_chopper.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_Auto_chopper.nxs.md5
@@ -1 +1 @@
-ea4a0c8a223ab9f0fe8d06fc9e1604d3
+d5cd699c2749e999f2273f78c08fff47
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_FocusSempty.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_FocusSempty.nxs.md5
index 37c1f1bed76e241bbdd40668ee219183b1db59db..5b3ac0c44df6e27058b4b652e46c9d94183fd6d1 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_FocusSempty.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder-POLARIS98533_FocusSempty.nxs.md5
@@ -1 +1 @@
-281e4969b42ff1e891bf3c1377bdc8de
+7d22193e2a7634cfe359b95f94e4dab7
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_all.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_all.nxs.md5
index 2998cab6e1cd122a707ab8ecdec4bea7f4b7509b..3e757cf3177a9c16c97b49bfdd6f76f377d53f72 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_all.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_all.nxs.md5
@@ -1 +1 @@
-92c4276dd271f2ad8d5e7bb37c089b57
+f1a2f87e9dac226355703b9e61cb82f0
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_groups.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_groups.nxs.md5
index c282ebfdfbd6e0a7ce3977517152be5cd193b447..ca5e1efc08be24576181f263365e80e1ebcffa60 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_groups.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_groups.nxs.md5
@@ -1 +1 @@
-3c19d9a0702c746db0b0ddb6a760abca
+80892f681d9cb6f598e50c57294ba44f
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_mods.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_mods.nxs.md5
index 5211bfef5affcff5ddefb92349b73cca0e850a9b..af4d09cc5803b7a8fde265c7df75b7030734b3be 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_mods.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_mods.nxs.md5
@@ -1 +1 @@
-1f5ba298ba0297181053bbae8c470a64
+ad8b3130bc3821f7f678798483a244fd
diff --git a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_trans.nxs.md5 b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_trans.nxs.md5
index 47f45cfc401751c784aa7a3646308395142d4f23..651fa9fe2309ae53d224de9f70a89aa4ab1e0802 100644
--- a/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_trans.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/ISIS_Powder_PRL98472_tt70_trans.nxs.md5
@@ -1 +1 @@
-719362df6faebf097f07fc95ecbaba9c
+76defafd738c04e38fa289bff568e294
diff --git a/Testing/SystemTests/tests/framework/reference/PG3_4844_reference.gsa.md5 b/Testing/SystemTests/tests/framework/reference/PG3_4844_reference.gsa.md5
index f5c92d08a9ac9f48fda4dbfbd878c4767fd0207e..98d9db432d9b07e6e21bbc8d664e66a55529d550 100644
--- a/Testing/SystemTests/tests/framework/reference/PG3_4844_reference.gsa.md5
+++ b/Testing/SystemTests/tests/framework/reference/PG3_4844_reference.gsa.md5
@@ -1 +1 @@
-8882c0f4cc428087529940ba49e3bc08
+dbde6312703bb8ab2de56edd29384e18
diff --git a/Testing/SystemTests/tests/framework/reference/PG3_4866_reference.gsa.md5 b/Testing/SystemTests/tests/framework/reference/PG3_4866_reference.gsa.md5
index 1d04c3a13d027d6d1e3040ca106f842398ce08f1..58dbd55244510d434cf0de02d536f91d02099c65 100644
--- a/Testing/SystemTests/tests/framework/reference/PG3_4866_reference.gsa.md5
+++ b/Testing/SystemTests/tests/framework/reference/PG3_4866_reference.gsa.md5
@@ -1 +1 @@
-27a8c7ff7c64574ea6034632f367c991
+b11628262a659bcb05a1368cd3ebe7c6
diff --git a/Testing/SystemTests/tests/framework/reference/irs_PP_MC_annulus.nxs.md5 b/Testing/SystemTests/tests/framework/reference/irs_PP_MC_annulus.nxs.md5
index 61486a33f7d85a28eb64740763a1a1db0db3b2ae..f54b15880090edcd5ac83a17cb15dd7047ed7387 100644
--- a/Testing/SystemTests/tests/framework/reference/irs_PP_MC_annulus.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/irs_PP_MC_annulus.nxs.md5
@@ -1 +1 @@
-ef7856d3e0a14466775eca85e505467f
+bb97dc39d482257c7550d5be0d70b963
diff --git a/Testing/SystemTests/tests/framework/reference/irs_PP_MC_cylinder.nxs.md5 b/Testing/SystemTests/tests/framework/reference/irs_PP_MC_cylinder.nxs.md5
index 7ba909cb94cf9084d188cc29317393350d568633..7839aa947e0e837933589638ad64b33d4d347a7d 100644
--- a/Testing/SystemTests/tests/framework/reference/irs_PP_MC_cylinder.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/irs_PP_MC_cylinder.nxs.md5
@@ -1 +1 @@
-2bdfdb8111a0963ba744c9b28996a666
+89bc63b5688fa02e399a5e957aa36a57
diff --git a/Testing/SystemTests/tests/framework/reference/irs_PP_MC_flat_plate.nxs.md5 b/Testing/SystemTests/tests/framework/reference/irs_PP_MC_flat_plate.nxs.md5
index 861d88b2b2c90fb393ebf766f30cd2b0e0f9c746..830031eb41976bdc4649fa95a5bc0fa001b4f3d0 100644
--- a/Testing/SystemTests/tests/framework/reference/irs_PP_MC_flat_plate.nxs.md5
+++ b/Testing/SystemTests/tests/framework/reference/irs_PP_MC_flat_plate.nxs.md5
@@ -1 +1 @@
-aedc8214e6828f2752e8d1119cbcb1fa
+0259338bccfe474bcebef8ebae2f27c9
diff --git a/buildconfig/CMake/ExternalSipPyQt4.cmake b/buildconfig/CMake/ExternalSipPyQt4.cmake
deleted file mode 100644
index 3b9771942d80b98f0947d9944fd6735654cc7dc8..0000000000000000000000000000000000000000
--- a/buildconfig/CMake/ExternalSipPyQt4.cmake
+++ /dev/null
@@ -1,82 +0,0 @@
-include(ExternalProject)
-include(ProcessorCount)
-
-set(_SIP_PYQT_DIR extern-pyt4-sip)
-set(_SIP_PYQT_INSTALL_DIR ${_SIP_PYQT_DIR}/install)
-
-# sipdir - use different variables to standard sip installation to be able to distingish it
-set(PYQT4_SIP_INCLUDE_DIR "${CMAKE_BINARY_DIR}/${_SIP_PYQT_INSTALL_DIR}/include"  CACHE STRING "sip include directory" FORCE)
-set(PYQT4_SIP_EXECUTABLE "${CMAKE_BINARY_DIR}/${_SIP_PYQT_INSTALL_DIR}/bin/sip" CACHE STRING "sip executable" FORCE)
-set(PYQT4_SIP_VERSION "041307" CACHE STRING "sip hexadecimal string" FORCE)
-set(PYQT4_SIP_VERSION_STR "4.19.7" CACHE STRING "sip version string" FORCE)
-ExternalProject_Add(extern-pyqt4-sip
-  PREFIX ${_SIP_PYQT_DIR}/sip
-  INSTALL_DIR ${_SIP_PYQT_INSTALL_DIR}
-  URL https://sourceforge.net/projects/pyqt/files/sip/sip-4.19.7/sip-4.19.7.tar.gz/download
-  URL_HASH MD5=ae4f2db79713046d61b2a44e5ee1e3ab
-  CONFIGURE_COMMAND "${Python_EXECUTABLE}" "<SOURCE_DIR>/configure.py"
-    --sip-module=PyQt4.sip
-    --bindir=<INSTALL_DIR>/bin
-    --destdir=<INSTALL_DIR>/lib/site-packages
-    --incdir=<INSTALL_DIR>/include
-    --sipdir=<INSTALL_DIR>/share/sip
-  BUILD_COMMAND make 2> build.log
-)
-
-# PyQt4
-set(PYQT4_VERSION "040c01" CACHE STRING "PyQt4's version as a 6-digit hexadecimal number" FORCE)
-set(PYQT4_VERSION_STR "4.12.1" CACHE STRING "PyQt4's version as a human-readable string" FORCE)
-set(PYQT4_VERSION_TAG "Qt_4_8_6" CACHE STRING "The Qt version tag used by PyQt4's .sip files" FORCE)
-set(PYQT4_SIP_DIR "${CMAKE_BINARY_DIR}/${_SIP_PYQT_INSTALL_DIR}/share/sip" CACHE PATH "The base directory where PyQt4's .sip files are installed" FORCE)
-set(PYQT4_SIP_FLAGS "-x VendorID -t WS_X11 -x PyQt_NoPrintRangeBug -t Qt_4_8_6" CACHE STRING "The SIP flags used to build PyQt4" FORCE)
-set(PRIVATE_PYQT_SITE_PACKAGES ${CMAKE_BINARY_DIR}/${_SIP_PYQT_INSTALL_DIR}/lib/site-packages)
-set(_pyqt4_lib_site_packages ${PRIVATE_PYQT_SITE_PACKAGES}/PyQt4)
-
-# Write a wrapper pyuic script so it can find out internal copy of PyQt4
-set(PYQT4_PYUIC "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/internal-pyuic.py" CACHE STRING "Location of the pyuic script" FORCE)
-configure_file(${CMAKE_MODULE_PATH}/internal-pyuic.py.in ${PYQT4_PYUIC} @ONLY)
-
-# Determine core count for make step
-ProcessorCount(NPROCESSORS)
-if(NPROCESSORS EQUAL 0)
-  set(NPROCESSORS 1)
-endif()
-
-ExternalProject_Add(extern-pyqt4
-  PREFIX ${_SIP_PYQT_DIR}/pyqt4
-  INSTALL_DIR ${_SIP_PYQT_INSTALL_DIR}
-  URL https://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.12.1/PyQt4_gpl_x11-4.12.1.tar.gz/download
-  URL_HASH MD5=0112e15858cd7d318a09e7366922f874
-  PATCH_COMMAND patch -p1 --input ${CMAKE_SOURCE_DIR}/buildconfig/CMake/pyqt4_qreal_float_support.patch
-    COMMAND patch -p0 --input ${CMAKE_SOURCE_DIR}/buildconfig/CMake/pyqt4_disable_unnecessary_modules.patch
-    # patch configure to pick up sipconfig built above
-    COMMAND sed -i -e "/^import sipconfig/i sys.path.insert(0, \"${_pyqt4_lib_site_packages}\")" "<SOURCE_DIR>/configure.py"
-  CONFIGURE_COMMAND "${Python_EXECUTABLE}" "<SOURCE_DIR>/configure.py"
-    --assume-shared
-    --confirm-license
-    --bindir=<INSTALL_DIR>/bin
-    --destdir=<INSTALL_DIR>/lib/site-packages
-    --sipdir=<INSTALL_DIR>/share/sip
-    --no-designer-plugin
-    --no-timestamp
-    --no-deprecated
-    --qmake=/usr/bin/qmake-qt4
-    --no-qsci-api
-  BUILD_COMMAND make -j${NPROCESSORS} 2> build.log
-  DEPENDS extern-pyqt4-sip
-)
-
-# Write out .pth file to find this. We ensure to insert our path ahead of others so it takes precendence over the system
-# We assume we only have to support a single-configuration build type on Linux
-file(WRITE ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/private-pyqt4.pth
-"import sys; sys.__plen = len(sys.path)
-${PRIVATE_PYQT_SITE_PACKAGES}
-${_pyqt4_lib_site_packages}
-import sys; new = sys.path[sys.__plen:]; del sys.path[sys.__plen:]; p = getattr(sys, '__egginsert', 0); sys.path[p:p] = new; sys.__egginsert = p + len(new)
-")
-
-# Package PyQt. We assume this is for Python 3
-install(DIRECTORY ${PRIVATE_PYQT_SITE_PACKAGES}/PyQt4
-        DESTINATION ${LIB_DIR}
-        PATTERN "__pycache__" EXCLUDE
-        PATTERN "port_v2" EXCLUDE)
diff --git a/buildconfig/CMake/pyqt4_disable_unnecessary_modules.patch b/buildconfig/CMake/pyqt4_disable_unnecessary_modules.patch
deleted file mode 100644
index 0d44a7f01d1fefb491cd5cec9fbf0ac259eabfda..0000000000000000000000000000000000000000
--- a/buildconfig/CMake/pyqt4_disable_unnecessary_modules.patch
+++ /dev/null
@@ -1,94 +0,0 @@
---- configure.py.orig	2020-01-09 15:07:19.231636923 +0000
-+++ configure.py	2020-01-09 15:10:28.159688544 +0000
-@@ -1,19 +1,19 @@
- # This script generates the PyQt configuration and generates the Makefiles.
- #
- # Copyright (c) 2016 Riverbank Computing Limited <info@riverbankcomputing.com>
--#
-+#
- # This file is part of PyQt4.
--#
-+#
- # This file may be used under the terms of the GNU General Public License
- # version 3.0 as published by the Free Software Foundation and appearing in
- # the file LICENSE included in the packaging of this file.  Please review the
- # following information to ensure the GNU General Public License version 3.0
- # requirements will be met: http://www.gnu.org/copyleft/gpl.html.
--#
-+#
- # If you do not wish to use this file under the terms of the GPL version 3.0
- # then you may purchase a commercial license.  For more information contact
- # info@riverbankcomputing.com.
--#
-+#
- # This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
- # WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
-
-@@ -90,7 +90,7 @@ def find_default_qmake():
-
-     for d in path.split(os.pathsep):
-         qmake = os.path.join(d, base_qmake)
--
-+
-         if os.access(qmake, os.X_OK):
-             return qmake
-
-@@ -109,7 +109,7 @@ def create_optparser():
-         if not os.path.isdir(value):
-             raise optparse.OptionValueError("'%s' is not a directory" % value)
-         setattr(parser.values, option.dest, os.path.abspath(value))
--
-+
-     def store_abspath_file(option, opt_str, value, parser):
-         if not os.path.isfile(value):
-             raise optparse.OptionValueError("'%s' is not a file" % value)
-@@ -342,8 +342,8 @@ class ConfigurePyQt4:
-
-         check_module("QtGui", "qwidget.h", "new QWidget()")
-         check_module("QtHelp", "qhelpengine.h", "new QHelpEngine(\"foo\")")
--        check_module("QtMultimedia", "QAudioDeviceInfo",
--                "new QAudioDeviceInfo()")
-+        # check_module("QtMultimedia", "QAudioDeviceInfo",
-+        #         "new QAudioDeviceInfo()")
-         check_module("QtNetwork", "qhostaddress.h", "new QHostAddress()")
-
-         # Qt v4.7 was current when we added support for QtDBus and we didn't
-@@ -364,14 +364,14 @@ class ConfigurePyQt4:
-                 extra_libs=sql_libs)
-         check_module("QtSvg", "qsvgwidget.h", "new QSvgWidget()")
-         check_module("QtTest", "QtTest", "QTest::qSleep(0)")
--        check_module("QtWebKit", "qwebpage.h", "new QWebPage()")
-+        # check_module("QtWebKit", "qwebpage.h", "new QWebPage()")
-         check_module("QtXml", "qdom.h", "new QDomDocument()")
-         check_module("QtXmlPatterns", "qxmlname.h", "new QXmlName()")
-         check_module("phonon", "phonon/videowidget.h",
-                 "new Phonon::VideoWidget()")
--        check_module("QtAssistant", "qassistantclient.h",
--                "new QAssistantClient(\"foo\")", extra_lib_dirs=ass_lib_dirs,
--                extra_libs=ass_libs)
-+        # check_module("QtAssistant", "qassistantclient.h",
-+        #         "new QAssistantClient(\"foo\")", extra_lib_dirs=ass_lib_dirs,
-+        #         extra_libs=ass_libs)
-
-         if qt_shared == '':
-             sipconfig.inform("QtDesigner module disabled with static Qt libraries.")
-@@ -503,8 +503,8 @@ class ConfigurePyQt4:
-         if "QtTest" in pyqt_modules:
-             generate_code("QtTest")
-
--        if "QtWebKit" in pyqt_modules:
--            generate_code("QtWebKit")
-+        # if "QtWebKit" in pyqt_modules:
-+        #     generate_code("QtWebKit")
-
-         if "QtXml" in pyqt_modules:
-             generate_code("QtXml")
-@@ -1511,7 +1511,7 @@ def needed_qt_libs(mname, qt_libs):
-         "QtSql": ["QtGui"],
-         "QtSvg": ["QtGui"],
-         "QtTest": ["QtGui"],
--        "QtWebKit": ["QtNetwork", "QtGui"],
-+        # "QtWebKit": ["QtNetwork", "QtGui"],
-         "QtXml": ["QtCore"],
-         "QtXmlPatterns": ["QtNetwork", "QtCore"],
-         "phonon": ["QtGui"],
diff --git a/buildconfig/CMake/pyqt4_qreal_float_support.patch b/buildconfig/CMake/pyqt4_qreal_float_support.patch
deleted file mode 100644
index d8c3ef74723e77f9ee28ca8303ebf7d669dc6708..0000000000000000000000000000000000000000
--- a/buildconfig/CMake/pyqt4_qreal_float_support.patch
+++ /dev/null
@@ -1,240 +0,0 @@
-From: Michael Casadevall <mcasadevall@debian.org>
-Date: Thu, 8 Oct 2015 12:56:35 -0700
-Subject: Add QList<double> support explicitly when qreal is not double
-
----
- sip/QtCore/qlist.sip | 224 +++++++++++++++++++++++++++++++++++++++++++++++++++
- 1 file changed, 224 insertions(+)
-
-diff --git a/sip/QtCore/qlist.sip b/sip/QtCore/qlist.sip
-index 978e576..63002d2 100644
---- a/sip/QtCore/qlist.sip
-+++ b/sip/QtCore/qlist.sip
-@@ -814,3 +814,227 @@ template<qreal, TYPE>
-     return sipGetState(sipTransferObj);
- %End
- };
-+
-+// If we're on an architecture where qreal != double, then we need to also
-+// explicately handle doubles. On architectures where qreal == double, they
-+// will automaticially be cast upwards
-+
-+%If (!PyQt_qreal_double)
-+
-+%If (Qt_4_3_0 -)
-+// QList<QPair<double, double> > is implemented as a Python list of 2-element tuples.
-+%MappedType QList<QPair<double, double> >
-+{
-+%TypeHeaderCode
-+#include <qlist.h>
-+#include <qpair.h>
-+%End
-+
-+%ConvertFromTypeCode
-+    // Create the list.
-+    PyObject *l;
-+
-+    if ((l = PyList_New(sipCpp->size())) == NULL)
-+        return NULL;
-+
-+    // Set the list elements.
-+    for (int i = 0; i < sipCpp->size(); ++i)
-+    {
-+        const QPair<double, double> &p = sipCpp->at(i);
-+        PyObject *pobj;
-+
-+        if ((pobj = Py_BuildValue((char *)"dd", p.first, p.second)) == NULL)
-+        {
-+            Py_DECREF(l);
-+
-+            return NULL;
-+        }
-+
-+        PyList_SET_ITEM(l, i, pobj);
-+    }
-+
-+    return l;
-+%End
-+
-+%ConvertToTypeCode
-+    SIP_SSIZE_T len;
-+
-+    // Check the type if that is all that is required.
-+    if (sipIsErr == NULL)
-+    {
-+        if (!PySequence_Check(sipPy) || (len = PySequence_Size(sipPy)) < 0)
-+            return 0;
-+
-+        for (SIP_SSIZE_T i = 0; i < len; ++i)
-+        {
-+            PyObject *tup = PySequence_ITEM(sipPy, i);
-+
-+            if (!PySequence_Check(tup) || PySequence_Size(tup) != 2)
-+                return 0;
-+        }
-+
-+        return 1;
-+    }
-+
-+    QList<QPair<double, double> > *ql = new QList<QPair<double, double> >;
-+    len = PySequence_Size(sipPy);
-+
-+    for (SIP_SSIZE_T i = 0; i < len; ++i)
-+    {
-+        PyObject *tup = PySequence_ITEM(sipPy, i);
-+
-+        double first = PyFloat_AsDouble(PySequence_ITEM(tup, 0));
-+        double second = PyFloat_AsDouble(PySequence_ITEM(tup, 1));
-+
-+        ql->append(QPair<double, double>(first, second));
-+    }
-+
-+    *sipCppPtr = ql;
-+
-+    return sipGetState(sipTransferObj);
-+%End
-+};
-+%End
-+%If (Qt_4_3_0 -)
-+// QList<QPair<double, TYPE> > is implemented as a Python list of 2-element tuples.
-+template<double, TYPE>
-+%MappedType QList<QPair<double, TYPE> >
-+{
-+%TypeHeaderCode
-+#include <qlist.h>
-+#include <qpair.h>
-+%End
-+
-+%ConvertFromTypeCode
-+    // Create the list.
-+    PyObject *l;
-+
-+    if ((l = PyList_New(sipCpp->size())) == NULL)
-+        return NULL;
-+
-+    // Set the list elements.
-+    for (int i = 0; i < sipCpp->size(); ++i)
-+    {
-+        const QPair<double, TYPE> &p = sipCpp->at(i);
-+        TYPE *t = new TYPE(p.second);
-+        PyObject *pobj;
-+
-+        if ((pobj = sipBuildResult(NULL, "(dB)", p.first, t, sipClass_TYPE, sipTransferObj)) == NULL)
-+        {
-+            Py_DECREF(l);
-+            delete t;
-+
-+            return NULL;
-+        }
-+
-+        PyList_SET_ITEM(l, i, pobj);
-+    }
-+
-+    return l;
-+%End
-+
-+%ConvertToTypeCode
-+    SIP_SSIZE_T len;
-+
-+    // Check the type if that is all that is required.
-+    if (sipIsErr == NULL)
-+    {
-+        if (!PySequence_Check(sipPy) || (len = PySequence_Size(sipPy)) < 0)
-+            return 0;
-+
-+        for (SIP_SSIZE_T i = 0; i < len; ++i)
-+        {
-+            PyObject *tup = PySequence_ITEM(sipPy, i);
-+
-+            if (!PySequence_Check(tup) || PySequence_Size(tup) != 2)
-+                return 0;
-+
-+            if (!sipCanConvertToInstance(PySequence_ITEM(tup, 1), sipClass_TYPE, SIP_NOT_NONE))
-+                return 0;
-+        }
-+
-+        return 1;
-+    }
-+
-+    QList<QPair<double, TYPE> > *ql = new QList<QPair<double, TYPE> >;
-+    len = PySequence_Size(sipPy);
-+
-+    for (SIP_SSIZE_T i = 0; i < len; ++i)
-+    {
-+        PyObject *tup = PySequence_ITEM(sipPy, i);
-+        double d;
-+        int state;
-+
-+        d = PyFloat_AsDouble(PySequence_ITEM(tup, 0));
-+        TYPE *t = reinterpret_cast<TYPE *>(sipConvertToInstance(PySequence_ITEM(tup, 1), sipClass_TYPE, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
-+
-+        if (*sipIsErr)
-+        {
-+            sipReleaseInstance(t, sipClass_TYPE, state);
-+
-+            delete ql;
-+            return 0;
-+        }
-+
-+        ql->append(QPair<double, TYPE>(d, *t));
-+
-+        sipReleaseInstance(t, sipClass_TYPE, state);
-+    }
-+
-+    *sipCppPtr = ql;
-+
-+    return sipGetState(sipTransferObj);
-+%End
-+};
-+%End
-+
-+// QList<double> is implemented as a Python list of doubles.
-+%MappedType QList<double>
-+{
-+%TypeHeaderCode
-+#include <qlist.h>
-+%End
-+
-+%ConvertFromTypeCode
-+    // Create the list.
-+    PyObject *l;
-+
-+    if ((l = PyList_New(sipCpp->size())) == NULL)
-+        return NULL;
-+
-+    // Set the list elements.
-+    for (int i = 0; i < sipCpp->size(); ++i)
-+    {
-+        PyObject *pobj;
-+
-+        if ((pobj = PyFloat_FromDouble(sipCpp->value(i))) == NULL)
-+        {
-+            Py_DECREF(l);
-+
-+            return NULL;
-+        }
-+
-+        PyList_SET_ITEM(l, i, pobj);
-+    }
-+
-+    return l;
-+%End
-+
-+%ConvertToTypeCode
-+    // Check the type if that is all that is required.
-+    if (sipIsErr == NULL)
-+        return (PySequence_Check(sipPy) && PySequence_Size(sipPy) >= 0);
-+
-+    QList<double> *ql = new QList<double>;
-+    SIP_SSIZE_T len = PySequence_Size(sipPy);
-+
-+    for (SIP_SSIZE_T i = 0; i < len; ++i)
-+        ql->append(PyFloat_AsDouble(PySequence_ITEM(sipPy, i)));
-+
-+    *sipCppPtr = ql;
-+
-+    return sipGetState(sipTransferObj);
-+%End
-+};
-+
-+%End
diff --git a/buildconfig/CMake/span_disable_testing.patch b/buildconfig/CMake/span_disable_testing.patch
index 739d81b6b54c0739c0c19196c17b35c8dd34b4e0..6d2b0b1a01e9618b12c357a088acd68afe0dc3f5 100644
--- a/buildconfig/CMake/span_disable_testing.patch
+++ b/buildconfig/CMake/span_disable_testing.patch
@@ -5,15 +5,15 @@ index 7ad07e2..0470696 100644
 @@ -2,7 +2,7 @@
  cmake_minimum_required(VERSION 3.8)
  project(span LANGUAGES CXX)
-
+ 
 -enable_testing()
 +# enable_testing()
-
+ 
  add_library(span INTERFACE)
  target_sources(span INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include/tcb/span.hpp)
 @@ -11,4 +11,4 @@ target_compile_features(span INTERFACE cxx_std_11)
-
+ 
  set(TCB_SPAN_TEST_CXX_STD 11 CACHE STRING "C++ standard version for testing")
-
+ 
 -add_subdirectory(test)
 +# add_subdirectory(test)
diff --git a/buildconfig/Jenkins/buildscript b/buildconfig/Jenkins/buildscript
index 2df15774b565f72d8e5a077ff7c82984c711a7a7..8a3a43adc7c6c43b38d464b241a66bda6f196eaa 100755
--- a/buildconfig/Jenkins/buildscript
+++ b/buildconfig/Jenkins/buildscript
@@ -391,14 +391,15 @@ fi
 # Prevent race conditions when creating the user config directory
 userconfig_dir=$HOME/.mantid
 rm -fr $userconfig_dir
-# Remove GUI qsettings files
+# Remove old application saved state & crash reports on mac.
+# If we don't do this then when a previous test has crashed macOS
+# will pop up a dialog box and wait for clicking okay. We are heavy
+# handed but builders tend not to be used for anything else.
 if [[ ${ON_MACOS} == true ]] ; then
-    rm -f $HOME/Library/Preferences/com.mantid.MantidPlot.plist
-    rm -f $HOME/Library/Preferences/org.mantidproject.MantidPlot.plist
-    rm -f "$HOME/Library/Saved Application State/org.mantidproject.MantidPlot.savedState/windows.plist"
-else
-    rm -f ~/.config/Mantid/MantidPlot.conf
+    rm -fr "$HOME/Library/Saved Application State/org.python.python"
+    rm -f $HOME/Library/Application\ Support/CrashReporter/*
 fi
+# Remove GUI qsettings files
 rm -f ~/.config/mantidproject/mantidworkbench.ini
 
 mkdir -p $userconfig_dir
diff --git a/buildconfig/pyport.patch b/buildconfig/pyport.patch
deleted file mode 100644
index 03fefffe34cbeac5422dcca1dd63eff2653f8ae6..0000000000000000000000000000000000000000
--- a/buildconfig/pyport.patch
+++ /dev/null
@@ -1,26 +0,0 @@
-diff --git a/pyport.h b/pyport.h
---- a/pyport.h
-+++ b/pyport.h
-@@ -657,6 +657,9 @@
- #endif
-
- #ifdef _PY_PORT_CTYPE_UTF8_ISSUE
-+
-+#ifndef __cplusplus
-+
- #include <ctype.h>
- #include <wctype.h>
- #undef isalnum
-@@ -673,7 +676,11 @@
- #define tolower(c) towlower(btowc(c))
- #undef toupper
- #define toupper(c) towupper(btowc(c))
--#endif
-+
-+#endif /* !__cplusplus */
-+
-+
-+#endif /* _PY_PORT_CTYPE_UTF8_ISSUE */
-
-
- /* Declarations for symbol visibility.
diff --git a/buildconfig/qglobal.patch b/buildconfig/qglobal.patch
deleted file mode 100644
index d3922418e721b37f90ebb84f1d455106b1e5181e..0000000000000000000000000000000000000000
--- a/buildconfig/qglobal.patch
+++ /dev/null
@@ -1,7 +0,0 @@
-319c319,322
-< #  if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_6)
----
-> #  if !defined(MAC_OS_X_VERSION_10_7)
-> #       define MAC_OS_X_VERSION_10_7 MAC_OS_X_VERSION_10_6 + 1
-> #  endif
-> #  if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_7)
diff --git a/docs/source/algorithms/ApplyDiffCal-v1.rst b/docs/source/algorithms/ApplyDiffCal-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..7e58cc152f540daf1f17fa92cdab2c74e2321f74
--- /dev/null
+++ b/docs/source/algorithms/ApplyDiffCal-v1.rst
@@ -0,0 +1,36 @@
+.. algorithm::
+
+.. summary::
+
+.. relatedalgorithms::
+
+.. properties::
+
+Description
+-----------
+
+Loads diffractometer constants (DIFA, DIFC, TZERO) from a calibration data source and
+stores them in the instrument parameter map of the `InstrumentWorkspace`. The constants
+are used in time of flight diffraction instruments to convert from time of flight to 
+d spacing and vice versa.
+
+This algorithm either reads the constants from the
+`CalibrationWorkspace`, reads them from `CalibrationFile` using :ref:`LoadDiffCal
+<algm-LoadDiffCal>`, or uses :ref:`ConvertDiffCal<algm-ConvertDiffCal>` to generate 
+them from the `OffsetsWorkspace`.
+
+This algorithm allows unit conversions between time of flight and d spacing using
+calibrated diffractometer constants to be performed using the
+:ref:`ConvertUnits <algm-ConvertUnits>` algorithm. :ref:`ConvertUnits <algm-ConvertUnits>`
+reads the constants from the instrument parameter map.
+
+When used together with :ref:`ConvertUnits <algm-ConvertUnits>` this algorithm provides a way of
+converting in both directions between time of flight and d spacing and it is an alternative to
+using :ref:`AlignDetectors <algm-AlignDetectors>`.
+
+The values of the diffractometer constants that are stored in the instrument parameter map
+can be viewed on the Show Detectors screen of a workspace.
+
+.. categories::
+
+.. sourcelink::
diff --git a/docs/source/algorithms/FindPeaksMD-v1.rst b/docs/source/algorithms/FindPeaksMD-v1.rst
index 2834f2473e5c9df77d6c1fff7f854033d75be3a9..b1a132ae9e850c60566e00977a718db95f9c0a75 100644
--- a/docs/source/algorithms/FindPeaksMD-v1.rst
+++ b/docs/source/algorithms/FindPeaksMD-v1.rst
@@ -33,9 +33,13 @@ The algorithm proceeds in this way:
 
 -  This is repeated until we find up to MaxPeaks peaks.
 
-Each peak created is placed in the output
-:ref:`PeaksWorkspace <PeaksWorkspace>`, which can be a new workspace or
-replace the old one.
+Each peak created is placed in the output :ref:`PeaksWorkspace
+<PeaksWorkspace>` or :ref:`LeanElasticPeaksWorkspace
+<LeanElasticPeaksWorkspace>` (depending on the `OutputType` option),
+which can be a new workspace or replace the old one. If `OutputType`
+is the default `Automatic` then the output type will be PeakWorkspace
+unless the input workspace doesn't contain an experiment info in which
+case it will default to LeanElasticPeaksWorkspace.
 
 This algorithm works on a :ref:`MDHistoWorkspace <MDHistoWorkspace>`
 resulting from the :ref:`algm-BinMD` algorithm also. It works in the
diff --git a/docs/source/algorithms/GenerateLogbook-v1.rst b/docs/source/algorithms/GenerateLogbook-v1.rst
new file mode 100644
index 0000000000000000000000000000000000000000..94998727a8ffc97d94ed4a5f05069aaa1f7d5237
--- /dev/null
+++ b/docs/source/algorithms/GenerateLogbook-v1.rst
@@ -0,0 +1,92 @@
+
+.. algorithm::
+
+.. summary::
+
+.. relatedalgorithms::
+
+.. properties::
+
+
+Description
+-----------
+
+This is the algorithm that allows to create a :ref:`TableWorkspace <Table Workspaces>` logbook containing metadata specific
+to the selected instrument and relevant technique, without the need to load NeXus data in Mantid.
+
+There are three levels of entries that can be included in a logbook: default, optional, and custom. The default entries
+are always included, the optional entries are included when header for that entry is given, through `OptionalHeaders`,
+and custom entries are included when the full path in the NeXus file is provided through `CustomEntries`.
+The NeXus paths for the default and optional entries are specified in the :ref:`Instrument Parameter File <InstrumentParameterFile>`
+relevant to the instrument that the data comes from. The header for the custom entry can be specified through `CustomHeaders`,
+or not at all. In that case, the name for the custom entry will be derived from its NeXus path.
+
+The files in the directory are expected to be uniquely raw data for the chosen instrument and facility, specified through
+`Facility` and `Instrument`, respectively. The range of files to be logged can be specified through `NumorRange` property,
+where both edges of the range are inclusive.
+
+Basic binary arithmetic operations: addition (the only one supported if the data in the entry is a string), subtraction,
+multiplication and division on entries data are supported. No support is offered for organizing the operations using parentheses
+or scaling by read data by constant numbers.
+
+The logbook can be stored as a CSV file and read outside of the Mantid using spreadsheet software, such as Microsoft Excel.
+
+Usage
+-----
+.. include:: ../usagedata-note.txt
+
+**Example - GenerateLogbook for ILL D7 rawdata**
+
+.. testcode:: ExGenerateLogbook_D7
+
+   data_dirs = config['datasearch.directories'].split(';')
+   unit_test_data_dir = [p for p in data_dirs if 'UnitTest' in p][0]
+   data_directory = os.path.abspath(os.path.join(unit_test_data_dir, 'ILL/D7'))
+
+   GenerateLogbook(Directory=data_directory,
+                   OutputWorkspace='d7_logbook', Facility='ILL', Instrument='D7',
+                   NumorRange=[396990,396991,396993], OptionalHeaders='TOF',
+                   CustomEntries='entry0/acquisition_mode')
+   print("Number of numors in the logbook: {}".format(len(mtd['d7_logbook'].column(0))))
+   print("Number of headers in the logbook: {}".format(len(mtd['d7_logbook'].row(0))))
+
+Output:
+
+.. testoutput:: ExGenerateLogbook_D7
+
+   Number of numors in the logbook: 3
+   Number of headers in the logbook: 8
+
+.. testcleanup:: ExGenerateLogbook_D7
+
+   mtd.clear()
+
+**Example - GenerateLogbook for ILL D7 rawdata with binary operations**
+
+.. testcode:: ExGenerateLogbook_D7_binary_operations
+
+   data_dirs = config['datasearch.directories'].split(';')
+   unit_test_data_dir = [p for p in data_dirs if 'UnitTest' in p][0]
+   data_directory = os.path.abspath(os.path.join(unit_test_data_dir, 'ILL/D7'))
+
+   GenerateLogbook(Directory=data_directory,
+                   OutputWorkspace='d7_logbook', Facility='ILL', Instrument='D7',
+                   NumorRange="396990:396993", CustomHeaders='polarisation',
+                   CustomEntries='/entry0/D7/POL/actual_state+/entry0/D7/POL/actual_stateB1B2')
+   print("Number of numors in the logbook: {}".format(len(mtd['d7_logbook'].column(0))))
+   print("Number of headers in the logbook: {}".format(len(mtd['d7_logbook'].row(0))))
+
+Output:
+
+.. testoutput:: ExGenerateLogbook_D7_binary_operations
+
+   Number of numors in the logbook: 3
+   Number of headers in the logbook: 7
+
+.. testcleanup:: ExGenerateLogbook_D7_binary_operations
+
+   mtd.clear()
+
+.. categories::
+
+.. sourcelink::
diff --git a/docs/source/algorithms/PreprocessDetectorsToMD-v1.rst b/docs/source/algorithms/PreprocessDetectorsToMD-v1.rst
index 7d000a25ae2ea632ba043e2b318db2b5f50fb701..2574019ba9629a08d21614b5f7fd265259b2e30e 100644
--- a/docs/source/algorithms/PreprocessDetectorsToMD-v1.rst
+++ b/docs/source/algorithms/PreprocessDetectorsToMD-v1.rst
@@ -82,7 +82,7 @@ Usage
 .. testoutput:: ExPreprocessDetectoresToMD
 
    The resulting table has the following columns:
-   ['DetDirections', 'L2', 'TwoTheta', 'Azimuthal', 'DetectorID', 'detIDMap', 'spec2detMap', 'detMask', 'eFixed']
+   ['DetDirections', 'L2', 'TwoTheta', 'Azimuthal', 'DetectorID', 'detIDMap', 'spec2detMap', 'detMask', 'eFixed', 'DIFA', 'DIFC', 'TZERO']
    The number of rows in the workspace is :  918
    The polar angle for detector N 10 is 0.314159 rad
    The table workspace logs (properties) are currently not available from python
diff --git a/docs/source/concepts/LeanElasticPeaksWorkspace.rst b/docs/source/concepts/LeanElasticPeaksWorkspace.rst
index 70a8894e138236d949e92b8152354eb5f3948e86..ca6861724acb369a7736fd8020ed521621d8d3eb 100644
--- a/docs/source/concepts/LeanElasticPeaksWorkspace.rst
+++ b/docs/source/concepts/LeanElasticPeaksWorkspace.rst
@@ -10,6 +10,7 @@ of single crystal LeanElasticPeak objects. It is the equivalent to the
 Creating a LeanElasticPeaksWorkspace
 ------------------------------------
 
+* :ref:`FindPeaksMD <algm-FindPeaksMD>` will find peaks in reciprocal space in a :ref:`MDWorkspace <MDWorkspace>`.
 * :ref:`CreatePeaksWorkspace <algm-CreatePeaksWorkspace>` will create an empty LeanElasticPeaksWorkspace that you can then edit.
 
 Viewing a LeanElasticPeaksWorkspace
diff --git a/docs/source/concepts/UnitFactory.rst b/docs/source/concepts/UnitFactory.rst
index 789f1076b29f75a23109a8b3ec54e879f859ba84..01e75231fe9143d269a1048778b6bb84d27c88c5 100644
--- a/docs/source/concepts/UnitFactory.rst
+++ b/docs/source/concepts/UnitFactory.rst
@@ -33,7 +33,7 @@ The following units are available in the default Mantid distribution. These unit
 +-------------------------------------------+---------------------------------+-----------------------------+------------------------------------------------------------------------------------------------------------------+
 | Momentum (k)                              | Momentum                        | :math:`\mathrm{\AA}^{-1}`   | :math:`k = \frac{2 \pi }{\lambda}=\frac{2 \pi \times m_N \times L_{tot}}{h \times \mathrm{tof}}`                 |
 +-------------------------------------------+---------------------------------+-----------------------------+------------------------------------------------------------------------------------------------------------------+
-| d-spacing                                 | dSpacing                        | :math:`\mathrm{\AA}`        | :math:`d = \frac{n \, \lambda}{2 \, sin \, \theta}`                                                              |
+| d-spacing                                 | dSpacing                        | :math:`\mathrm{\AA}`        | :math:`TOF = DIFA \, d^2 + DIFC d + TZERO` (see below)                                                           |
 +-------------------------------------------+---------------------------------+-----------------------------+------------------------------------------------------------------------------------------------------------------+
 | Momentum transfer (Q)                     | MomentumTransfer                | :math:`\mathrm{\AA}^{-1}`   | :math:`Q = 2 \, k \, sin \, \theta = \frac{4 \pi sin \theta}{\lambda}`                                           |
 +-------------------------------------------+---------------------------------+-----------------------------+------------------------------------------------------------------------------------------------------------------+
@@ -68,6 +68,15 @@ energy respectively. Units conversion into elastic momentum transfer
 (MomentumTransfer) will throw in elastic mode (emode=0) on inelastic
 workspace (when energy transfer is specified along x-axis)
 
+**Note on d-spacing**: The coefficients DIFA, DIFC and TZERO may be obtained 
+via calibration of a TOF diffraction instrument. In the absence of a calibration,
+DIFA=TZERO=0 and the default value of DIFC is:
+
+:math:`DIFC = 10^{-4} \frac{m_N}{h} (L_1 + L_2) 2 \sin(\theta)`
+
+where the scaling factor adjusts for the fact that DIFC is required in units 
+of :math:`\mu s` per :math:`\mathrm{\AA}`.
+
 **d-spacingPerpendicular** is a unit invented in `J. Appl. Cryst. (2015) 48, pp. 1627--1636 <https://doi.org/10.1107/S1600576715016520>`_ for 2D Rietveld refinement
 of angular and wavelength-dispersive neutron time-of-flight powder diffraction data. Together with the d-Spacing :math:`d`,
 d-SpacingPerpendicular :math:`d_{\perp}` forms a new orthogonal coordinate system.
diff --git a/docs/source/concepts/calibration/PowderDiffractionCalibration.rst b/docs/source/concepts/calibration/PowderDiffractionCalibration.rst
index 623a2e7fc7d0d4a4b1b38aefd79fe5853b35d777..162adbd741889e2db150f59685620c740bf0a9d5 100644
--- a/docs/source/concepts/calibration/PowderDiffractionCalibration.rst
+++ b/docs/source/concepts/calibration/PowderDiffractionCalibration.rst
@@ -356,4 +356,103 @@ which accepts a tuple of the starting detector ID and ending detector ID to plot
 
 To adjust the horizontal bars above and below the mean, a percent can be passed to the ``threshold`` option.
 
+Pearson Correlation Coefficient
+###############################
+
+It can be useful to compare the linearity of the relationship between time of flight and d-spacing for each peak involved
+in calibration. In theory, the relationship between (TOF, d-spacing) will always be perfectly linear, but in practice,
+that is not always the case. This diagnostic plot primarily serves as a tool to ensure that the calibration makes sense,
+i.e., that a single DIFC parameter is enough to do the transformation. In the ideal case, all Pearson correlation
+coefficients will be close to 1. For more on Pearson correlation coefficients please see
+`this wikipedia article <https://en.wikipedia.org/wiki/Pearson_correlation_coefficient>`_. Below is an example plot for the Pearson correlation
+coefficient of (TOF, d-spacing).
+
+.. figure:: /images/VULCAN_pearsoncorr.png
+
+The following script can be used to generate the above plot.
+
+.. code::
+
+    # import mantid algorithms, numpy and matplotlib
+    from mantid.simpleapi import *
+    import matplotlib.pyplot as plt
+    import numpy as npfrom Calibration.tofpd import diagnosticsFILENAME = 'VULCAN_192226.nxs.h5'  # 88 sec
+
+    FILENAME = 'VULCAN_192227.nxs.h5'  # 2.8 hour
+    CALFILE = 'VULCAN_Calibration_CC_4runs_hybrid.h5'peakpositions = np.asarray(
+      (0.3117, 0.3257, 0.3499, 0.3916, 0.4205, 0.4645, 0.4768, 0.4996, 0.515, 0.5441, 0.5642, 0.6307, 0.6867,
+       0.7283, 0.8186, 0.892, 1.0758, 1.2615, 2.06))
+
+    peakpositions = peakpositions[peakpositions > 0.4]
+    peakpositions = peakpositions[peakpositions < 1.5]
+    peakpositions.sort()LoadEventAndCompress(Filename=FILENAME, OutputWorkspace='ws', FilterBadPulses=0)
+
+    LoadInstrument(Workspace='ws', Filename="mantid/instrument/VULCAN_Definition.xml", RewriteSpectraMap='True')
+    Rebin(InputWorkspace='ws', OutputWorkspace='ws', Params=(5000, -.002, 70000))
+    PDCalibration(InputWorkspace='ws', TofBinning=(5000,-.002,70000),
+               PeakPositions=peakpositions,
+               MinimumPeakHeight=5,
+               OutputCalibrationTable='calib',
+               DiagnosticWorkspaces='diag')
+    center_tof = diagnostics.collect_fit_result('diag_fitparam', 'center_tof', peakpositions, donor='ws', infotype='centre')
+    fig, ax = diagnostics.plot_corr('center_tof')
+
+Peak Information
+################
+
+Plotting the fitted peak parameters for different instrument banks can also provide useful information for
+calibration diagnostics. The fitted peak parameters from :ref:`FitPeaks <algm-FitPeaks>` (center, width,
+height, and intensity) are plotted for each bank at different peak positions. This can be used to help calibrate
+each group rather than individual detector pixels.
+
+.. figure:: /images/VULCAN_peakinfo_diagnostic.png
+  :width: 400px
+
+The above figure can be generated using the following script:
+
+.. code::
+
+    import numpy as np
+    from mantid.simpleapi import (AlignAndFocusPowder, ConvertUnits, FitPeaks, LoadEventAndCompress,
+                                  LoadDiffCal, LoadInstrument)
+    from Calibration.tofpd import diagnostics
+
+    FILENAME = 'VULCAN_192227.nxs.h5'  # 2.8 hour
+    CALFILE = 'VULCAN_Calibration_CC_4runs_hybrid.h5'
+
+    peakpositions = np.asarray(
+        (0.3117, 0.3257, 0.3499, 0.3916, 0.4205, 0.4645, 0.4768, 0.4996, 0.515, 0.5441, 0.5642, 0.6307, 0.6867,
+         0.7283, 0.8186, 0.892, 1.0758, 1.2615, 2.06))
+    peakpositions = peakpositions[peakpositions > 0.4]
+    peakpositions = peakpositions[peakpositions < 1.5]
+    peakpositions.sort()
+    peakwindows = diagnostics.get_peakwindows(peakpositions)
+
+    LoadEventAndCompress(Filename=FILENAME, OutputWorkspace='ws', FilterBadPulses=0)
+    LoadInstrument(Workspace='ws', InstrumentName="VULCAN", RewriteSpectraMap='True')
+
+    LoadDiffCal(Filename=CALFILE, InputWorkspace='ws', WorkspaceName='VULCAN')
+    AlignAndFocusPowder(InputWorkspace='ws',
+                        OutputWorkspace='focus',
+                        GroupingWorkspace="VULCAN_group",
+                        CalibrationWorkspace="VULCAN_cal",
+                        MaskWorkspace="VULCAN_mask",
+                        Dspacing=True,
+                        Params="0.3,3e-4,1.5")
+
+    ConvertUnits(InputWorkspace='focus', OutputWorkspace='focus', Target='dSpacing', EMode='Elastic')
+    FitPeaks(InputWorkspace='focus',
+            OutputWorkspace='output',
+            PeakFunction='Gaussian',
+            RawPeakParameters=False,
+            HighBackground=False,  # observe background
+            ConstrainPeakPositions=False,
+            MinimumPeakHeight=3,
+            PeakCenters=peakpositions,
+            FitWindowBoundaryList=peakwindows,
+            FittedPeaksWorkspace='fitted',
+            OutputPeakParametersWorkspace='parameters')
+
+    fig, ax = diagnostics.plot_peak_info('parameters', peakpositions)
+
 .. categories:: Calibration
diff --git a/docs/source/images/VULCAN_peakinfo_diagnostic.png b/docs/source/images/VULCAN_peakinfo_diagnostic.png
new file mode 100644
index 0000000000000000000000000000000000000000..be29cb1f8bca9917ef5c2c229390510cafff83fd
Binary files /dev/null and b/docs/source/images/VULCAN_peakinfo_diagnostic.png differ
diff --git a/docs/source/images/VULCAN_pearsoncorr.png b/docs/source/images/VULCAN_pearsoncorr.png
new file mode 100644
index 0000000000000000000000000000000000000000..ae4044ec0da558ebf26a0e2d27acecab1a44872a
Binary files /dev/null and b/docs/source/images/VULCAN_pearsoncorr.png differ
diff --git a/docs/source/interfaces/ILL/DrILL.rst b/docs/source/interfaces/ILL/DrILL.rst
index 94c3c4d15a9640048a065fe6ff8475179522fc2e..058ba44932759fa03d600cd1bfbc61d2851c7978 100644
--- a/docs/source/interfaces/ILL/DrILL.rst
+++ b/docs/source/interfaces/ILL/DrILL.rst
@@ -209,9 +209,11 @@ For each acquisition mode, a list of adapated algorithms will be displayed in
 that dialog. Some of them are activated by default but the user is free to
 select the ones he wants. All checked algorithms will be applied on all output
 workspaces of all processed rows. The exported files are saved in the Mantid
-default save directory. If the algorithm is not adapted to the data, it will be
-skipped. For further information, the documentation of each algorithm can be
-obtained using the help button associated to it in the dialog.
+default save directory with an extension that is defined by the algorithm (if no
+default save directory is provided, there will be no export). If the algorithm
+is not adapted to the data, it will be skipped. For further information, the
+documentation of each algorithm can be obtained using the help button associated
+to it in the dialog.
 
 
 Import and export as Rundex file
diff --git a/docs/source/release/v6.1.0/diffraction.rst b/docs/source/release/v6.1.0/diffraction.rst
index a35d38becde30524e42ac18e08d45ee0e35e3a26..7f28755960b60e7ead737968c76ec199c419e1ec 100644
--- a/docs/source/release/v6.1.0/diffraction.rst
+++ b/docs/source/release/v6.1.0/diffraction.rst
@@ -19,12 +19,17 @@ New features
 - New diagnostic plotting tool `Calibration.tofpd..diagnostics.plot2d` which adds markers for expected peak positions
 - New diagnostic plotting tool `Calibration.tofpd.diagnostics.difc_plot2d` which plots the change in DIFC between two instrument calibrations.
 - New diagnostic plotting tool `Calibration.tofpd.diagnostics.plot_peakd` which plots the d-spacing relative strain of peak positions.
+- New diagnostic plotting tool `Calibration.tofpd.diagnostics.plot_corr` which plots the Pearson correlation coefficient for time-of-flight and d-spacing for each detector.
+- New diagnostic plotting tool `Calibration.tofpd.diagnostics.plot_peak_info` which plots fitted peak parameters for instrument banks.
 
 Improvements
 ############
 
 - :ref:`PDCalibration <algm-PDCalibration>` now intitialises A,B and S of BackToBackExponential if correpsonding coeficients are in the instrument parameter.xml file.
 - Support fitting diffractometer constants with chi-squared cost function in <algm-PDCalibration>.
+- Differential evolution minimizer added to :ref:`AlignComponents <algm-AlignComponents>`.
+- Differential evolution minimizer added to :ref:`CorelliPowderCalibrationCreate <algm-CorelliPowderCalibrationCreate>`.
+- Added option to fix banks' vertical coordinate :ref:`CorelliPowderCalibrationCreate <algm-CorelliPowderCalibrationCreate>`.
 
 Bugfixes
 ########
@@ -47,6 +52,8 @@ Bugfixes
 - Allow a different number of spectra for absorption correction division of PEARL data. This allows ``create_vanadium`` to work for a non-standard dataset.
 - Saved filenames for summed empty workspaces now include spline properties to avoid long_mode confusion when focussing.
 
+- The :ref:`ConvertUnits <algm-ConvertUnits>` algorithm has been extended to use a quadratic relationship between d spacing and TOF when doing conversions between these units. The diffractometer constants DIFA, DIFC and TZERO that determine the form of the quadratic can be loaded into a workspace using a new :ref:`ApplyDiffCal <algm-ApplyDiffCal>` algorithm. This functionality was previously only available in :ref:`AlignDetectors <algm-AlignDetectors>` which only performed the conversion in the direction TOF to d spacing. This change will ensure that the conversion of focussed datasets from d spacing back to TOF at the end of the ISIS powder diffraction data reduction is performed correctly.
+
 Engineering Diffraction
 -----------------------
 
@@ -95,7 +102,8 @@ this peak is a Q-sample vector. There are a number of modifications
 made to facilitate this.
 
 - New LeanElasticPeak and LeanElasticPeakWorkspace has been created :ref:`LeanElasticPeaksWorkspace <LeanElasticPeaksWorkspace>`
-- :ref:`CreatePeaksWorkspace <algm-CreatePeaksWorkspace>` has been modified to optionally create a  :ref:`LeanElasticPeaksWorkspace <LeanElasticPeaksWorkspace>`.
+- :ref:`CreatePeaksWorkspace <algm-CreatePeaksWorkspace>` has been modified to optionally create a :ref:`LeanElasticPeaksWorkspace <LeanElasticPeaksWorkspace>`.
+- :ref:`FindPeaksMD <algm-FindPeaksMD>` has been modified to optionally create a :ref:`LeanElasticPeaksWorkspace <LeanElasticPeaksWorkspace>`.
 - These following other algorithms have either been made to work or confirmed to already work with the LeanElasticPeak:
 
    - :ref:`algm-AddPeakHKL`
@@ -109,6 +117,8 @@ made to facilitate this.
    - :ref:`algm-FindUBUsingMinMaxD`
    - :ref:`algm-IndexPeaks`
    - :ref:`algm-IntegratePeaksMD`
+   - :ref:`algm-LoadNexusProcessed`
+   - :ref:`algm-SaveNexusProcessed`
    - :ref:`algm-SelectCellOfType`
    - :ref:`algm-SelectCellWithForm`
    - :ref:`algm-ShowPossibleCells`
diff --git a/docs/source/release/v6.1.0/framework.rst b/docs/source/release/v6.1.0/framework.rst
index da18ca50e88ce120c51db195caf92692d149a60a..de97ab3797ebc77a0da905f6eca9bc6722e18ca9 100644
--- a/docs/source/release/v6.1.0/framework.rst
+++ b/docs/source/release/v6.1.0/framework.rst
@@ -17,9 +17,13 @@ Algorithms
 :ref:`LoadNexusLogs <algm-LoadNexusLogs>` has additional parameters to allow or block specific logs from being loaded.
 :ref:`LoadEventNexus <algm-LoadEventNexus>` now utilizes the log filter provided by `LoadNexusLogs <algm-LoadNexusLogs>`.
 
+- New algorithm :ref:`GenerateLogbook <algm-GenerateLogbook>`, that allows creating TableWorkspace
+  logbooks based on provided directory path with rawdata.
 - :ref:`CompareWorkspaces <algm-CompareWorkspaces>` compares the positions of both source and sample (if extant) when property `checkInstrument` is set.
 - :ref:`SetGoniometer <algm-SetGoniometer>` can now set multiple goniometers from log values instead of just the time-avereged value.
 - Added the ability to specify the spectrum number in :ref:`FindPeaksAutomatic <algm-FindPeaksAutomatic>`.
+- :ref:`LoadLog <algm-LoadLog>` will now detect old unsupported log files and set an appropriate explanatory string in the exception.
+
 
 Data Objects
 ------------
@@ -59,5 +63,6 @@ Bugfixes
 
 - Fix problem with dictionary parameters on :ref:`SetSample <algm-SetSample>` algorithm when running from the algorithm dialog
 - Fix segmentation fault when running :ref:`MonteCarloAbsorption <algm-MonteCarloAbsorption>` algorithm on Ubuntu without a material defined on one of the sample\environment shapes
+- Fix calculation of region where scattering points are sampled in :ref:`MonteCarloAbsorption <algm-MonteCarloAbsorption>` when a shape is defined for the environment but not the sample
 
 :ref:`Release 6.1.0 <v6.1.0>`
diff --git a/docs/source/release/v6.1.0/mantidworkbench.rst b/docs/source/release/v6.1.0/mantidworkbench.rst
index 32ccddc3677f5b2b2ce385f8d774e3e1d5d4f614..9501038b96e3ea81dbc6c8bb90c05543b99fa064 100644
--- a/docs/source/release/v6.1.0/mantidworkbench.rst
+++ b/docs/source/release/v6.1.0/mantidworkbench.rst
@@ -7,14 +7,15 @@ Mantid Workbench Changes
 
 New and Improved
 ----------------
-Added Floating/On Top setting for all the windows that are opened by workbench (plots, interfaces, etc.)
 
+- Added Floating/On Top setting for all the windows that are opened by workbench (plots, interfaces, etc.)
 - New plot interactions: Double click a legend to hide it, double click a curve to open it in the plot config dialog.
 - It is now possible to overplot bin data from the matrix workspace view.
 - Improved the performance of the table workspace display for large datasets
 - Added a sample material dialog that is accessed via the context menu in the workspace widget.
 - When a workspace is renamed it now updates the relevant plot labels with the new workspace name.
 - Add a checkbox to freeze the rotation in the instrument viewer in Full 3D mode.
+- Calling python's `input` now raises an input dialog in the script editor and the iPython shell.
 - A new empty facility with empty instrument is the default facility now, and
   user has to select their choice of facility (including ISIS) and instrument for the first time.
 - Added an algorithm ProfileChiSquared1D to profile chi squared after a fit. This can be used
diff --git a/instrument/D11B_Parameters.xml b/instrument/D11B_Parameters.xml
index f4e739481b2cb3fc5d5bb3b342482522706452b5..88772630efd01f6796f4b8e85cd8557044f15676 100644
--- a/instrument/D11B_Parameters.xml
+++ b/instrument/D11B_Parameters.xml
@@ -23,6 +23,78 @@
       <parameter name="y-pixel-size" type="number">
         <value val="8"/>
       </parameter>
+      <!-- These parameters are used to define headers and entries for GenerateLogbook algorithm -->
+      <parameter name="logbook_default_parameters" type="string">
+	<value val="SampleDescription:/entry0/sample_description,
+		    TotalTime:/entry0/duration,
+		    RateMonitor1:/entry0/monitor1/monrate,
+		    TotalCountsDetMain:/entry0/D11/Detector 1/detsum,
+		    RateCountsDetMain:/entry0/D11/Detector 1/detrate,
+		    StartTime:/entry0/start_time,
+		    Wavelength:/entry0/D11/selector/wavelength,
+		    Attenuator:/entry0/D11/attenuator/attenuation_value,
+		    Collimation:/entry0/D11/collimation/actual_position,
+		    SD:/entry0/D11/Detector 1/det_actual,
+		    BeamStopY:/entry0/D11/beamstop/by_actual" />
+      </parameter>
+      <parameter name="logbook_optional_parameters" type="string">
+	<value val="AcquisitionMode:/entry0/acquisition_mode,
+		    TotalCountsDetAll:/entry0/D11/Detector 1/detsum+/entry0/D11/Detector 2/detsum+/entry0/D11/Detector 3/detsum,
+		    Beamstop:/entry0/D11/beamstop/actual_beamstop_number,
+		    VelocitySelectorSpeed:/entry0/D11/selector/rotation_speed,
+		    VelocitySelectorTilt:/entry0/D11/selector/selrot_actual,
+		    AttenuationFactor:/entry0/D11/attenuator/attenuation_coefficient,
+		    Diaphragm1:/entry0/D11/collimation/diaphragm1_position,
+		    Diaphragm2:/entry0/D11/collimation/diaphragm2_position,
+		    Diaphragm3:/entry0/D11/collimation/diaphragm3_position,
+		    Diaphragm4:/entry0/D11/collimation/diaphragm4_position,
+		    Diaphragm5:/entry0/D11/collimation/diaphragm5_position,
+		    Diaphragm6:/entry0/D11/collimation/diaphragm6_position,
+		    Guide1:/entry0/D11/collimation/col1_actual_state,
+		    Guide2:/entry0/D11/collimation/col2_actual_state,
+		    Guide3:/entry0/D11/collimation/col3_actual_state,
+		    Guide4:/entry0/D11/collimation/col4_actual_state,
+		    Guide5:/entry0/D11/collimation/col5_actual_state,
+		    Guide6:/entry0/D11/collimation/col6_actual_state,
+		    Guide7:/entry0/D11/collimation/col7_actual_state,
+		    Guide8:/entry0/D11/collimation/col8_actual_state,
+		    Guide9:/entry0/D11/collimation/col9_actual_state,
+		    Guide10:/entry0/D11/collimation/col10_actual_state,
+		    Guide11:/entry0/D11/collimation/col11_actual_state,
+		    Guide12:/entry0/D11/collimation/col12_actual_state,
+		    Guide13:/entry0/D11/collimation/col13_actual_state,
+		    TotalCountsMonitor1:/entry0/monitor1/monsum,
+		    ReactorPower:/entry0/reactor_power,
+		    BeamstopX:/entry0/D11/beamstop/bx_actual,
+		    BeamstopX2:/entry0/D11/beamstop/bx2_actual,
+		    TotalCountsDetLeft:/entry0/D11/Detector 2/detsum,
+		    TotalCountsDetRight:/entry0/D11/Detector 3/detsum,
+		    RateCountsDetLeft:/entry0/D11/Detector 2/detrate,
+		    RateCountsDetRight:/entry0/D11/Detector 3/detrate,
+		    san:/entry0/sample/san_actual,
+		    omega:/entry0/sample/omega_actual,
+		    phi:/entry0/sample/phi_actual,
+		    sdi:/entry0/sample/sdi_actual,
+		    sht:/entry0/sample/sht_actual,
+		    str:/entry0/sample/str_actual,
+		    trs:/entry0/sample/trs_actual,
+		    SampleTemperature:/entry0/sample/temperature,
+		    AirTemperature:/entry0/sample/air_temperature,
+		    RackTemperature:/entry0/sample/rack_temperature,
+		    Bath1Temperature:/entry0/sample/bath1_regulation_temperature,
+		    Bath2Temperature:/entry0/sample/bath2_regulation_temperature,
+		    BathSelector:/entry0/sample/Actual bath,
+		    Thermocouple1:/entry0/sample/thermo_temperature1,
+		    Thermocouple2:/entry0/sample/thermo_temperature2,
+		    SampleChanger:/entry0/sample/sample_changer_nickname,
+		    Position:/entry0/sample/sample_changer_slot_value,
+		    SampleThickness:/entry0/sample/thickness,
+		    MeasurementType:/entry0/D11/MeasurementType/typeOfMeasure,
+		    SampleType:/entry0/D11/MeasurementType/typeOfSample,
+		    SampleSubType:/entry0/D11/MeasurementType/subType,
+		    SampleApertureWidth:/entry0/D11/Beam/sample_ap_x_or_diam,
+		    SampleApertureHeight:/entry0/D11/Beam/sample_ap_y" />
+      </parameter>
     </component-link>
     <!-- These parameters are used in ParallaxCorrection algorithm -->
     <component-link name="detector_center">
diff --git a/instrument/D11_Parameters.xml b/instrument/D11_Parameters.xml
index 07bbbca32c11e481647b3f8eaebb723a59690169..5ad42b67740e1fadcca3b200f47015ac7fc55f9c 100644
--- a/instrument/D11_Parameters.xml
+++ b/instrument/D11_Parameters.xml
@@ -23,6 +23,75 @@
       <parameter name="y-pixel-size" type="number">
         <value val="3.75"/>
       </parameter>
+      <!-- These parameters are used to define headers and entries for GenerateLogbook algorithm -->
+
+      <parameter name="logbook_default_parameters" type="string">
+	<value val="SampleDescription:/entry0/sample_description,
+		    TotalTime:/entry0/duration,
+		    RateMonitor1:/entry0/monitor1/monrate,
+		    TotalCountsDetMain:/entry0/D11/detector/detsum,
+		    RateCountsDetMain:/entry0/D11/detector/detrate,
+		    StartTime:/entry0/start_time,
+		    Wavelength:/entry0/D11/selector/wavelength,
+		    Attenuator:/entry0/D11/attenuator/attenuation_value,
+		    Collimation:/entry0/D11/collimation/actual_position,
+		    SD:/entry0/D11/detector/det_actual,
+		    BeamStopY:/entry0/D11/beamstop/by_actual" />
+      </parameter>
+      <parameter name="logbook_optional_parameters" type="string">
+	<value val="AcquisitionMode:/entry0/acquisition_mode,
+		    TotalCountsDetAll:/entry0/D11/detector/detsum+/entry0/D11/detectorLeft/detsum+/entry0/D11/detectorRight/detsum,
+		    Beamstop:/entry0/D11/beamstop/actual_beamstop_number,
+		    VelocitySelectorSpeed:/entry0/D11/selector/rotation_speed,
+		    VelocitySelectorTilt:/entry0/D11/selector/selrot_actual,
+		    AttenuationFactor:/entry0/D11/attenuator/attenuation_coefficient,
+		    Diaphragm1:/entry0/D11/collimation/diaphragm1_position,
+		    Diaphragm2:/entry0/D11/collimation/diaphragm2_position,
+		    Diaphragm3:/entry0/D11/collimation/diaphragm3_position,
+		    Diaphragm4:/entry0/D11/collimation/diaphragm4_position,
+		    Diaphragm5:/entry0/D11/collimation/diaphragm5_position,
+		    Diaphragm6:/entry0/D11/collimation/diaphragm6_position,
+		    Guide1:/entry0/D11/collimation/col1_actual_state,
+		    Guide2:/entry0/D11/collimation/col2_actual_state,
+		    Guide3:/entry0/D11/collimation/col3_actual_state,
+		    Guide4:/entry0/D11/collimation/col4_actual_state,
+		    Guide5:/entry0/D11/collimation/col5_actual_state,
+		    Guide6:/entry0/D11/collimation/col6_actual_state,
+		    Guide7:/entry0/D11/collimation/col7_actual_state,
+		    Guide8:/entry0/D11/collimation/col8_actual_state,
+		    Guide9:/entry0/D11/collimation/col9_actual_state,
+		    Guide10:/entry0/D11/collimation/col10_actual_state,
+		    Guide11:/entry0/D11/collimation/col11_actual_state,
+		    Guide12:/entry0/D11/collimation/col12_actual_state,
+		    Guide13:/entry0/D11/collimation/col13_actual_state,
+		    TotalCountsMonitor1:/entry0/monitor1/monsum,
+		    ReactorPower:/entry0/reactor_power,
+		    BeamstopX:/entry0/D11/beamstop/bx_actual,
+		    TotalCountsDetLeft:/entry0/D11/detectorLeft/detsum,
+		    TotalCountsDetRight:/entry0/D11/detectorRight/detsum,
+		    RateCountsDetLeft:/entry0/D11/detectorLeft/detrate,
+		    RateCountsDetRight:/entry0/D11/detectorRight/detrate,
+		    san:/entry0/sample/san_actual,
+		    omega:/entry0/sample/omega_actual,
+		    phi:/entry0/sample/phi_actual,
+		    sdi:/entry0/sample/sdi_actual,
+		    sht:/entry0/sample/sht_actual,
+		    str:/entry0/sample/str_actual,
+		    trs:/entry0/sample/trs_actual,
+		    SampleTemperature:/entry0/sample/temperature,
+		    AirTemperature:/entry0/sample/air_temperature,
+		    RackTemperature:/entry0/sample/rack_temperature,
+		    Bath1Temperature:/entry0/sample/bath1_regulation_temperature,
+		    Bath2Temperature:/entry0/sample/bath2_regulation_temperature,
+		    BathSelector:/entry0/sample/Actual bath,
+		    Thermocouple1:/entry0/sample/thermo_temperature1,
+		    Thermocouple2:/entry0/sample/thermo_temperature2,
+		    SampleChanger:/entry0/sample/sample_changer_nickname,
+		    Position:/entry0/sample/sample_changer_slot_value,
+		    SampleThickness:/entry0/sample/thickness,
+		    SampleApertureWidth:/entry0/D11/Beam/sample_ap_x_or_diam,
+		    SampleApertureHeight:/entry0/D11/Beam/sample_ap_y" />
+      </parameter>
     </component-link>
 
 </parameter-file>
diff --git a/instrument/D16_Parameters.xml b/instrument/D16_Parameters.xml
index 890887abd56ae6e96f31266b014e9f9ffd1c4798..8e555f85f44f64422f49acfedc7c43cbb41ec76d 100644
--- a/instrument/D16_Parameters.xml
+++ b/instrument/D16_Parameters.xml
@@ -26,6 +26,50 @@
       <parameter name="detector-height" type="number">
         <value val="320"/>
       </parameter>
+
+      <!-- These parameters are used to define headers and entries for GenerateLogbook algorithm -->
+      <parameter name="logbook_default_parameters" type="string">
+	<value val="SampleDescription:/entry0/experiment_identifier,
+		    TotalTime:/entry0/duration,
+		    RateMonitor1:/entry0/monitor1/monrate,
+		    TotalCountsDet:/entry0/instrument/Detector1/detsum,
+		    RateCountsDet:/entry0/instrument/Detector1/detrate,
+		    StartTime:/entry0/start_time,
+		    Wavelength:/entry0/instrument/Beam/wavelength,
+		    Attenuator:/entry0/instrument/attenuator/attenuation_value,
+		    Det:/entry0/instrument/Det/value" />
+      </parameter>
+      <parameter name="logbook_optional_parameters" type="string">
+	<value val="AcquisitionMode:/entry0/acquisition_mode,
+		    Mode:/entry0/mode,
+		    BeamStopYOffset:/entry0/instrument/BStopY/offset_value,
+		    BeamStopYValue:/entry0/instrument/BStopY/value,
+		    BeamStopXOffset:/entry0/instrument/BStopX/offset_value,
+		    BeamStopXValue:/entry0/instrument/BStopX/value,
+		    BeamstopX:/entry0/instrument/beamstop/bx_actual,
+		    BeamstopY:/entry0/instrument/beamstop/by_actual,
+		    TotalCountsMonitor1:/entry0/monitor1/monsum,
+		    TotalCountsMonitor2:/entry0/monitor2/monsum,
+		    RateMonitor2:/entry0/monitor2/monrate,
+		    ReactorPower:/entry0/reactor_power,
+		    Omega:/entry0/instrument/Omega/value,
+		    OmegaOffset:/entry0/instrument/Omega/offset_value,
+		    Phi:/entry0/instrument/Phi/value,
+		    PhiOffset:/entry0/instrument/Phi/offset_value,
+		    Khi:/entry0/instrument/Khi/value,
+		    KhiOffset:/entry0/instrument/Khi/offset_value,
+		    Str:/entry0/instrument/Str/value,
+		    StrOffset:/entry0/instrument/Str/offset_value,
+		    Slitr:/entry0/instrument/Slitr/value,
+		    SlitrOffset:/entry0/instrument/Slitr/offset_value,
+		    SampleTemperature:/entry0/sample/temperature,
+		    SetpointTemperature:/entry0/sample/setpoint_temperature,
+		    RegulationTemperature:/entry0/sample/regulation_temperature,
+		    Bath1Temperature:/entry0/sample/bath1_regulation_temperature,
+		    Bath2Temperature:/entry0/sample/bath2_regulation_temperature,
+		    SampleChangerPos:/entry0/instrument/VirtualSampleChanger/sample_changer_slot_value" />
+      </parameter>
+
     </component-link>
 
 </parameter-file>
diff --git a/instrument/D22B_Parameters.xml b/instrument/D22B_Parameters.xml
index cf7714129fc211b5c0719d4ce8534f05ba9eb00b..633a4ead585096f0c07c48d166b0f6c04d5adc50 100644
--- a/instrument/D22B_Parameters.xml
+++ b/instrument/D22B_Parameters.xml
@@ -27,6 +27,72 @@
       <parameter name="y-pixel-size" type="number">
         <value val="4"/>
       </parameter>
+
+      <!-- These parameters are used to define headers and entries for GenerateLogbook algorithm -->
+      <parameter name="logbook_default_parameters" type="string">
+	<value val="SampleDescription:/entry0/sample_description,
+		    TotalTime:/entry0/duration,
+		    RateMonitor1:/entry0/monitor1/monrate,
+		    TotalCountsDet1:/entry0/D22/Detector 1/det1_sum,
+		    RateCountsDet1:/entry0/D22/Detector 1/det1_rate,
+		    TotalCountsDet2:/entry0/D22/Detector 2/det2_sum,
+		    RateCountsDet2:/entry0/D22/Detector 2/det2_rate,
+		    StartTime:/entry0/start_time,
+		    Wavelength:/entry0/D22/selector/wavelength,
+		    Attenuator:/entry0/D22/attenuator/attenuation_value,
+		    Collimation:/entry0/D22/collimation/actual_position,
+		    SD1:/entry0/D22/Detector 1/det1_actual,
+		    SD2:/entry0/D22/Detector 2/det2_actual,
+		    BeamStopY:/entry0/D22/beamstop/by1_actual" />
+      </parameter>
+      <parameter name="logbook_optional_parameters" type="string">
+	<value val="AcquisitionMode:/entry0/acquisition_mode,
+		    TotalCountsDetAll:/entry0/D22/Detector 1/det1_sum+/entry0/D22/Detector 2/det2_sum,
+		    Beamstop:/entry0/D22/beamstop/actual_beamstop_number,
+		    VelocitySelectorSpeed:/entry0/D22/selector/rotation_speed,
+		    VelocitySelectorTilt:/entry0/D22/selector/selrot_actual,
+		    Diaphragm1:/entry0/D22/collimation/Dia1_actual_position,
+		    Diaphragm2:/entry0/D22/collimation/Dia2_actual_position,
+		    Diaphragm3:/entry0/D22/collimation/Dia3_actual_position,
+		    Diaphragm4:/entry0/D22/collimation/Dia4_actual_position,
+		    Diaphragm5:/entry0/D22/collimation/Dia5_actual_position,
+		    Diaphragm6:/entry0/D22/collimation/Dia6_actual_position,
+		    Diaphragm7:/entry0/D22/collimation/Dia7_actual_position,
+		    Diaphragm8:/entry0/D22/collimation/Dia8_actual_position,
+		    Guide1:/entry0/D22/collimation/Coll1_actual_position,
+		    Guide2:/entry0/D22/collimation/Coll2_actual_position,
+		    Guide3:/entry0/D22/collimation/Coll3_actual_position,
+		    Guide4:/entry0/D22/collimation/Coll4_actual_position,
+		    Guide5:/entry0/D22/collimation/Coll5_actual_position,
+		    Guide6:/entry0/D22/collimation/Coll6_actual_position,
+		    Guide7:/entry0/D22/collimation/Coll7_actual_position,
+		    TotalCountsMonitor1:/entry0/monitor1/monsum,
+		    TotalCountsMonitor2:/entry0/monitor2/monsum,
+		    RateMonitor2:/entry0/monitor2/monrate,
+		    ReactorPower:/entry0/reactor_power,
+		    BeamstopX:/entry0/D22/beamstop/bx1_actual,
+		    BeamstopX2:/entry0/D22/beamstop/bx2_actual,
+		    san:/entry0/sample/san_actual,
+		    omega:/entry0/sample/omega_actual,
+		    phi:/entry0/sample/phi_actual,
+		    sdi:/entry0/sample/sdi_actual,
+		    sht:/entry0/sample/sht_actual,
+		    str:/entry0/sample/str_actual,
+		    trs:/entry0/sample/trs_actual,
+		    SampleTemperature:/entry0/sample/temperature,
+		    AirTemperature:/entry0/sample/air_temperature,
+		    RackTemperature:/entry0/sample/rack_temperature,
+		    Bath1Temperature:/entry0/sample/bath1_regulation_temperature,
+		    Bath2Temperature:/entry0/sample/bath2_regulation_temperature,
+		    BathSelector:/entry0/sample/bath_selector_actual,
+		    Position:/entry0/sample/sample_changer_value,
+		    SampleThickness:/entry0/sample/thickness,
+		    MeasurementType:/entry0/D22/MeasurementType/typeOfMeasure,
+		    SampleType:/entry0/D22/MeasurementType/typeOfSample,
+		    SampleSubType:/entry0/D22/MeasurementType/subType,
+		    SampleApertureWidth:/entry0/D22/Beam/sample_ap_x_or_diam,
+		    SampleApertureHeight:/entry0/D22/Beam/sample_ap_y" />
+      </parameter>
     </component-link>
     <!-- These parameters are used in ParallaxCorrection algorithm -->
     <component-link name="detector_back">
diff --git a/instrument/D22_Parameters.xml b/instrument/D22_Parameters.xml
index e72fc364b5e4637cd0104f8517d0cea44bffd866..88ae6e5ea3d45248a43fd60100a167357b524743 100644
--- a/instrument/D22_Parameters.xml
+++ b/instrument/D22_Parameters.xml
@@ -23,6 +23,65 @@
       <parameter name="y-pixel-size" type="number">
         <value val="4"/>
       </parameter>
+
+      <!-- These parameters are used to define headers and entries for GenerateLogbook algorithm -->
+      <parameter name="logbook_default_parameters" type="string">
+	<value val="SampleDescription:/entry0/sample_description,
+		    TotalTime:/entry0/duration,
+		    RateMonitor1:/entry0/monitor1/monrate,
+		    TotalCountsDet:/entry0/D22/detector/detsum,
+		    RateCountsDet:/entry0/D22/detector/detrate,
+		    StartTime:/entry0/start_time,
+		    Wavelength:/entry0/D22/selector/wavelength,
+		    Attenuator:/entry0/D22/attenuator/attenuation_value,
+		    Collimation:/entry0/D22/collimation/actual_position,
+		    SD:/entry0/D22/detector/det_actual,
+		    BeamStopY:/entry0/D22/beamstop/by_actual" />
+      </parameter>
+      <parameter name="logbook_optional_parameters" type="string">
+	<value val="AcquisitionMode:/entry0/acquisition_mode,
+		    Beamstop:/entry0/D22/beamstop/actual_beamstop_number,
+		    VelocitySelectorSpeed:/entry0/D22/selector/rotation_speed,
+		    VelocitySelectorTilt:/entry0/D22/selector/selrot_actual,
+		    Diaphragm1:/entry0/D22/collimation/Dia1_actual_position,
+		    Diaphragm2:/entry0/D22/collimation/Dia2_actual_position,
+		    Diaphragm3:/entry0/D22/collimation/Dia3_actual_position,
+		    Diaphragm4:/entry0/D22/collimation/Dia4_actual_position,
+		    Diaphragm5:/entry0/D22/collimation/Dia5_actual_position,
+		    Diaphragm6:/entry0/D22/collimation/Dia6_actual_position,
+		    Diaphragm7:/entry0/D22/collimation/Dia7_actual_position,
+		    Diaphragm8:/entry0/D22/collimation/Dia8_actual_position,
+		    Guide1:/entry0/D22/collimation/Coll1_actual_position,
+		    Guide2:/entry0/D22/collimation/Coll2_actual_position,
+		    Guide3:/entry0/D22/collimation/Coll3_actual_position,
+		    Guide4:/entry0/D22/collimation/Coll4_actual_position,
+		    Guide5:/entry0/D22/collimation/Coll5_actual_position,
+		    Guide6:/entry0/D22/collimation/Coll6_actual_position,
+		    Guide7:/entry0/D22/collimation/Coll7_actual_position,
+		    TotalCountsMonitor1:/entry0/monitor1/monsum,
+		    TotalCountsMonitor2:/entry0/monitor2/monsum,
+		    RateMonitor2:/entry0/monitor2/monrate,
+		    ReactorPower:/entry0/reactor_power,
+		    BeamstopX:/entry0/D22/beamstop/bx_actual,
+		    san:/entry0/sample/san_actual,
+		    omega:/entry0/sample/omega_actual,
+		    phi:/entry0/sample/phi_actual,
+		    sdi:/entry0/sample/sdi_actual,
+		    sht:/entry0/sample/sht_actual,
+		    str:/entry0/sample/str_actual,
+		    trs:/entry0/sample/trs_actual,
+		    SampleTemperature:/entry0/sample/temperature,
+		    AirTemperature:/entry0/sample/air_temperature,
+		    RackTemperature:/entry0/sample/rack_temperature,
+		    Bath1Temperature:/entry0/sample/bath1_regulation_temperature,
+		    Bath2Temperature:/entry0/sample/bath2_regulation_temperature,
+		    BathSelector:/entry0/sample/bath_selector_actual,
+		    Position:/entry0/sample/sample_changer_value,
+		    SampleThickness:/entry0/sample/thickness,
+		    SampleApertureWidth:/entry0/D22/Beam/sample_ap_x_or_diam,
+		    SampleApertureHeight:/entry0/D22/Beam/sample_ap_y" />
+      </parameter>
+
     </component-link>
 
     <!-- These parameters are used in ParallaxCorrection algorithm -->
diff --git a/instrument/D33_Parameters.xml b/instrument/D33_Parameters.xml
index 28b278424cf1232434026e623dfb2d9ad2662573..386869be4d5d98744326da481367010fe3a9e2c0 100644
--- a/instrument/D33_Parameters.xml
+++ b/instrument/D33_Parameters.xml
@@ -36,6 +36,55 @@
     <parameter name="y-pixel-size" type="number">
       <value val="5"/>
     </parameter>
+
+    <!-- These parameters are used to define headers and entries for GenerateLogbook algorithm -->
+    <parameter name="logbook_default_parameters" type="string">
+      <value val="SampleDescription:/entry0/sample_description,
+		  TotalTime:/entry0/duration,
+		  RateMonitor1:/entry0/monitor1/monrate,
+		  TotalCountsDetAll:/entry0/D33/detector/detsum,
+		  RateCountsDetAll:/entry0/D33/detector/detrate,
+		  StartTime:/entry0/start_time,
+		  Wavelength:/entry0/D33/selector/wavelength,
+		  Attenuator:/entry0/D33/attenuator/attenuation_value,
+		  Collimation:/entry0/D33/collimation/actual_position,
+		  SD1:/entry0/D33/detector/det1_actual,
+		  SD2:/entry0/D33/detector/det2_actual,
+		  BeamStopY:/entry0/D33/beamstop/by_actual" />
+    </parameter>
+    <parameter name="logbook_optional_parameters" type="string">
+      <value val="AcquisitionMode:/entry0/mode,
+		  VelocitySelectorSpeed:/entry0/D33/selector/rotation_speed,
+		  VelocitySelectorTilt:/entry0/D33/selector/selrot_actual,
+		  Diaphragm1:/entry0/D33/collimation/Dia1_actual_position,
+		  Diaphragm2:/entry0/D33/collimation/Dia2_actual_position,
+		  Diaphragm3:/entry0/D33/collimation/Dia3_actual_position,
+		  Diaphragm4:/entry0/D33/collimation/Dia4_actual_position,
+		  Diaphragm5:/entry0/D33/collimation/Dia5_actual_position,
+		  Guide1:/entry0/D33/collimation/Coll1_actual_position,
+		  Guide2:/entry0/D33/collimation/Coll2_actual_position,
+		  Guide3:/entry0/D33/collimation/Coll3_actual_position,
+		  Guide4:/entry0/D33/collimation/Coll4_actual_position,
+		  TotalCountsMonitor1:/entry0/monitor1/monsum,
+		  ReactorPower:/entry0/reactor_power,
+		  BeamstopX:/entry0/D33/beamstop/bx_actual,
+		  san:/entry0/sample/san_actual,
+		  omega:/entry0/sample/omega_actual,
+		  phi:/entry0/sample/phi_actual,
+		  sdi1:/entry0/sample/sdi1_actual,
+		  sdi2:/entry0/sample/sdi2_actual,
+		  sht:/entry0/sample/sht_actual,
+		  str:/entry0/sample/str_actual,
+		  trs:/entry0/sample/trs_actual,
+		  SampleTemperature:/entry0/sample/temperature,
+		  AirTemperature:/entry0/sample/air_temperature,
+		  RackTemperature:/entry0/sample/rack_temperature,
+		  Bath1Temperature:/entry0/sample/bath1_regulation_temperature,
+		  Bath2Temperature:/entry0/sample/bath2_regulation_temperature,
+		  Position:/entry0/sample/sample_changer_value,
+		  SampleApertureWidth:/entry0/D33/Beam/sample_ap_x_or_diam,
+		  SampleApertureHeight:/entry0/D33/Beam/sample_ap_y" />
+    </parameter>
   </component-link>
 
   <!-- These parameters are used in ParallaxCorrection algorithm -->
diff --git a/instrument/D7_Parameters.xml b/instrument/D7_Parameters.xml
index 0d8701ed70f92c83a152cb932e69c8dac21f28fb..8e4b7f8b1076a2b53197a0a30fdffa51fee44ef0 100644
--- a/instrument/D7_Parameters.xml
+++ b/instrument/D7_Parameters.xml
@@ -20,6 +20,17 @@
       <value val="1.50518" />
     </parameter>
 
+    <parameter name="logbook_default_parameters" type="string">
+      <value val="wavelength:/entry0/D7/monochromator/wavelength,
+		  experiment_identifier:/entry0/experiment_identifier,
+		  start_time:/entry0/start_time,
+		  end_time:/entry0/end_time,
+		  duration:/entry0/duration" />
+    </parameter>
+    <parameter name="logbook_optional_parameters" type="string">
+      <value val="TOF:/entry0/acquisition_mode,
+		  polarisation:/entry0/D7/POL/actual_state+/entry0/D7/POL/actual_stateB1B2" />
+    </parameter>
   </component-link>
 
 </parameter-file>
diff --git a/instrument/IN4_Parameters.xml b/instrument/IN4_Parameters.xml
index 987f8f6c63b542562a31c2232c050d4c8692a3ac..ff8c55038edf86f5546c33f943fc4483e361afdd 100644
--- a/instrument/IN4_Parameters.xml
+++ b/instrument/IN4_Parameters.xml
@@ -66,6 +66,47 @@
 		<parameter name="temperature_sample_log" type="string">
 			<value val="sample.temperature" />
 		</parameter>
+
+		<!-- These parameters are used to define headers and entries for GenerateLogbook algorithm -->
+		<parameter name="logbook_default_parameters" type="string">
+		  <value val="acq_mode:/entry0/mode,
+			      start_time:/entry0/start_time,
+			      end_time:/entry0/end_time,
+			      BC2:/entry0/instrument/BC2/rotation_speed,
+			      Ph2:/entry0/instrument/BC2/phase,
+			      FC:/entry0/instrument/FC/rotation_speed,
+			      PhF:/entry0/instrument/FC/setpoint_phase,
+			      Monrate:/entry0/monitor/monsum/,
+			      cps:/entry0/instrument/Detector/detrate,
+			      Treg:/entry0/sample/regulation_temperature,
+			      Tsamp:/entry0/sample/temperature,
+			      subtitle:/entry0/experiment_identifier" />
+		</parameter>
+		<parameter name="logbook_optional_parameters" type="string">
+		  <value val="lambda:/entry0/wavelength,
+			      NCH:/entry0/monitor/time_of_flight/1,
+			      CHW:/entry0/monitor/time_of_flight/0,
+			      delay:/entry0/monitor/time_of_flight/2,
+			      duration:/entry0/duration,
+			      BC1:/entry0/instrument/BC1/rotation_speed,
+			      Tset:/entry0/sample/setpoint_temperature,
+			      Monsum:/entry0/monitor/monsum,
+			      Detrate:/entry0/instrument/Detector/detrate,
+			      BCTR:/entry0/instrument/bctr/value,
+			      HD:/entry0/instrument/hd/value,
+			      FCP:/entry0/instrument/fcp/value,
+			      MBA:/entry0/instrument/mba/value,
+			      MCR:/entry0/instrument/mcr/value,
+			      MFC:/entry0/instrument/mfc/value,
+			      MHCU111:/entry0/instrument/mhcu111/value,
+			      MHCU220:/entry0/instrument/mhcu220/value,
+			      MHGR:/entry0/instrument/mhgr/value,
+			      MHSPARE:/entry0/instrument/mhspare/value,
+			      MNA:/entry0/instrument/mna/value,
+			      MTR:/entry0/instrument/mtr/value,
+			      MVC:/entry0/instrument/mvc/value" />
+		</parameter>
+
 	</component-link>
 
 	<component-link name="wide_angle">
diff --git a/instrument/IN5_Parameters.xml b/instrument/IN5_Parameters.xml
index 4eb0ad4c8519fe3b9920d54de22e0289ed4f1ce0..380f634fea70d150e7746eaf02c1602668c1a000 100644
--- a/instrument/IN5_Parameters.xml
+++ b/instrument/IN5_Parameters.xml
@@ -78,6 +78,35 @@
 		<parameter name="temperature_sample_log" type="string">
 			<value val="sample.temperature" />
 		</parameter>
+
+
+		<!-- These parameters are used to define headers and entries for GenerateLogbook algorithm -->
+		<parameter name="logbook_default_parameters" type="string">
+		  <value val="acq_mode:/entry0/acquisition_mode,
+			      start_time:/entry0/start_time,
+			      end_time:/entry0/end_time,
+			      SRot:/entry0/instrument/SRot/value,
+			      CO:/entry0/instrument/CO/rotation_speed,
+			      PhC:/entry0/instrument/CO/setpoint_phase,
+			      FO:/entry0/instrument/FO/rotation_speed,
+			      PhF:/entry0/instrument/FO/setpoint_phase,
+			      Monrate:/entry0/monitor/monrate,
+			      cps:/entry0/instrument/Detector/detrate,
+			      Treg:/entry0/sample/regulation_temperature,
+			      Tsamp:/entry0/sample/temperature,
+			      subtitle:/entry0/experiment_identifier" />
+		</parameter>
+		<parameter name="logbook_optional_parameters" type="string">
+		  <value val="lambda:/entry0/wavelength,
+			      NCH:/entry0/monitor/time_of_flight/1,
+			      CHW:/entry0/monitor/time_of_flight/0,
+			      delay:/entry0/monitor/time_of_flight/2,
+			      duration:/entry0/duration,
+			      Tset:/entry0/sample/setpoint_temperature,
+			      Monsum:/entry0/monitor/monsum,
+			      ReactorPower:/entry0/reactor_power,
+			      ScannedVariables:/entry0/data_scan/scanned_variables/variables_names/name" />
+		</parameter>
 	</component-link>
 
 </parameter-file>
diff --git a/instrument/IN6_Parameters.xml b/instrument/IN6_Parameters.xml
index 72c1f965df1aef06e5cf72bbd4d379dea24b2df3..5ce363e211922f5177571e5eded1fa2811b65967 100644
--- a/instrument/IN6_Parameters.xml
+++ b/instrument/IN6_Parameters.xml
@@ -78,6 +78,35 @@
 		<parameter name="temperature_sample_log" type="string">
 			<value val="sample.temperature" />
 		</parameter>
+
+		<!-- These parameters are used to define headers and entries for GenerateLogbook algorithm -->
+		<parameter name="logbook_default_parameters" type="string">
+		  <value val="acq_mode:/entry0/acquisition_mode,
+			      start_time:/entry0/start_time,
+			      end_time:/entry0/end_time,
+			      Fermi:/entry0/instrument/Fermi/rotation_speed,
+			      Ph:/entry0/instrument/Fermi/setpoint_phase,
+			      Mon2rate:/entry0/monitor2/monrate,
+			      cps:/entry0/instrument/Detector/detrate,
+			      Treg:/entry0/sample/regulation_temperature,
+			      Tsamp:/entry0/sample/temperature,
+			      subtitle:/entry0/experiment_identifier" />
+		</parameter>
+		<parameter name="logbook_optional_parameters" type="string">
+		  <value val="lambda:/entry0/wavelength,
+			      NCH:/entry0/monitor1/time_of_flight/1,
+			      CHW:/entry0/monitor1/time_of_flight/0,
+			      delay:/entry0/monitor1/time_of_flight/2,
+			      duration:/entry0/duration,
+			      Suppressor:/entry0/instrument/Suppressor/rotation_speed,
+			      SuppressorPh:/entry0/instrument/Suppressor/setpoint_phase,
+			      Tset:/entry0/sample/setpoint_temperature,
+			      Mon1sum:/entry0/monitor1/monsum,
+			      Mon1rate:/entry0/monitor1/monrate,
+			      Mon2sum:/entry0/monitor2/monsum,
+			      Mon3sum:/entry0/monitor3/monsum,
+			      Mon3rate:/entry0/monitor3/monrate" />
+		</parameter>
 	</component-link>
 
 </parameter-file>
diff --git a/instrument/PANTHER_Parameters.xml b/instrument/PANTHER_Parameters.xml
index fbd7117b5bf64dd1b880b767db3634420591f55d..9aec0924cdcdfb9df9c75a9b94943e9fbe1d5967 100644
--- a/instrument/PANTHER_Parameters.xml
+++ b/instrument/PANTHER_Parameters.xml
@@ -86,6 +86,53 @@
 		<parameter name="detector_for_height_axis" type="string">
 			<value val="tube_1" />
 		</parameter>
-	</component-link>
+
+		<!-- These parameters are used to define headers and entries for GenerateLogbook algorithm -->
+		<parameter name="logbook_default_parameters" type="string">
+		  <value val="acq_mode:/entry0/acquisition_mode,
+			      start_time:/entry0/start_time,
+			      end_time:/entry0/end_time,
+			      Sapphire:/entry0/instrument/SapphirFilter/value_string,
+			      A2:/entry0/instrument/a2/value,
+			      A1:/entry0/instrument/a1/value,
+			      TM:/entry0/instrument/tm/value,
+			      GM:/entry0/instrument/gm/value,
+			      DCV:/entry0/instrument/dcv/value,
+			      DCH:/entry0/instrument/dch/value,
+			      RMVpg:/entry0/instrument/rmvpg/value,
+			      RMHpg:/entry0/instrument/rmhpg/value,
+			      L12:/entry0/instrument/bctr/value,
+			      BC2:/entry0/instrument/BC2/rotation_speed,
+			      Ph2:/entry0/instrument/BC2/phase,
+			      FC:/entry0/instrument/FC/rotation_speed,
+			      PhF:/entry0/instrument/FC/setpoint_phase,
+			      NCH:/entry0/monitor/time_of_flight/1,
+			      CHW:/entry0/monitor/time_of_flight/0,
+			      delay:/entry0/monitor/time_of_flight/2,
+			      D1h:/entry0/instrument/d1t/value+/entry0/instrument/d1b/value,
+			      D1w:/entry0/instrument/d1l/value+/entry0/instrument/d1r/value,
+			      Monrate:/entry0/monitor/monsum//entry0/actual_time,
+			      cps:/entry0/instrument/Detector/detsum//entry0/actual_time,
+			      Treg:/entry0/sample/regulation_temperature,
+			      Tsamp:/entry0/sample/temperature,
+			      subtitle:/entry0/experiment_identifier" />
+		</parameter>
+		<parameter name="logbook_optional_parameters" type="string">
+		  <value val="lambda:/entry0/wavelength,
+			      duration:/entry0/duration,
+			      FilterVal:/entry0/instrument/SapphirFilter/value,
+			      BC1:/entry0/instrument/BC1/rotation_speed,
+			      CH:/entry0/instrument/ch/value,
+			      D1t:/entry0/instrument/d1t/value,
+			      D1b:/entry0/instrument/d1b/value,
+			      D1l:/entry0/instrument/d1l/value,
+			      D1r:/entry0/instrument/d1r/value,
+			      Tset:/entry0/sample/setpoint_temperature,
+			      NTubes:/entry0/instrument/Detector/num_tubes,
+			      Monsum:/entry0/monitor/monsum,
+			      Detrate:entry0/instrument/Detector/detrate" />
+		</parameter>
+
+        </component-link>
 
 </parameter-file>
diff --git a/qt/applications/workbench/workbench/app/mainwindow.py b/qt/applications/workbench/workbench/app/mainwindow.py
index f38c0ac94b1d3f00e46f494bee7fafaf666ad8b9..16e6dd64d588c8bc96750516f9f71cd8702f4bc2 100644
--- a/qt/applications/workbench/workbench/app/mainwindow.py
+++ b/qt/applications/workbench/workbench/app/mainwindow.py
@@ -10,6 +10,7 @@
 """
 Defines the QMainWindow of the application and the main() entry point.
 """
+import builtins
 import os
 
 from mantid.api import FrameworkManager
@@ -39,6 +40,7 @@ from workbench.config import CONF  # noqa
 from workbench.plotting.globalfiguremanager import GlobalFigureManager  # noqa
 from workbench.utils.windowfinder import find_all_windows_that_are_savable  # noqa
 from workbench.utils.workspacehistorygeneration import get_all_workspace_history_from_ads  # noqa
+from workbench.utils.io import input_qinputdialog
 from workbench.projectrecovery.projectrecovery import ProjectRecovery  # noqa
 from workbench.utils.recentlyclosedscriptsmenu import RecentlyClosedScriptsMenu # noqa
 from mantidqt.utils.asynchronous import BlockingAsyncTaskWithCallback  # noqa
@@ -204,6 +206,8 @@ class MainWindow(QMainWindow):
         self.readSettings(CONF)
         self.config_updated()
 
+        self.override_python_input()
+
     def post_mantid_init(self):
         """Run any setup that requires mantid
         to have been initialized
@@ -756,3 +760,7 @@ class MainWindow(QMainWindow):
         for widget in self.widgets:
             if hasattr(widget, 'writeSettings'):
                 widget.writeSettings(settings)
+
+    def override_python_input(self):
+        """Replace python input with a call to a qinputdialog"""
+        builtins.input = QAppThreadCall(input_qinputdialog)
diff --git a/qt/applications/workbench/workbench/plugins/jupyterconsole.py b/qt/applications/workbench/workbench/plugins/jupyterconsole.py
index 5f8f599f011781256ff6f38b5686a4109ea4e4ba..9798b9626a104d8a06e49ab72575ebf2d721dd19 100644
--- a/qt/applications/workbench/workbench/plugins/jupyterconsole.py
+++ b/qt/applications/workbench/workbench/plugins/jupyterconsole.py
@@ -27,7 +27,7 @@ from ..plugins.base import PluginWidget # noqa
 # from mantidqt.utils.qt import toQSettings when readSettings/writeSettings are implemented
 
 # should we share this with plugins.editor?
-STARTUP_CODE = """from __future__ import (absolute_import, division, print_function, unicode_literals)
+STARTUP_CODE = """
 from mantid.simpleapi import *
 import matplotlib.pyplot as plt
 import numpy as np
diff --git a/qt/applications/workbench/workbench/test/mainwindowtest.py b/qt/applications/workbench/workbench/test/mainwindowtest.py
index 2f9b1e7fe1f4f55da2b333c09ea32bb4ce049b28..233dd8a7953b8ca17a38389e54d981f80e4261da 100644
--- a/qt/applications/workbench/workbench/test/mainwindowtest.py
+++ b/qt/applications/workbench/workbench/test/mainwindowtest.py
@@ -337,6 +337,14 @@ class MainWindowTest(unittest.TestCase):
         }
         self.assertDictEqual(expected_interfaces, all_interfaces)
 
+    @patch('workbench.app.mainwindow.input_qinputdialog')
+    def test_override_python_input_replaces_input_with_qinputdialog(self, mock_input):
+        self.main_window.override_python_input()
+
+        input("prompt")
+
+        mock_input.assert_called_with("prompt")
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/qt/applications/workbench/workbench/utils/io.py b/qt/applications/workbench/workbench/utils/io.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b9ca5b699d2c1793a183137ddf56dccac394859
--- /dev/null
+++ b/qt/applications/workbench/workbench/utils/io.py
@@ -0,0 +1,24 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2021 ISIS Rutherford Appleton Laboratory UKRI,
+#   NScD Oak Ridge National Laboratory, European Spallation Source,
+#   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidworkbench package
+from qtpy.QtWidgets import QInputDialog
+
+
+def input_qinputdialog(prompt: str = "") -> str:
+    """
+    Raises a QInputDialog with a given prompt and returns the user input as a string.
+    If the user cancels the dialog, an EOFError is raised.
+    Intended to be used to override python's `input` function to be more user friendly.
+    """
+    dlg = QInputDialog()
+    dlg.setInputMode(QInputDialog.TextInput)
+    dlg.setLabelText(str(prompt) if prompt is not None else "")
+    accepted = dlg.exec_()
+    if accepted:
+        return dlg.textValue()
+    else:
+        raise EOFError("User input request cancelled")
diff --git a/qt/applications/workbench/workbench/utils/test/test_io.py b/qt/applications/workbench/workbench/utils/test/test_io.py
new file mode 100644
index 0000000000000000000000000000000000000000..ffcc14e79f730d599a25ab8086a1532a72b945f0
--- /dev/null
+++ b/qt/applications/workbench/workbench/utils/test/test_io.py
@@ -0,0 +1,42 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2021 ISIS Rutherford Appleton Laboratory UKRI,
+#   NScD Oak Ridge National Laboratory, European Spallation Source,
+#   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidworkbench package
+from unittest import TestCase
+from unittest.mock import patch
+
+from qtpy.QtWidgets import QInputDialog
+from workbench.utils.io import input_qinputdialog
+
+
+class IOTest(TestCase):
+
+    @patch('workbench.utils.io.QInputDialog')
+    def test_input_qinputdialog_setup_is_correct(self, mock_QInputDialogClass):
+        mock_QInputDialogClass.TextInput = QInputDialog.TextInput
+        mock_QInputDialog = mock_QInputDialogClass()
+
+        _ = input_qinputdialog("prompt")
+
+        mock_QInputDialog.setInputMode.assert_called_with(QInputDialog.TextInput)
+        mock_QInputDialog.setLabelText.assert_called_with("prompt")
+
+    @patch('workbench.utils.io.QInputDialog')
+    def test_input_qinputdialog_return_value_is_correct_when_dialog_accepted(self, mock_QInputDialogClass):
+        mock_QInputDialog = mock_QInputDialogClass()
+        mock_QInputDialog.exec_.return_value = True
+        mock_QInputDialog.textValue.return_value = "their input"
+
+        user_input = input_qinputdialog()
+
+        self.assertEqual(user_input, "their input")
+
+    @patch('workbench.utils.io.QInputDialog')
+    def test_input_qinputdialog_raises_RuntimeError_when_input_cancelled(self, mock_QInputDialogClass):
+        mock_QInputDialog = mock_QInputDialogClass()
+        mock_QInputDialog.exec_.return_value = False
+
+        self.assertRaises(EOFError, input_qinputdialog)
diff --git a/qt/python/mantidqt/widgets/instrumentview/test/test_instrumentview_io.py b/qt/python/mantidqt/widgets/instrumentview/test/test_instrumentview_io.py
index ed3c7ae1ee2c70db86fd4a2fd86686ca1a314ec4..09176a619dbe19a27860a7f3f5f9ba9a4bc3956c 100644
--- a/qt/python/mantidqt/widgets/instrumentview/test/test_instrumentview_io.py
+++ b/qt/python/mantidqt/widgets/instrumentview/test/test_instrumentview_io.py
@@ -34,8 +34,8 @@ INSTRUMENT_VIEW_DICT = {u'workspaceName': u'ws',
                                            u'freezeRotation': False},
                             u'treeTab': {u'expandedItems': []},
                             u'pickTab': {u'freeDraw': False, u'ringEllipse': False, u'edit': False,
-                                         u'tube': False, u'peakSelect': False, u'zoom': False, u'one': True,
-                                         u'ringRectangle': False, u'peak': False, u'ellipse': False,
+                                         u'tube': False, u'peakErase': False, u'zoom': False, u'one': True,
+                                         u'ringRectangle': False, u'peakAdd': False, u'ellipse': False,
                                          u'rectangle': False}}, u'surfaceType': 0,
                         u'actor': {u'binMasks': [], u'fileName': u'viridis'},
                         u'energyTransfer': [0.0, 20000.0],
diff --git a/qt/python/mantidqt/widgets/jupyterconsole.py b/qt/python/mantidqt/widgets/jupyterconsole.py
index 8b2c65657320c3906144c7a804f36ba2aac5612a..d5efc6a88b165417f26ec43d3dd08a21549ae5f7 100644
--- a/qt/python/mantidqt/widgets/jupyterconsole.py
+++ b/qt/python/mantidqt/widgets/jupyterconsole.py
@@ -26,6 +26,8 @@ except ImportError:
 # local imports
 from inspect import getfullargspec
 from mantidqt.utils.asynchronous import BlockingAsyncTaskWithCallback
+from mantidqt.utils.qt.qappthreadcall import QAppThreadCall
+from workbench.utils.io import input_qinputdialog
 
 
 class InProcessJupyterConsole(RichJupyterWidget):
@@ -62,6 +64,9 @@ class InProcessJupyterConsole(RichJupyterWidget):
         self.kernel_manager = kernel_manager
         self.kernel_client = kernel_client
 
+        # Override python input to raise a QInputDialog.
+        kernel.raw_input = QAppThreadCall(input_qinputdialog)
+
     def keyPressEvent(self, event):
         if QApplication.keyboardModifiers() & Qt.ControlModifier and (event.key() == Qt.Key_Equal):
             self.change_font_size(1)
diff --git a/qt/python/mantidqt/widgets/test/test_jupyterconsole.py b/qt/python/mantidqt/widgets/test/test_jupyterconsole.py
index a1b84668154ea983b88bf2faedb03b3e9d6694f8..f471a211778541f8b9ea3dfcb8dad5afc3cfc9c9 100644
--- a/qt/python/mantidqt/widgets/test/test_jupyterconsole.py
+++ b/qt/python/mantidqt/widgets/test/test_jupyterconsole.py
@@ -14,6 +14,7 @@ try:
 except ImportError:
     pass
 import unittest
+from unittest.mock import patch
 
 # third-party library imports
 
@@ -41,6 +42,18 @@ class InProcessJupyterConsoleTest(unittest.TestCase):
         widget.kernel_manager.shutdown_kernel()
         del widget
 
+    @patch('mantidqt.widgets.jupyterconsole.input_qinputdialog')
+    def test_construction_overrides_python_input_with_qinputdialog(self, mock_input):
+        widget = InProcessJupyterConsole()
+        kernel = widget.kernel_manager.kernel
+        kernel.raw_input("prompt")
+
+        mock_input.assert_called_with("prompt")
+
+        self._pre_delete_console_cleanup(widget)
+        widget.kernel_manager.shutdown_kernel()
+        del widget
+
     def _pre_delete_console_cleanup(self, console):
         """Certain versions of qtconsole seem to raise an attribute error on
         exit of the process when an event is delivered to an event filter
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/ImageInfoModelMatrixWS.h b/qt/widgets/common/inc/MantidQtWidgets/Common/ImageInfoModelMatrixWS.h
index 48f6955e1c8d0d1632229d6257cc13d200c3aadc..9a1563b0c5227a6c7a5f2a23aafa188f2c1cc195 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/ImageInfoModelMatrixWS.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/ImageInfoModelMatrixWS.h
@@ -43,8 +43,6 @@ public:
 private:
   void setUnitsInfo(ImageInfoModel::ImageInfo *info, int infoIndex,
                     const size_t wsIndex, const double x) const;
-  std::tuple<Mantid::Kernel::DeltaEMode::Type, double>
-  efixedAt(const size_t wsIndex) const;
   void cacheWorkspaceInfo();
   void createItemNames();
 
diff --git a/qt/widgets/common/src/ImageInfoModelMatrixWS.cpp b/qt/widgets/common/src/ImageInfoModelMatrixWS.cpp
index 9a2e687c2f85502777846f81a4db73d98cc41e79..caf5ca327624b6b9b87264d817f7aa641f011823 100644
--- a/qt/widgets/common/src/ImageInfoModelMatrixWS.cpp
+++ b/qt/widgets/common/src/ImageInfoModelMatrixWS.cpp
@@ -105,10 +105,17 @@ void ImageInfoModelMatrixWS::setUnitsInfo(ImageInfoModel::ImageInfo *info,
                                           int infoIndex, const size_t wsIndex,
                                           const double x) const {
   const auto l1 = m_spectrumInfo->l1();
-  const auto l2 = m_spectrumInfo->l2(wsIndex);
-  const auto twoTheta = m_spectrumInfo->twoTheta(wsIndex);
-  const auto [emode, efixed] = efixedAt(wsIndex);
-
+  auto emode = m_workspace->getEMode();
+  UnitParametersMap pmap{};
+  m_spectrumInfo->getDetectorValues(Units::Empty(), Units::Empty(), emode,
+                                    false, wsIndex, pmap);
+  double efixed = 0.;
+  if (pmap.find(UnitParams::efixed) != pmap.end()) {
+    efixed = pmap[UnitParams::efixed];
+  }
+  // if it's not possible to find an efixed we are forced to treat is as elastic
+  if (efixed == 0.0)
+    emode = DeltaEMode::Elastic;
   double tof(0.0);
   const auto &unitFactory = UnitFactory::Instance();
   const auto tofUnit = unitFactory.create("TOF");
@@ -117,8 +124,7 @@ void ImageInfoModelMatrixWS::setUnitsInfo(ImageInfoModel::ImageInfo *info,
     tof = x;
   } else {
     try {
-      tof =
-          m_xunit->convertSingleToTOF(x, l1, l2, twoTheta, emode, efixed, 0.0);
+      tof = m_xunit->convertSingleToTOF(x, l1, emode, pmap);
     } catch (std::exception &exc) {
       // without TOF we can't get to the other units
       if (g_log.is(Logger::Priority::PRIO_DEBUG))
@@ -133,8 +139,7 @@ void ImageInfoModelMatrixWS::setUnitsInfo(ImageInfoModel::ImageInfo *info,
     if (!requiresEFixed || efixed > 0.0) {
       try {
         // the final parameter is unused and a relic
-        const auto unitValue = unit->convertSingleFromTOF(tof, l1, l2, twoTheta,
-                                                          emode, efixed, 0.0);
+        const auto unitValue = unit->convertSingleFromTOF(tof, l1, emode, pmap);
         info->setValue(infoIndex, defaultFormat(unitValue));
       } catch (std::exception &exc) {
         if (g_log.is(Logger::Priority::PRIO_DEBUG))
@@ -228,56 +233,5 @@ void ImageInfoModelMatrixWS::createItemNames() {
   }
 }
 
-/**
- * Return tuple(emode, efixed) for the pixel at the given workspace index
- * @param wsIndex Indeox of spectrum to query
- */
-std::tuple<Mantid::Kernel::DeltaEMode::Type, double>
-ImageInfoModelMatrixWS::efixedAt(const size_t wsIndex) const {
-  DeltaEMode::Type emode = m_workspace->getEMode();
-  double efixed(0.0);
-  if (m_spectrumInfo->isMonitor(wsIndex))
-    return std::make_tuple(emode, efixed);
-
-  if (emode == DeltaEMode::Direct) {
-    const auto &run = m_workspace->run();
-    for (const auto &logName : {"Ei", "EnergyRequested", "EnergyEstimate"}) {
-      if (run.hasProperty(logName)) {
-        efixed = run.getPropertyValueAsType<double>(logName);
-        break;
-      }
-    }
-  } else if (emode == DeltaEMode::Indirect) {
-    const auto &detector = m_spectrumInfo->detector(wsIndex);
-    const ParameterMap &pmap = m_workspace->constInstrumentParameters();
-    try {
-      for (const auto &paramName : {"Efixed", "Efixed-val"}) {
-        auto parameter = pmap.getRecursive(&detector, paramName);
-        if (parameter) {
-          efixed = parameter->value<double>();
-          break;
-        }
-        // check the instrument as if the detector is a group the above
-        // recursion doesn't work
-        parameter = pmap.getRecursive(m_instrument.get(), paramName);
-        if (parameter) {
-          efixed = parameter->value<double>();
-          break;
-        }
-      }
-    } catch (std::runtime_error &exc) {
-      g_log.debug() << "Failed to get efixed from spectrum at index " << wsIndex
-                    << ": " << exc.what() << "\n";
-      efixed = 0.0;
-    }
-  }
-
-  // if it's not possible to find an efixed we are forced to treat is as elastic
-  if (efixed == 0.0)
-    emode = DeltaEMode::Elastic;
-
-  return std::make_tuple(emode, efixed);
-}
-
 } // namespace MantidWidgets
 } // namespace MantidQt
diff --git a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h
index 7c6b4e346bba3677624e03c76ab30d408bcd6045..faf7f1db0175fe4cefc260c2d1f8a8b35428086a 100644
--- a/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h
+++ b/qt/widgets/instrumentview/inc/MantidQtWidgets/InstrumentView/InstrumentWidgetPickTab.h
@@ -137,8 +137,8 @@ private:
   QPushButton *m_zoom;  ///< Button switching on navigation mode
   QPushButton *m_one;   ///< Button switching on single detector selection mode
   QPushButton *m_tube; ///< Button switching on detector's parent selection mode
-  QPushButton *m_peak; ///< Button switching on peak creation mode
-  QPushButton *m_peakSelect;  ///< Button switching on peak selection mode
+  QPushButton *m_peakAdd;     ///< Button switching on peak creation mode
+  QPushButton *m_peakErase;   ///< Button switching on peak erase mode
   QPushButton *m_peakCompare; ///< Button switching on peak comparison mode
   QPushButton *m_peakAlign;   ///< Button switching on peak alignment mode
   QPushButton *m_rectangle;   ///< Button switching on drawing a rectangular
diff --git a/qt/widgets/instrumentview/src/InstrumentWidgetDecoder.cpp b/qt/widgets/instrumentview/src/InstrumentWidgetDecoder.cpp
index 1e20c08595998e6e34cc79cec5fc5a65c45e1085..14191b6a6253a65100282bb2b66392c396f07924 100644
--- a/qt/widgets/instrumentview/src/InstrumentWidgetDecoder.cpp
+++ b/qt/widgets/instrumentview/src/InstrumentWidgetDecoder.cpp
@@ -173,8 +173,8 @@ void InstrumentWidgetDecoder::decodePickTab(const QMap<QString, QVariant> &map,
   obj->m_free_draw->setChecked(map[QString("freeDraw")].toBool());
   obj->m_one->setChecked(map[QString("one")].toBool());
   obj->m_tube->setChecked(map[QString("tube")].toBool());
-  obj->m_peak->setChecked(map[QString("peak")].toBool());
-  obj->m_peakSelect->setChecked(map[QString("peakSelect")].toBool());
+  obj->m_peakAdd->setChecked(map[QString("peakAdd")].toBool());
+  obj->m_peakErase->setChecked(map[QString("peakErase")].toBool());
 }
 
 void InstrumentWidgetDecoder::decodeActor(
diff --git a/qt/widgets/instrumentview/src/InstrumentWidgetEncoder.cpp b/qt/widgets/instrumentview/src/InstrumentWidgetEncoder.cpp
index e6dae0dfab9cbf4cb8f1fc7678517eef7c7aff5b..32afb27febaaf62417d7bbb42af98bb0795a8b5f 100644
--- a/qt/widgets/instrumentview/src/InstrumentWidgetEncoder.cpp
+++ b/qt/widgets/instrumentview/src/InstrumentWidgetEncoder.cpp
@@ -207,8 +207,8 @@ InstrumentWidgetEncoder::encodePickTab(const InstrumentWidgetPickTab *tab) {
   map.insert(QString("freeDraw"), QVariant(tab->m_free_draw->isChecked()));
   map.insert(QString("one"), QVariant(tab->m_one->isChecked()));
   map.insert(QString("tube"), QVariant(tab->m_tube->isChecked()));
-  map.insert(QString("peak"), QVariant(tab->m_peak->isChecked()));
-  map.insert(QString("peakSelect"), QVariant(tab->m_peakSelect->isChecked()));
+  map.insert(QString("peakAdd"), QVariant(tab->m_peakAdd->isChecked()));
+  map.insert(QString("peakErase"), QVariant(tab->m_peakErase->isChecked()));
 
   return map;
 }
diff --git a/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp b/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp
index 70a8c7e30b770bc686b74fd5737aca300f1821c1..4e17dc09626c7ffa29b3c8470704b694210d2d65 100644
--- a/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp
+++ b/qt/widgets/instrumentview/src/InstrumentWidgetPickTab.cpp
@@ -236,17 +236,17 @@ InstrumentWidgetPickTab::InstrumentWidgetPickTab(InstrumentWidget *instrWidget)
   m_edit->setIcon(QIcon(":/PickTools/selection-edit.png"));
   m_edit->setToolTip("Edit a shape");
 
-  m_peak = new QPushButton();
-  m_peak->setCheckable(true);
-  m_peak->setAutoExclusive(true);
-  m_peak->setIcon(QIcon(":/PickTools/selection-peak.png"));
-  m_peak->setToolTip("Add single crystal peak");
-
-  m_peakSelect = new QPushButton();
-  m_peakSelect->setCheckable(true);
-  m_peakSelect->setAutoExclusive(true);
-  m_peakSelect->setIcon(QIcon(":/PickTools/eraser.png"));
-  m_peakSelect->setToolTip("Erase single crystal peak(s)");
+  m_peakAdd = new QPushButton();
+  m_peakAdd->setCheckable(true);
+  m_peakAdd->setAutoExclusive(true);
+  m_peakAdd->setIcon(QIcon(":/PickTools/selection-peak.png"));
+  m_peakAdd->setToolTip("Add single crystal peak");
+
+  m_peakErase = new QPushButton();
+  m_peakErase->setCheckable(true);
+  m_peakErase->setAutoExclusive(true);
+  m_peakErase->setIcon(QIcon(":/PickTools/eraser.png"));
+  m_peakErase->setToolTip("Erase single crystal peak(s)");
 
   m_peakCompare = new QPushButton();
   m_peakCompare->setCheckable(true);
@@ -271,8 +271,8 @@ InstrumentWidgetPickTab::InstrumentWidgetPickTab(InstrumentWidget *instrWidget)
   toolBox->addWidget(m_free_draw, 0, 7);
   toolBox->addWidget(m_one, 1, 0);
   toolBox->addWidget(m_tube, 1, 1);
-  toolBox->addWidget(m_peak, 1, 2);
-  toolBox->addWidget(m_peakSelect, 1, 3);
+  toolBox->addWidget(m_peakAdd, 1, 2);
+  toolBox->addWidget(m_peakErase, 1, 3);
   toolBox->addWidget(m_peakCompare, 1, 4);
   toolBox->addWidget(m_peakAlign, 1, 5);
   toolBox->setColumnStretch(8, 1);
@@ -280,8 +280,8 @@ InstrumentWidgetPickTab::InstrumentWidgetPickTab(InstrumentWidget *instrWidget)
   connect(m_zoom, SIGNAL(clicked()), this, SLOT(setSelectionType()));
   connect(m_one, SIGNAL(clicked()), this, SLOT(setSelectionType()));
   connect(m_tube, SIGNAL(clicked()), this, SLOT(setSelectionType()));
-  connect(m_peak, SIGNAL(clicked()), this, SLOT(setSelectionType()));
-  connect(m_peakSelect, SIGNAL(clicked()), this, SLOT(setSelectionType()));
+  connect(m_peakAdd, SIGNAL(clicked()), this, SLOT(setSelectionType()));
+  connect(m_peakErase, SIGNAL(clicked()), this, SLOT(setSelectionType()));
   connect(m_peakCompare, SIGNAL(clicked()), this, SLOT(setSelectionType()));
   connect(m_peakAlign, SIGNAL(clicked()), this, SLOT(setSelectionType()));
   connect(m_rectangle, SIGNAL(clicked()), this, SLOT(setSelectionType()));
@@ -313,7 +313,7 @@ void InstrumentWidgetPickTab::collapsePlotPanel() {
  * Returns true if the plot can be updated when the mouse moves over detectors
  */
 bool InstrumentWidgetPickTab::canUpdateTouchedDetector() const {
-  return !m_peak->isChecked();
+  return !m_peakAdd->isChecked();
 }
 
 /**
@@ -444,12 +444,12 @@ void InstrumentWidgetPickTab::setSelectionType() {
     if (plotType < DetectorPlotController::TubeSum) {
       plotType = DetectorPlotController::TubeSum;
     }
-  } else if (m_peak->isChecked()) {
+  } else if (m_peakAdd->isChecked()) {
     m_selectionType = AddPeak;
     m_activeTool->setText("Tool: Add a single crystal peak");
     surfaceMode = ProjectionSurface::AddPeakMode;
     plotType = DetectorPlotController::Single;
-  } else if (m_peakSelect->isChecked()) {
+  } else if (m_peakErase->isChecked()) {
     m_selectionType = ErasePeak;
     m_activeTool->setText("Tool: Erase crystal peak(s)");
     surfaceMode = ProjectionSurface::ErasePeakMode;
@@ -717,12 +717,13 @@ void InstrumentWidgetPickTab::selectTool(const ToolType tool) {
     m_tube->setChecked(true);
     break;
   case PeakSelect:
-    m_peak->setChecked(true);
+    m_peakAdd->setChecked(true);
     break;
   case PeakCompare:
     m_peakCompare->setChecked(true);
+    break;
   case PeakErase:
-    m_peakSelect->setChecked(true);
+    m_peakErase->setChecked(true);
     break;
   case DrawRectangle:
     m_rectangle->setChecked(true);
@@ -841,7 +842,7 @@ void InstrumentWidgetPickTab::loadFromProject(const std::string &lines) {
   std::vector<QPushButton *> buttons{
       m_zoom,         m_edit,           m_ellipse,   m_rectangle,
       m_ring_ellipse, m_ring_rectangle, m_free_draw, m_one,
-      m_tube,         m_peak,           m_peakSelect};
+      m_tube,         m_peakAdd,        m_peakErase};
 
   tab.selectLine("ActiveTools");
   for (auto button : buttons) {
@@ -867,7 +868,7 @@ std::string InstrumentWidgetPickTab::saveToProject() const {
   std::vector<QPushButton *> buttons{
       m_zoom,         m_edit,           m_ellipse,   m_rectangle,
       m_ring_ellipse, m_ring_rectangle, m_free_draw, m_one,
-      m_tube,         m_peak,           m_peakSelect};
+      m_tube,         m_peakAdd,        m_peakErase};
 
   tab.writeLine("ActiveTools");
   for (auto button : buttons) {
diff --git a/qt/widgets/instrumentview/src/PeakOverlay.cpp b/qt/widgets/instrumentview/src/PeakOverlay.cpp
index aa10501faed032efdeb3237eae78c97f40e176cc..5ae8b426095d8588b8c3794c350cddc0ad03deb0 100644
--- a/qt/widgets/instrumentview/src/PeakOverlay.cpp
+++ b/qt/widgets/instrumentview/src/PeakOverlay.cpp
@@ -7,6 +7,7 @@
 #include "MantidQtWidgets/InstrumentView/PeakOverlay.h"
 #include "MantidAPI/AlgorithmManager.h"
 #include "MantidAPI/IPeaksWorkspace.h"
+#include "MantidDataObjects/Peak.h"
 #include "MantidQtWidgets/InstrumentView/UnwrappedSurface.h"
 
 #include <QList>
@@ -15,6 +16,10 @@
 #include <cmath>
 #include <stdexcept>
 
+namespace {
+Mantid::Kernel::Logger g_log("PeakOverlay");
+}
+
 namespace MantidQt {
 namespace MantidWidgets {
 
@@ -243,7 +248,14 @@ void PeakOverlay::createMarkers(const PeakMarker2D::Style &style) {
   this->clear();
   for (int i = 0; i < nPeaks; ++i) {
     Mantid::Geometry::IPeak &peak = getPeak(i);
-    const Mantid::Kernel::V3D &pos = peak.getDetPos();
+    Mantid::Kernel::V3D pos;
+    try {
+      auto peakFull = dynamic_cast<Mantid::DataObjects::Peak &>(peak);
+      pos = peakFull.getDetPos();
+    } catch (std::bad_cast &) {
+      g_log.error("Cannot create markers for this type of peak");
+      return;
+    }
     // Project the peak (detector) position onto u,v coords
     double u, v, uscale, vscale;
     m_surface->project(pos, u, v, uscale, vscale);
diff --git a/scripts/Calibration/tofpd/diagnostics.py b/scripts/Calibration/tofpd/diagnostics.py
index a4c60fdb2612154325098df052b8a6cf4958367d..114306584d027c402c8fa453b6dbe2949e296a2a 100644
--- a/scripts/Calibration/tofpd/diagnostics.py
+++ b/scripts/Calibration/tofpd/diagnostics.py
@@ -125,6 +125,27 @@ def __get_bad_counts(y, mean=None, band=0.01):
     return len(y[(y > top) | (y < bot)])
 
 
+def get_peakwindows(peakpositions: np.ndarray):
+    """
+    Calculates the peak windows for the given peak positions used for FitPeaks
+    :param peakpositions: List of peak positions in d-space
+    :return: List of peak windows (left and right) for each peak position
+    """
+    peakwindows = []
+    deltas = 0.5 * (peakpositions[1:] - peakpositions[:-1])
+    # first left and right
+    peakwindows.append(peakpositions[0] - deltas[0])
+    peakwindows.append(peakpositions[0] + deltas[0])
+    # ones in the middle
+    for i in range(1, len(peakpositions) - 1):
+        peakwindows.append(peakpositions[i] - deltas[i - 1])
+        peakwindows.append(peakpositions[i] + deltas[i])
+    # last one
+    peakwindows.append(peakpositions[-1] - deltas[-1])
+    peakwindows.append(peakpositions[-1] + deltas[-1])
+    return peakwindows
+
+
 def plot2d(workspace, tolerance: float=0.001, peakpositions: np.ndarray=DIAMOND,
            xmin: float=np.nan, xmax: float=np.nan, horiz_markers=[]):
     TOLERANCE_COLOR = 'w'  # color to mark the area within tolerance
@@ -575,3 +596,103 @@ def plot_peakd(wksp: Union[str, Workspace2D], peak_positions: Union[float, list]
     plt.show()
 
     return fig, ax
+
+def plot_corr(tof_ws):
+    """
+    Plots Pearson correlation coefficient for each detector
+    :param tof_ws: Workspace returned from collect_fit_result
+    :return: plot, plot axes
+    """
+    if not mtd.doesExist(str(tof_ws)):
+        raise ValueError("Could not find the provided workspace in ADS")
+
+    tof_ws = mtd[str(tof_ws)]
+
+    numHist = tof_ws.getNumberHistograms()
+
+    # Create an array for Pearson corr coef
+    r_vals = np.empty((numHist,), dtype=float)
+    r_vals.fill(np.nan)
+
+    # Create an array for detector IDs
+    detectors = tof_ws.detectorInfo().detectorIDs()
+    detID = np.empty((numHist,), dtype=float)
+    detID.fill(np.nan)
+
+    for workspaceIndex in range(numHist):
+        # Get Pearson correlation coefficient for each detector
+        x = tof_ws.dataY(workspaceIndex)
+        y = tof_ws.dataX(workspaceIndex)
+
+        mask = np.logical_not(np.isnan(x))
+        if np.sum(mask) > 1:
+            r, p = np.corrcoef(x[mask], y[mask])
+            # Use r[1] because the corr coef is always the off-diagonal element here
+            r_vals[workspaceIndex] = r[1]
+        else:
+            r_vals[workspaceIndex] = np.nan
+
+        # Get detector ID for this spectrum
+        detID[workspaceIndex] = detectors[workspaceIndex]
+
+    fig, ax = plt.subplots()
+    ax.set_xlabel("det IDs")
+    ax.set_ylabel("Pearson correlation coefficient (TOF, d)")
+
+    ax.plot(detID, r_vals, marker="x", linestyle="None")
+
+def plot_peak_info(wksp: Union[str, TableWorkspace], peak_positions: Union[float, list], donor=None):
+    """
+    Generates a plot using the PeakParameter Workspace returned from FitPeaks to show peak information
+    (center, width, height, and intensity) for each bank at different peak positions.
+    :param wksp: Peak Parameter TableWorkspace returned from FitPeaks
+    :param peak_positions: List of peak positions to plot peak info at
+    :param donor: Optional donor Workspace2D to use for collect_fit_result
+    :return: plot, plot axes for each information type
+    """
+    wksp = mtd[str(wksp)]
+
+    if not mtd.doesExist(str(wksp)):
+        raise ValueError("Could not find provided workspace in ADS")
+
+    peaks = peak_positions
+    if isinstance(peak_positions, float):
+        peaks = [peak_positions]
+
+    if len(peaks) == 0:
+        raise ValueError("Expected one or more peak positions")
+
+    # Generate workspaces for each kind of peak info
+    center = collect_fit_result(wksp, 'center', peaks, donor=donor, infotype='centre')
+    width = collect_fit_result(wksp, 'width', peaks, donor=donor, infotype='width')
+    height = collect_fit_result(wksp, 'height', peaks, donor=donor, infotype='height')
+    intensity = collect_fit_result(wksp, 'intensity', peaks, donor=donor,
+        infotype='intensity')
+
+    nbanks = center.getNumberHistograms()
+    workspaces = [center, width, height, intensity]
+    labels = ['center', 'width', 'height', 'intensity']
+
+    fig, ax = plt.subplots(len(workspaces), 1, sharex="all")
+    for i in range(len(workspaces)):
+        ax[i].set_ylabel(labels[i])
+
+    # Show small ticks on x axis at peak positions
+    ax[-1].set_xticks(peaks, True)
+    ax[-1].set_xlabel(r"peak position ($\AA$)")
+
+    markers = ["x", ".", "^", "s", "d", "h", "p", "v"]
+    for i in range(nbanks):
+        for j in range(len(workspaces)):
+            x = workspaces[j].dataX(i)
+            data = workspaces[j].dataY(i)
+            # Cycle through marker list if there are too many banks
+            marker = i % len(markers)
+            ax[j].plot(x, data, marker=markers[marker], ls="None", label="bank{}".format(i))
+
+    ax[0].legend()
+    fig.tight_layout()
+
+    plt.show()
+
+    return fig, ax
diff --git a/scripts/Diffraction/isis_powder/abstract_inst.py b/scripts/Diffraction/isis_powder/abstract_inst.py
index 62b5a8c40ab1a194af43bce235a5bccadc2471e0..2e402d3142c1bd50f24f71a9afd1a085ef97945b 100644
--- a/scripts/Diffraction/isis_powder/abstract_inst.py
+++ b/scripts/Diffraction/isis_powder/abstract_inst.py
@@ -7,6 +7,7 @@
 import os
 from isis_powder.routines import calibrate, focus, common, common_enums, common_output
 from mantid.kernel import config, logger
+import mantid.simpleapi as mantid
 # This class provides common hooks for instruments to override
 # if they want to define the behaviour of the hook. Otherwise it
 # returns the object passed in without manipulating it as a default
@@ -276,6 +277,11 @@ class AbstractInst(object):
         """
         return None
 
+    def apply_calibration_to_focused_data(self, focused_ws):
+        # convert back to TOF based on engineered detector positions
+        mantid.ApplyDiffCal(InstrumentWorkspace=focused_ws,
+                            ClearCalibration=True)
+
     # Steps applicable to all instruments
 
     @staticmethod
diff --git a/scripts/Diffraction/isis_powder/routines/calibrate.py b/scripts/Diffraction/isis_powder/routines/calibrate.py
index 5884992041952c994ffb5ca70470a0fb9bad97e7..9e1e42c859c2e57295350da3bd119ca2a5aed2a3 100644
--- a/scripts/Diffraction/isis_powder/routines/calibrate.py
+++ b/scripts/Diffraction/isis_powder/routines/calibrate.py
@@ -43,21 +43,23 @@ def create_van(instrument, run_details, absorb):
         # Assume that create_van only uses Vanadium runs
         mantid.SetSampleMaterial(InputWorkspace=corrected_van_ws, ChemicalFormula='V')
 
-    aligned_ws = mantid.AlignDetectors(InputWorkspace=corrected_van_ws,
-                                       CalibrationFile=run_details.offset_file_path)
+    mantid.ApplyDiffCal(InstrumentWorkspace=corrected_van_ws,
+                        CalibrationFile=run_details.offset_file_path)
+    aligned_ws = mantid.ConvertUnits(InputWorkspace=corrected_van_ws, Target="dSpacing")
     solid_angle = instrument.get_solid_angle_corrections(run_details.run_number, run_details)
     if solid_angle:
         aligned_ws = mantid.Divide(LHSWorkspace=aligned_ws,RHSWorkspace=solid_angle)
         mantid.DeleteWorkspace(solid_angle)
     focused_vanadium = mantid.DiffractionFocussing(InputWorkspace=aligned_ws,
                                                    GroupingFileName=run_details.grouping_file_path)
-
+    # convert back to TOF based on engineered detector positions
+    mantid.ApplyDiffCal(InstrumentWorkspace=focused_vanadium,
+                        ClearCalibration=True)
     focused_spectra = common.extract_ws_spectra(focused_vanadium)
     focused_spectra = instrument._crop_van_to_expected_tof_range(focused_spectra)
 
     d_spacing_group, tof_group = instrument._output_focused_ws(processed_spectra=focused_spectra,
                                                                run_details=run_details)
-
     _create_vanadium_splines(focused_spectra, instrument, run_details)
 
     common.keep_single_ws_unit(d_spacing_group=d_spacing_group, tof_group=tof_group,
diff --git a/scripts/Diffraction/isis_powder/routines/focus.py b/scripts/Diffraction/isis_powder/routines/focus.py
index 51afbfc32585a176f38eaf37bcf735d02f90d7c3..0fb1cc8e2935cc8718acd6de98982ca5d8b0c6ae 100644
--- a/scripts/Diffraction/isis_powder/routines/focus.py
+++ b/scripts/Diffraction/isis_powder/routines/focus.py
@@ -65,8 +65,9 @@ def _focus_one_ws(input_workspace, run_number, instrument, perform_vanadium_norm
                              Geometry=common.generate_sample_geometry(sample_details),
                              Material=common.generate_sample_material(sample_details))
     # Align
-    aligned_ws = mantid.AlignDetectors(InputWorkspace=input_workspace,
-                                       CalibrationFile=run_details.offset_file_path)
+    mantid.ApplyDiffCal(InstrumentWorkspace=input_workspace,
+                        CalibrationFile=run_details.offset_file_path)
+    aligned_ws = mantid.ConvertUnits(InputWorkspace=input_workspace, Target="dSpacing")
 
     solid_angle = instrument.get_solid_angle_corrections(run_details.vanadium_run_numbers, run_details)
     if solid_angle:
@@ -77,6 +78,8 @@ def _focus_one_ws(input_workspace, run_number, instrument, perform_vanadium_norm
     focused_ws = mantid.DiffractionFocussing(InputWorkspace=aligned_ws,
                                              GroupingFileName=run_details.grouping_file_path)
 
+    instrument.apply_calibration_to_focused_data(focused_ws)
+
     calibrated_spectra = _apply_vanadium_corrections(instrument=instrument,
                                                      input_workspace=focused_ws,
                                                      perform_vanadium_norm=perform_vanadium_norm,
diff --git a/scripts/Interface/reduction_gui/reduction/diffraction/diffraction_adv_setup_script.py b/scripts/Interface/reduction_gui/reduction/diffraction/diffraction_adv_setup_script.py
index 5b77ae02c7373c025b1c7d8fdd700280811a35ea..e5c5997c5a735c84514087ec937a68e4e083e935 100644
--- a/scripts/Interface/reduction_gui/reduction/diffraction/diffraction_adv_setup_script.py
+++ b/scripts/Interface/reduction_gui/reduction/diffraction/diffraction_adv_setup_script.py
@@ -83,7 +83,7 @@ class AdvancedSetupScript(BaseScriptElement):
     def cache_dir(self):
         """Passing all three candidates back as one list"""
         # A comma ',' is used here since it is the default delimiter in mantid
-        return ",".join(self.cache_dir_scan_save, self.cache_dir_scan_1, self.cache_dir_scan_2)
+        return ",".join((self.cache_dir_scan_save, self.cache_dir_scan_1, self.cache_dir_scan_2))
 
     def __init__(self, inst_name):
         """ Initialization
@@ -179,7 +179,7 @@ class AdvancedSetupScript(BaseScriptElement):
         pardict["SampleNumberDensity"] = self.samplenumberdensity
         pardict["ContainerShape"] = self.containershape
         #Caching options
-        pardict['CacheDir'] = ";".join(self.cache_dir)
+        pardict['CacheDir'] = self.cache_dir
         pardict['CleanCache'] = str(int(self.clean_cache))
 
         return pardict
@@ -195,7 +195,7 @@ class AdvancedSetupScript(BaseScriptElement):
                 # special map for bool type
                 value = '1' if value else '0'
             else:
-                value = ";".join(self.cache_dir) if keyname == "CacheDir" else str(value)
+                value = str(value)
 
             xml += f"<{keyname.lower()}>{value}</{keyname.lower()}>\n"
         xml += "</AdvancedSetup>\n"
@@ -288,8 +288,13 @@ class AdvancedSetupScript(BaseScriptElement):
             # Caching options
             # split it into the three cache dirs
             # NOTE: there should only be three entries, if not, let it fail early
-            self.cache_dir_scan_save, self.cache_dir_scan_1, self.cache_dir_scan_2 = BaseScriptElement.getStringElement(
-                instrument_dom, 'cachedir', default=";;").split(";")
+            try:
+                self.cache_dir_scan_save, self.cache_dir_scan_1, self.cache_dir_scan_2 = BaseScriptElement.getStringElement(
+                    instrument_dom, 'cachedir', default=";;").replace(";", ",").split(",")
+            except ValueError:
+                self.cache_dir_scan_save = ''
+                self.cache_dir_scan_1 = ''
+                self.cache_dir_scan_2 = ''
 
             tempbool = BaseScriptElement.getStringElement(instrument_dom,
                                                           "cleancache",
diff --git a/scripts/Interface/ui/drill/model/DrillExportModel.py b/scripts/Interface/ui/drill/model/DrillExportModel.py
index 7d5a8348abe7409dcac629c8f136b536b0fea772..f6dac962efd07ae14e531fc713c29524afc0ad9f 100644
--- a/scripts/Interface/ui/drill/model/DrillExportModel.py
+++ b/scripts/Interface/ui/drill/model/DrillExportModel.py
@@ -5,7 +5,7 @@
 #     & Institut Laue - Langevin
 # SPDX - License - Identifier: GPL - 3.0 +
 
-from mantid.simpleapi import mtd
+from mantid.simpleapi import mtd, AlgorithmManager
 from mantid.kernel import config, logger
 from mantid.api import WorkspaceGroup
 
@@ -24,6 +24,16 @@ class DrillExportModel:
     """
     _exportAlgorithms = None
 
+    """
+    Dictionnary of export file extensions.
+    """
+    _exportExtensions = None
+
+    """
+    Dictionnary of export algorithm short doc.
+    """
+    _exportDocs = None
+
     """
     ThreadPool to run export algorithms asynchronously.
     """
@@ -49,6 +59,17 @@ class DrillExportModel:
         self._exportAlgorithms = {k:v
                 for k,v
                 in RundexSettings.EXPORT_ALGORITHMS[acquisitionMode].items()}
+        self._exportExtensions = dict()
+        self._exportDocs = dict()
+        for a in self._exportAlgorithms.keys():
+            if a in RundexSettings.EXPORT_ALGO_EXTENSION:
+                self._exportExtensions[a] = \
+                    RundexSettings.EXPORT_ALGO_EXTENSION[a]
+            try:
+                alg = AlgorithmManager.createUnmanaged(a)
+                self._exportDocs[a] = alg.summary()
+            except:
+                pass
         self._pool = DrillAlgorithmPool()
         self._pool.signals.taskError.connect(self._onTaskError)
         self._pool.signals.taskSuccess.connect(self._onTaskSuccess)
@@ -64,6 +85,24 @@ class DrillExportModel:
         """
         return [algo for algo in self._exportAlgorithms.keys()]
 
+    def getAlgorithmExtentions(self):
+        """
+        Get the extension used for the output file of each export algorithm.
+
+        Returns:
+            dict(str:str): dictionnary algo:extension
+        """
+        return {k:v for k,v in self._exportExtensions.items()}
+
+    def getAlgorithmDocs(self):
+        """
+        Get the short documentation of each export algorithm.
+
+        Return:
+            dict(str:str): dictionnary algo:doc
+        """
+        return {k:v for k,v in self._exportDocs.items()}
+
     def isAlgorithmActivated(self, algorithm):
         """
         Get the state of a specific algorithm.
@@ -190,7 +229,10 @@ class DrillExportModel:
         """
         exportPath = config.getString("defaultsave.directory")
         if not exportPath:
-            exportPath = os.getcwd()
+            logger.warning("Default save directory is not defined. Please "
+                           "specify one in the data directories dialog to "
+                           "enable exports.")
+            return
         workspaceName = sample.getOutputName()
 
         try:
diff --git a/scripts/Interface/ui/drill/model/DrillModel.py b/scripts/Interface/ui/drill/model/DrillModel.py
index 8e5d5fad29c77d8555ebb0fc9ac4553b3a068277..db7afa114700923ecffd85a6d8870acb360cef7d 100644
--- a/scripts/Interface/ui/drill/model/DrillModel.py
+++ b/scripts/Interface/ui/drill/model/DrillModel.py
@@ -28,7 +28,7 @@ class DrillModel(QObject):
     """
     Data directory on Linux.
     """
-    LINUX_BASE_DATA_PATH = "/net/serdon/illdata/"
+    LINUX_BASE_DATA_PATH = "/net4/serdon/illdata/"
 
     """
     Data directory on MacOS.
diff --git a/scripts/Interface/ui/drill/model/configurations.py b/scripts/Interface/ui/drill/model/configurations.py
index 70e0ac599b76d281e8e45d7f4d1bf3f5cea4ece1..d3f7ab04d5ea4d0192df9e2b6c8e4489c1fc5338 100644
--- a/scripts/Interface/ui/drill/model/configurations.py
+++ b/scripts/Interface/ui/drill/model/configurations.py
@@ -161,7 +161,7 @@ class RundexSettings(object):
             POWDER_DSCAN: {
                 "SaveNexusProcessed": False,
                 "SaveAscii": False,
-                "SaveFocussedXYE": True
+                "SaveFocusedXYE": True
                 },
             POWDER_PSCAN: {
                 "SaveNexusProcessed": False,
diff --git a/scripts/Interface/ui/drill/presenter/DrillExportPresenter.py b/scripts/Interface/ui/drill/presenter/DrillExportPresenter.py
index 6226257a3077bfb35bb7f2833f8e09195c836bd9..a0c510ff83c9b17bd4ef04bbf6af877b39e88465 100644
--- a/scripts/Interface/ui/drill/presenter/DrillExportPresenter.py
+++ b/scripts/Interface/ui/drill/presenter/DrillExportPresenter.py
@@ -30,7 +30,9 @@ class DrillExportPresenter:
         self._model = model
         self._view.setPresenter(self)
         algorithms = self._model.getAlgorithms()
-        self._view.setAlgorithms(algorithms)
+        extensions = self._model.getAlgorithmExtentions()
+        tooltips = self._model.getAlgorithmDocs()
+        self._view.setAlgorithms(algorithms, extensions, tooltips)
         states = dict()
         for a in algorithms:
             states[a] = self._model.isAlgorithmActivated(a)
diff --git a/scripts/Interface/ui/drill/presenter/DrillPresenter.py b/scripts/Interface/ui/drill/presenter/DrillPresenter.py
index b083571ad59c0fca2f8b6abe445c5916b5b9a006..f4b681bf962b7ea73656e391efced212d1fc58be 100644
--- a/scripts/Interface/ui/drill/presenter/DrillPresenter.py
+++ b/scripts/Interface/ui/drill/presenter/DrillPresenter.py
@@ -114,8 +114,8 @@ class DrillPresenter:
                                       "separated key=value pairs.")
                     return
                 try:
-                    name = option.split("=")[0]
-                    value = option.split("=")[1]
+                    name = option.split("=")[0].strip()
+                    value = option.split("=")[1].strip()
                 except:
                     self.onParamError(row, column, "Please provide semicolon "
                                       "separated key=value pairs.")
@@ -190,6 +190,10 @@ class DrillPresenter:
                     return value
 
             if re.match("^-{,1}\d+$", value):
+                if int(value) == 0:
+                    return value
+                if int(value) + i == 0:
+                    return value
                 return str(int(value) + i)
             suffix = re.search("\d+$", value)
             if suffix:
@@ -533,6 +537,8 @@ class DrillPresenter:
 
     def _syncViewTable(self):
         columns, tooltips = self.model.getColumnHeaderData()
+        if tooltips and tooltips[-1] == "CustomOptions":
+            tooltips[-1] = "Provide semicolon (;) separated key=value pairs"
         samples = self.model.getSamples()
         groups = self.model.getSamplesGroups()
         masters = self.model.getMasterSamples()
diff --git a/scripts/Interface/ui/drill/test/DrillTest.py b/scripts/Interface/ui/drill/test/DrillTest.py
index c6d72400180a097f3dcb4e29192d15faaeae08f7..2c75f5e9994f49d97b288446f14264bbacb66973 100644
--- a/scripts/Interface/ui/drill/test/DrillTest.py
+++ b/scripts/Interface/ui/drill/test/DrillTest.py
@@ -512,11 +512,11 @@ class DrillTest(unittest.TestCase):
 
         # increment
         self.view.increment.setValue(7)
-        self.setCellContents(0, 1, "0")
+        self.setCellContents(0, 1, "1")
         self.selectColumn(1, Qt.NoModifier)
         QTest.mouseClick(self.view.fill, Qt.LeftButton)
         column = self.model.columns[1]
-        value = 0
+        value = 1
         for i in range(10):
             self.assertEqual(self.model.samples[i]._parameters[column],
                              str(value))
diff --git a/scripts/Interface/ui/drill/view/DrillExportDialog.py b/scripts/Interface/ui/drill/view/DrillExportDialog.py
index b5284d5007ad9b9a150821d0fe94ca38cec44969..6fc5533ff7f74482949f61fe36a8cd635eba6a0d 100644
--- a/scripts/Interface/ui/drill/view/DrillExportDialog.py
+++ b/scripts/Interface/ui/drill/view/DrillExportDialog.py
@@ -56,15 +56,24 @@ class DrillExportDialog(QDialog):
         """
         self._presenter = presenter
 
-    def setAlgorithms(self, algorithms):
+    def setAlgorithms(self, algorithms, extensions, tooltips):
         """
         Set the algorithms displayed on the dialog.
 
         Args:
             algorithms (list(str)): list of algorithms
+            extensions (dict(str:str)): extension used by each algorithm
+            tooltips (dict(str:str)): short doc of each algorithm
         """
         for i in range(len(algorithms)):
-            widget = QCheckBox(algorithms[i], self)
+            algo = algorithms[i]
+            if algo in extensions:
+                text = algo + " (" + extensions[algo] + ")"
+            else:
+                text = algo
+            widget = QCheckBox(text, self)
+            if algo in tooltips:
+                widget.setToolTip(tooltips[algo])
             self._widgets[algorithms[i]] = widget
             self.algoList.addWidget(widget, i, 0, Qt.AlignLeft)
             helpButton = QToolButton(self)